@opengeni/db 4.4.0-canary.0 → 4.4.0-canary.2
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/dist/canonical-human-identities.js +4 -3
- package/dist/{chunk-2ZMTQVTR.js → chunk-2KOLL33E.js} +2 -2
- package/dist/chunk-45CP2YKY.js +1062 -0
- package/dist/chunk-45CP2YKY.js.map +1 -0
- package/dist/{chunk-6TKL3GQO.js → chunk-5UUWZDAV.js} +6531 -6480
- package/dist/chunk-5UUWZDAV.js.map +1 -0
- package/dist/{chunk-QLA3UQNX.js → chunk-6IJD3XXF.js} +17 -3
- package/dist/chunk-6IJD3XXF.js.map +1 -0
- package/dist/{chunk-232VC2LW.js → chunk-6V3HDEMR.js} +2 -2
- package/dist/{chunk-M7PSMVFF.js → chunk-CHBLBEU6.js} +2 -2
- package/dist/chunk-M4PKBJRS.js +20318 -0
- package/dist/chunk-M4PKBJRS.js.map +1 -0
- package/dist/chunk-S2TZ3F4L.js +237 -0
- package/dist/chunk-S2TZ3F4L.js.map +1 -0
- package/dist/{chunk-UK57QA2H.js → chunk-UOXWVP3T.js} +2 -2
- package/dist/{chunk-GVZJ7XSR.js → chunk-WFU6PS54.js} +11 -4
- package/dist/chunk-WFU6PS54.js.map +1 -0
- package/dist/{chunk-THJGMEK5.js → chunk-XJP7KJ6U.js} +121 -66
- package/dist/chunk-XJP7KJ6U.js.map +1 -0
- package/dist/connection-token-resolver.d.ts +2 -0
- package/dist/database.d.ts +2 -0
- package/dist/editable-artifact-durable-export.js +3 -2
- package/dist/editable-artifact-durable-export.js.map +1 -1
- package/dist/editable-artifacts.js +4 -3
- package/dist/external-identities.d.ts +1 -0
- package/dist/external-membership-operations.d.ts +44 -0
- package/dist/governed-learning-evaluator.d.ts +1 -14
- package/dist/host-mcp-bindings.d.ts +4 -0
- package/dist/host-mcp-resolvers.d.ts +47 -0
- package/dist/host-mcp-task-authority.d.ts +2 -6
- package/dist/index.d.ts +29 -31
- package/dist/index.js +8616 -8321
- package/dist/index.js.map +1 -1
- package/dist/knowledge-document-preparation.d.ts +56 -0
- package/dist/knowledge-entries-schema.d.ts +2479 -0
- package/dist/knowledge-entries.d.ts +421 -0
- package/dist/knowledge-indexing.d.ts +99 -0
- package/dist/knowledge-migration.d.ts +6 -0
- package/dist/managed-auth-session-sets.js +4 -3
- package/dist/mcp-operations.js +3 -2
- package/dist/mcp-operations.js.map +1 -1
- package/dist/migrate.js +2 -1
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +3 -2
- package/dist/retained-provider-commands.js.map +1 -1
- package/dist/runtime-posture.d.ts +7 -7
- package/dist/schema.d.ts +115 -12
- package/dist/schema.js +26 -1
- package/dist/session-background-commands.js +5 -4
- package/dist/session-command-output.js +8 -6
- package/dist/session-command-output.js.map +1 -1
- package/dist/session-event-slices.js +6 -4
- package/dist/session-event-slices.js.map +1 -1
- package/dist/session-mcp-credential-rotation.js +5 -4
- package/dist/session-mcp-credential-rotation.js.map +1 -1
- package/dist/session-tenancy.js +4 -3
- package/dist/video-generation.js +4 -3
- package/dist/workspace-artifacts.d.ts +1 -0
- package/dist/workspace-instruction-policies-schema.d.ts +21 -2
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-membership-access.d.ts +11 -0
- package/dist/workspace-tool-defaults.js +5 -4
- package/dist/workspace-tool-defaults.js.map +1 -1
- package/drizzle/0461_unified_knowledge.sql +2859 -0
- package/drizzle/0462_agent_instruction_non_destructive_edits.sql +212 -0
- package/drizzle/0463_host_mcp_resolver_registration.sql +111 -0
- package/drizzle/0464_external_workspace_membership_operations.sql +166 -0
- package/package.json +6 -6
- package/src/connection-token-resolver.ts +75 -6
- package/src/database.ts +12 -1
- package/src/external-identities.ts +1 -0
- package/src/external-membership-operations.ts +134 -0
- package/src/governed-learning-evaluator.ts +0 -69
- package/src/host-mcp-binding-match.ts +1 -0
- package/src/host-mcp-bindings.ts +24 -1
- package/src/host-mcp-resolvers.ts +266 -0
- package/src/host-mcp-task-authority.ts +11 -10
- package/src/index.ts +307 -128
- package/src/knowledge-document-preparation.ts +90 -0
- package/src/knowledge-entries-schema.ts +264 -0
- package/src/knowledge-entries.ts +860 -0
- package/src/knowledge-indexing.ts +126 -0
- package/src/knowledge-migration.ts +761 -0
- package/src/knowledge-source-sync.ts +46 -7
- package/src/migrate.ts +19 -0
- package/src/provision-roles.ts +23 -57
- package/src/runtime-posture.ts +103 -7
- package/src/schema.ts +15 -3
- package/src/skills.ts +4 -0
- package/src/video-generation.ts +14 -0
- package/src/workspace-artifacts.ts +2 -0
- package/src/workspace-instruction-policies-schema.ts +1 -0
- package/src/workspace-membership-access.ts +61 -0
- package/dist/chunk-6TKL3GQO.js.map +0 -1
- package/dist/chunk-EYF6MH6C.js +0 -4772
- package/dist/chunk-EYF6MH6C.js.map +0 -1
- package/dist/chunk-GSBONN55.js +0 -533
- package/dist/chunk-GSBONN55.js.map +0 -1
- package/dist/chunk-GVZJ7XSR.js.map +0 -1
- package/dist/chunk-QLA3UQNX.js.map +0 -1
- package/dist/chunk-THJGMEK5.js.map +0 -1
- /package/dist/{chunk-2ZMTQVTR.js.map → chunk-2KOLL33E.js.map} +0 -0
- /package/dist/{chunk-232VC2LW.js.map → chunk-6V3HDEMR.js.map} +0 -0
- /package/dist/{chunk-M7PSMVFF.js.map → chunk-CHBLBEU6.js.map} +0 -0
- /package/dist/{chunk-UK57QA2H.js.map → chunk-UOXWVP3T.js.map} +0 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
FileAsset,
|
|
4
|
+
KnowledgeFilePreparationResult,
|
|
5
|
+
AgentLearningDefaults,
|
|
6
|
+
AgentLearningOverridePatch,
|
|
7
|
+
AgentInstructionSaveRequest,
|
|
8
|
+
AgentInstructionReviewRequest,
|
|
9
|
+
AgentInstructionReceipt,
|
|
10
|
+
AgentInstructionReviewItem,
|
|
11
|
+
WorkspaceInstructionPolicyTarget,
|
|
12
|
+
KnowledgeEntryListRequest,
|
|
13
|
+
KnowledgeReviewBatch,
|
|
14
|
+
KnowledgeReviewBatchListRequest,
|
|
15
|
+
KnowledgeEntryBatchReviewRequest,
|
|
16
|
+
KnowledgeTaskNotePromotionRequest,
|
|
17
|
+
KnowledgeEntryRecord,
|
|
18
|
+
KnowledgeEntryReviewRequest,
|
|
19
|
+
KnowledgeEntryRestoreRequest,
|
|
20
|
+
KnowledgeEntrySaveRequest,
|
|
21
|
+
KnowledgeEntrySummary,
|
|
22
|
+
KnowledgeEntryWriteReceipt,
|
|
23
|
+
type AgentLearningContext,
|
|
24
|
+
type KnowledgeEntryScope,
|
|
25
|
+
} from "@opengeni/contracts";
|
|
26
|
+
import { sql } from "drizzle-orm";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { rawRows, withWorkspaceRls, withWorkspaceSubjectRls, type Database } from "./database";
|
|
29
|
+
import {
|
|
30
|
+
fromPostgresLosslessJson,
|
|
31
|
+
fromPostgresLosslessText,
|
|
32
|
+
LOSSLESS_CONTENT_CODEC_VERSION,
|
|
33
|
+
toPostgresLosslessJson,
|
|
34
|
+
toPostgresLosslessText,
|
|
35
|
+
} from "./lossless-json";
|
|
36
|
+
|
|
37
|
+
/** Trusted host claims. Never deserialize this context from tool/request input. */
|
|
38
|
+
export type KnowledgeActor =
|
|
39
|
+
| {
|
|
40
|
+
kind: "service";
|
|
41
|
+
principalKind: "service" | "api_key" | "configured_key" | "mcp_gateway";
|
|
42
|
+
subjectId: string;
|
|
43
|
+
writeScopes: Array<"workspace" | "organization">;
|
|
44
|
+
review: false;
|
|
45
|
+
settingsScopes: [];
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
kind: "human";
|
|
49
|
+
principalKind: "human_session";
|
|
50
|
+
subjectId: string;
|
|
51
|
+
writeScopes: KnowledgeEntryScope[];
|
|
52
|
+
settingsScopes: Array<"workspace" | "personal">;
|
|
53
|
+
review: boolean;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
kind: "agent";
|
|
57
|
+
sessionId: string;
|
|
58
|
+
turnId: string;
|
|
59
|
+
attemptId: string;
|
|
60
|
+
executionGeneration: number;
|
|
61
|
+
};
|
|
62
|
+
export type KnowledgeContext = { accountId: string; workspaceId: string; actor: KnowledgeActor };
|
|
63
|
+
|
|
64
|
+
async function inKnowledgeContext<T>(
|
|
65
|
+
db: Database,
|
|
66
|
+
context: KnowledgeContext,
|
|
67
|
+
fn: (tx: Database) => Promise<T>,
|
|
68
|
+
) {
|
|
69
|
+
if (context.actor.kind === "agent") return withWorkspaceRls(db, context.workspaceId, fn);
|
|
70
|
+
return withWorkspaceSubjectRls(db, context.workspaceId, context.actor.subjectId, async (tx) => {
|
|
71
|
+
await tx.execute(
|
|
72
|
+
sql`SELECT set_config('opengeni.principal_kind', ${context.actor.kind === "agent" ? "agent_attempt" : context.actor.principalKind}, true)`,
|
|
73
|
+
);
|
|
74
|
+
return fn(tx);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function apply(db: Database, context: KnowledgeContext, request: Record<string, unknown>) {
|
|
79
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
80
|
+
const [row] = await rawRows<{ receipt: unknown }>(
|
|
81
|
+
tx,
|
|
82
|
+
sql`
|
|
83
|
+
SELECT knowledge_entry_apply(${context.accountId}::uuid,${context.workspaceId}::uuid,
|
|
84
|
+
${JSON.stringify(context.actor)}::jsonb,${JSON.stringify(request)}::jsonb) AS receipt`,
|
|
85
|
+
);
|
|
86
|
+
return KnowledgeEntryWriteReceipt.parse(row?.receipt);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Freeze before model execution; retries of the logical turn recover the same policy. */
|
|
91
|
+
export async function freezeAgentLearningPolicy(db: Database, context: KnowledgeContext) {
|
|
92
|
+
if (context.actor.kind !== "agent")
|
|
93
|
+
throw new Error("Learning snapshots require an exact agent attempt");
|
|
94
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
95
|
+
const [row] = await rawRows<{ policy: unknown }>(
|
|
96
|
+
tx,
|
|
97
|
+
sql`SELECT knowledge_entry_read(${context.accountId}::uuid,
|
|
98
|
+
${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,'{"operation":"policy"}'::jsonb) AS policy`,
|
|
99
|
+
);
|
|
100
|
+
return z
|
|
101
|
+
.object({
|
|
102
|
+
defaultScope: z.enum(["workspace", "personal"]),
|
|
103
|
+
subjectId: z.string().nullable(),
|
|
104
|
+
effective: AgentLearningDefaults,
|
|
105
|
+
})
|
|
106
|
+
.passthrough()
|
|
107
|
+
.parse(row?.policy);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function saveKnowledgeEntry(
|
|
112
|
+
db: Database,
|
|
113
|
+
context: KnowledgeContext,
|
|
114
|
+
input: KnowledgeEntrySaveRequest,
|
|
115
|
+
) {
|
|
116
|
+
const request = KnowledgeEntrySaveRequest.parse(input);
|
|
117
|
+
return apply(db, context, {
|
|
118
|
+
...request,
|
|
119
|
+
operation: "save",
|
|
120
|
+
entry: toPostgresLosslessJson(request.entry),
|
|
121
|
+
codecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
|
|
122
|
+
preview: toPostgresLosslessText(request.entry.content.slice(0, 512)),
|
|
123
|
+
searchText: toPostgresLosslessText(`${request.entry.title}\n${request.entry.content}`),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function promoteTaskNoteToKnowledge(
|
|
128
|
+
db: Database,
|
|
129
|
+
context: KnowledgeContext,
|
|
130
|
+
input: KnowledgeTaskNotePromotionRequest,
|
|
131
|
+
) {
|
|
132
|
+
return apply(db, context, {
|
|
133
|
+
...KnowledgeTaskNotePromotionRequest.parse(input),
|
|
134
|
+
operation: "promote_note",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function reviewKnowledgeEntry(
|
|
139
|
+
db: Database,
|
|
140
|
+
context: KnowledgeContext,
|
|
141
|
+
input: KnowledgeEntryReviewRequest,
|
|
142
|
+
) {
|
|
143
|
+
const { decision, entry, ...request } = KnowledgeEntryReviewRequest.parse(input);
|
|
144
|
+
return apply(db, context, {
|
|
145
|
+
...request,
|
|
146
|
+
operation: entry ? "approve_edit" : decision,
|
|
147
|
+
...(entry
|
|
148
|
+
? {
|
|
149
|
+
entry: toPostgresLosslessJson(entry),
|
|
150
|
+
codecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
|
|
151
|
+
preview: toPostgresLosslessText(entry.content.slice(0, 512)),
|
|
152
|
+
searchText: toPostgresLosslessText(`${entry.title}\n${entry.content}`),
|
|
153
|
+
}
|
|
154
|
+
: {}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Atomic review of exact revisions; dependencies publish before their findings. */
|
|
159
|
+
export async function reviewKnowledgeEntries(
|
|
160
|
+
db: Database,
|
|
161
|
+
context: KnowledgeContext,
|
|
162
|
+
input: KnowledgeEntryBatchReviewRequest,
|
|
163
|
+
) {
|
|
164
|
+
const request = KnowledgeEntryBatchReviewRequest.parse(input);
|
|
165
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
166
|
+
// Same lock/order as individual publication. No unrelated reviewer can
|
|
167
|
+
// change a selected revision between ordering, validation and application.
|
|
168
|
+
await tx.execute(
|
|
169
|
+
sql`SELECT 1 FROM workspaces WHERE id=${context.workspaceId}::uuid AND account_id=${context.accountId}::uuid FOR KEY SHARE`,
|
|
170
|
+
);
|
|
171
|
+
await tx.execute(
|
|
172
|
+
sql`SELECT pg_advisory_xact_lock(hashtextextended(${`knowledge-publication:${context.accountId}`},0))`,
|
|
173
|
+
);
|
|
174
|
+
const remaining = new Map(request.entries.map((entry) => [entry.entryId, entry]));
|
|
175
|
+
const pending = new Map<string, KnowledgeEntryRecord>();
|
|
176
|
+
for (const entry of request.entries) {
|
|
177
|
+
const record = await getKnowledgeEntry(tx, context, entry.entryId, { view: "needs_review" });
|
|
178
|
+
if (record) pending.set(entry.entryId, record);
|
|
179
|
+
}
|
|
180
|
+
const receipts: KnowledgeEntryWriteReceipt[] = [];
|
|
181
|
+
while (remaining.size) {
|
|
182
|
+
let advanced = false;
|
|
183
|
+
for (const [id, entry] of remaining) {
|
|
184
|
+
const record = pending.get(id);
|
|
185
|
+
const content = entry.entry ?? record?.revision.entry;
|
|
186
|
+
const dependencies =
|
|
187
|
+
entry.decision === "approve" && record?.revision.change !== "archive" && content
|
|
188
|
+
? [
|
|
189
|
+
...(content.evidence ?? [])
|
|
190
|
+
.filter(
|
|
191
|
+
(evidence) =>
|
|
192
|
+
pending.get(evidence.entryId)?.revision.id === evidence.revisionId,
|
|
193
|
+
)
|
|
194
|
+
.map((evidence) => evidence.entryId),
|
|
195
|
+
...[
|
|
196
|
+
...(content.groupIds ?? []),
|
|
197
|
+
...(content.relationships ?? []).map((relation) => relation.entryId),
|
|
198
|
+
].filter(
|
|
199
|
+
(dependency) =>
|
|
200
|
+
pending.has(dependency) && !pending.get(dependency)?.publishedRevisionId,
|
|
201
|
+
),
|
|
202
|
+
]
|
|
203
|
+
: [];
|
|
204
|
+
if (
|
|
205
|
+
dependencies.some(
|
|
206
|
+
(dependency) =>
|
|
207
|
+
remaining.has(dependency) && remaining.get(dependency)?.decision === "approve",
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
continue;
|
|
211
|
+
receipts.push(await reviewKnowledgeEntry(tx, context, entry));
|
|
212
|
+
remaining.delete(id);
|
|
213
|
+
advanced = true;
|
|
214
|
+
}
|
|
215
|
+
if (!advanced)
|
|
216
|
+
throw new z.ZodError([
|
|
217
|
+
{
|
|
218
|
+
code: "custom",
|
|
219
|
+
path: ["entries"],
|
|
220
|
+
message: "Review contains a dependency cycle; review the linked entries first",
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
return { receipts };
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function restoreKnowledgeEntry(
|
|
229
|
+
db: Database,
|
|
230
|
+
context: KnowledgeContext,
|
|
231
|
+
input: KnowledgeEntryRestoreRequest,
|
|
232
|
+
) {
|
|
233
|
+
const request = KnowledgeEntryRestoreRequest.parse(input);
|
|
234
|
+
return apply(db, context, { ...request, operation: "restore" });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function archiveKnowledgeEntry(
|
|
238
|
+
db: Database,
|
|
239
|
+
context: KnowledgeContext,
|
|
240
|
+
input: {
|
|
241
|
+
operationId: string;
|
|
242
|
+
entryId: string;
|
|
243
|
+
expectedVersion: number;
|
|
244
|
+
},
|
|
245
|
+
) {
|
|
246
|
+
const request = z
|
|
247
|
+
.object({
|
|
248
|
+
operationId: z.uuid(),
|
|
249
|
+
entryId: z.uuid(),
|
|
250
|
+
expectedVersion: z.number().int().positive(),
|
|
251
|
+
})
|
|
252
|
+
.strict()
|
|
253
|
+
.parse(input);
|
|
254
|
+
return apply(db, context, { ...request, operation: "archive" });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const StoredProjection = z
|
|
258
|
+
.object({
|
|
259
|
+
revision: z
|
|
260
|
+
.object({
|
|
261
|
+
entry: z.unknown().nullable(),
|
|
262
|
+
title: z.string(),
|
|
263
|
+
preview: z.string(),
|
|
264
|
+
bodyCodecVersion: z.number().nullable(),
|
|
265
|
+
previewCodecVersion: z.number().nullable(),
|
|
266
|
+
})
|
|
267
|
+
.passthrough(),
|
|
268
|
+
})
|
|
269
|
+
.passthrough();
|
|
270
|
+
|
|
271
|
+
function decodeProjection(value: unknown, full: true): KnowledgeEntryRecord;
|
|
272
|
+
function decodeProjection(value: unknown, full: false): KnowledgeEntrySummary;
|
|
273
|
+
function decodeProjection(value: unknown, full: boolean) {
|
|
274
|
+
const stored = StoredProjection.parse(value);
|
|
275
|
+
const {
|
|
276
|
+
bodyCodecVersion,
|
|
277
|
+
previewCodecVersion,
|
|
278
|
+
entry,
|
|
279
|
+
title,
|
|
280
|
+
preview,
|
|
281
|
+
kind,
|
|
282
|
+
groupIds,
|
|
283
|
+
sourceKind,
|
|
284
|
+
...revision
|
|
285
|
+
} = stored.revision;
|
|
286
|
+
const { revision: _storedRevision, score, excerpts, ...record } = stored;
|
|
287
|
+
const decodedExcerpts = z
|
|
288
|
+
.array(
|
|
289
|
+
z.object({
|
|
290
|
+
field: z.enum(["title", "content"]),
|
|
291
|
+
start: z.number().int().nonnegative(),
|
|
292
|
+
end: z.number().int().nonnegative(),
|
|
293
|
+
text: z.string(),
|
|
294
|
+
codecVersion: z.number().nullable(),
|
|
295
|
+
}),
|
|
296
|
+
)
|
|
297
|
+
.parse(excerpts ?? [])
|
|
298
|
+
.map(({ codecVersion, text, ...excerpt }) => ({
|
|
299
|
+
...excerpt,
|
|
300
|
+
text: fromPostgresLosslessText(text, codecVersion),
|
|
301
|
+
}));
|
|
302
|
+
return full
|
|
303
|
+
? KnowledgeEntryRecord.parse({
|
|
304
|
+
...record,
|
|
305
|
+
revision: { ...revision, entry: fromPostgresLosslessJson(entry, bodyCodecVersion) },
|
|
306
|
+
})
|
|
307
|
+
: KnowledgeEntrySummary.parse({
|
|
308
|
+
...record,
|
|
309
|
+
score,
|
|
310
|
+
excerpts: decodedExcerpts,
|
|
311
|
+
revision: {
|
|
312
|
+
...revision,
|
|
313
|
+
kind,
|
|
314
|
+
groupIds,
|
|
315
|
+
sourceKind,
|
|
316
|
+
title: fromPostgresLosslessJson(title, bodyCodecVersion),
|
|
317
|
+
preview: fromPostgresLosslessText(preview, previewCodecVersion),
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function read(
|
|
323
|
+
db: Database,
|
|
324
|
+
context: KnowledgeContext,
|
|
325
|
+
request: Record<string, unknown>,
|
|
326
|
+
): Promise<unknown[]> {
|
|
327
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
328
|
+
const [row] = await rawRows<{ result: unknown }>(
|
|
329
|
+
tx,
|
|
330
|
+
sql`
|
|
331
|
+
SELECT knowledge_entry_read(${context.accountId}::uuid,${context.workspaceId}::uuid,
|
|
332
|
+
${JSON.stringify(context.actor)}::jsonb,${JSON.stringify(request)}::jsonb) AS result`,
|
|
333
|
+
);
|
|
334
|
+
return z.array(z.unknown()).parse(row?.result);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export async function getKnowledgeEntry(
|
|
339
|
+
db: Database,
|
|
340
|
+
context: KnowledgeContext,
|
|
341
|
+
entryId: string,
|
|
342
|
+
options: {
|
|
343
|
+
revisionId?: string | undefined;
|
|
344
|
+
view?: "published" | "needs_review" | "archived" | "rejected" | undefined;
|
|
345
|
+
} = {},
|
|
346
|
+
): Promise<KnowledgeEntryRecord | null> {
|
|
347
|
+
const request = z
|
|
348
|
+
.object({
|
|
349
|
+
entryId: z.uuid(),
|
|
350
|
+
revisionId: z.uuid().optional(),
|
|
351
|
+
view: z.enum(["published", "needs_review", "archived", "rejected"]).optional(),
|
|
352
|
+
})
|
|
353
|
+
.strict()
|
|
354
|
+
.parse({ entryId, ...options });
|
|
355
|
+
const rows = await read(db, context, { ...request, operation: "get", limit: 1 });
|
|
356
|
+
return rows[0] ? decodeProjection(rows[0], true) : null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const Cursor = z
|
|
360
|
+
.object({
|
|
361
|
+
v: z.literal(2),
|
|
362
|
+
afterId: z.uuid(),
|
|
363
|
+
afterScore: z.number().finite(),
|
|
364
|
+
queryHash: z.string().length(64),
|
|
365
|
+
})
|
|
366
|
+
.strict();
|
|
367
|
+
export async function listKnowledgeEntries(
|
|
368
|
+
db: Database,
|
|
369
|
+
context: KnowledgeContext,
|
|
370
|
+
input: KnowledgeEntryListRequest = {},
|
|
371
|
+
embedding?: { model: string; values: number[] } | undefined,
|
|
372
|
+
) {
|
|
373
|
+
const { cursor, ...request } = KnowledgeEntryListRequest.parse(input);
|
|
374
|
+
const queryHash = createHash("sha256")
|
|
375
|
+
.update(JSON.stringify({ context, request, embeddingModel: embedding?.model ?? null }))
|
|
376
|
+
.digest("hex");
|
|
377
|
+
let position: z.infer<typeof Cursor> | null = null;
|
|
378
|
+
try {
|
|
379
|
+
position = cursor
|
|
380
|
+
? Cursor.parse(JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")))
|
|
381
|
+
: null;
|
|
382
|
+
if (position && position.queryHash !== queryHash) throw new Error("Changed query");
|
|
383
|
+
} catch {
|
|
384
|
+
throw new z.ZodError([
|
|
385
|
+
{
|
|
386
|
+
code: "custom",
|
|
387
|
+
path: ["cursor"],
|
|
388
|
+
message: "This search changed. Refresh the results to continue.",
|
|
389
|
+
},
|
|
390
|
+
]);
|
|
391
|
+
}
|
|
392
|
+
const rows = await read(db, context, {
|
|
393
|
+
...request,
|
|
394
|
+
operation: "list",
|
|
395
|
+
...(position ? { afterId: position.afterId, afterScore: position.afterScore } : {}),
|
|
396
|
+
...(embedding
|
|
397
|
+
? {
|
|
398
|
+
embedding: z.array(z.number().finite()).min(1).max(4096).parse(embedding.values),
|
|
399
|
+
embeddingModel: embedding.model,
|
|
400
|
+
}
|
|
401
|
+
: {}),
|
|
402
|
+
});
|
|
403
|
+
const entries = rows.slice(0, request.limit).map((row) => decodeProjection(row, false));
|
|
404
|
+
return {
|
|
405
|
+
entries,
|
|
406
|
+
nextCursor:
|
|
407
|
+
rows.length > request.limit && entries.length
|
|
408
|
+
? Buffer.from(
|
|
409
|
+
JSON.stringify({
|
|
410
|
+
v: 2,
|
|
411
|
+
afterId: entries.at(-1)!.id,
|
|
412
|
+
afterScore: entries.at(-1)!.score ?? 0,
|
|
413
|
+
queryHash,
|
|
414
|
+
}),
|
|
415
|
+
).toString("base64url")
|
|
416
|
+
: null,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export async function listKnowledgeReviewBatches(
|
|
421
|
+
db: Database,
|
|
422
|
+
context: KnowledgeContext,
|
|
423
|
+
input: KnowledgeReviewBatchListRequest = {},
|
|
424
|
+
) {
|
|
425
|
+
const { cursor, ...request } = KnowledgeReviewBatchListRequest.parse(input);
|
|
426
|
+
const queryHash = createHash("sha256")
|
|
427
|
+
.update(JSON.stringify({ context, request, operation: "review_batches" }))
|
|
428
|
+
.digest("hex");
|
|
429
|
+
let position: z.infer<typeof Cursor> | null = null;
|
|
430
|
+
try {
|
|
431
|
+
position = cursor
|
|
432
|
+
? Cursor.parse(JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")))
|
|
433
|
+
: null;
|
|
434
|
+
if (position && position.queryHash !== queryHash) throw new Error("Changed filter");
|
|
435
|
+
} catch {
|
|
436
|
+
throw new z.ZodError([
|
|
437
|
+
{ code: "custom", path: ["cursor"], message: "Review groups changed. Refresh to continue." },
|
|
438
|
+
]);
|
|
439
|
+
}
|
|
440
|
+
const rows = z.array(KnowledgeReviewBatch).parse(
|
|
441
|
+
await read(db, context, {
|
|
442
|
+
...request,
|
|
443
|
+
operation: "review_batches",
|
|
444
|
+
...(position ? { afterId: position.afterId } : {}),
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
const batches = rows.slice(0, request.limit);
|
|
448
|
+
return {
|
|
449
|
+
batches,
|
|
450
|
+
nextCursor:
|
|
451
|
+
rows.length > request.limit && batches.length
|
|
452
|
+
? Buffer.from(
|
|
453
|
+
JSON.stringify({ v: 2, afterId: batches.at(-1)!.id, afterScore: 0, queryHash }),
|
|
454
|
+
).toString("base64url")
|
|
455
|
+
: null,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export async function listKnowledgeEntryHistory(
|
|
460
|
+
db: Database,
|
|
461
|
+
context: KnowledgeContext,
|
|
462
|
+
entryId: string,
|
|
463
|
+
beforeRevision?: number,
|
|
464
|
+
) {
|
|
465
|
+
const request = z
|
|
466
|
+
.object({ entryId: z.uuid(), beforeRevision: z.number().int().positive().optional() })
|
|
467
|
+
.parse({ entryId, beforeRevision });
|
|
468
|
+
const rows = await read(db, context, { ...request, operation: "history", limit: 20 });
|
|
469
|
+
const entries = rows.slice(0, 20).map((row) => decodeProjection(row, false));
|
|
470
|
+
return { entries, beforeRevision: rows.length > 20 ? entries.at(-1)!.revision.number : null };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const SettingsRecord = z.object({
|
|
474
|
+
ownerKey: z.string(),
|
|
475
|
+
contextKey: z.string(),
|
|
476
|
+
version: z.number().int().nonnegative(),
|
|
477
|
+
settings: AgentLearningOverridePatch,
|
|
478
|
+
});
|
|
479
|
+
function contextKey(context?: AgentLearningContext) {
|
|
480
|
+
return context ? `${context.kind}:${context.id}` : "defaults";
|
|
481
|
+
}
|
|
482
|
+
async function manageLearning(
|
|
483
|
+
db: Database,
|
|
484
|
+
context: KnowledgeContext,
|
|
485
|
+
request: Record<string, unknown>,
|
|
486
|
+
) {
|
|
487
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
488
|
+
const [row] = await rawRows<{ result: unknown }>(
|
|
489
|
+
tx,
|
|
490
|
+
sql`
|
|
491
|
+
SELECT agent_learning_manage(${context.accountId}::uuid,${context.workspaceId}::uuid,
|
|
492
|
+
${JSON.stringify(context.actor)}::jsonb,${JSON.stringify(request)}::jsonb) AS result`,
|
|
493
|
+
);
|
|
494
|
+
return row?.result;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
export async function getAgentLearningSettings(
|
|
498
|
+
db: Database,
|
|
499
|
+
context: KnowledgeContext,
|
|
500
|
+
scope: "workspace" | "personal" | "context",
|
|
501
|
+
source?: AgentLearningContext,
|
|
502
|
+
) {
|
|
503
|
+
return SettingsRecord.parse(
|
|
504
|
+
await manageLearning(db, context, { operation: "read", scope, contextKey: contextKey(source) }),
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
export async function saveAgentLearningSettings(
|
|
508
|
+
db: Database,
|
|
509
|
+
context: KnowledgeContext,
|
|
510
|
+
input: {
|
|
511
|
+
scope: "workspace" | "personal";
|
|
512
|
+
source?: AgentLearningContext | undefined;
|
|
513
|
+
operationId: string;
|
|
514
|
+
expectedVersion: number;
|
|
515
|
+
settings: AgentLearningOverridePatch;
|
|
516
|
+
},
|
|
517
|
+
) {
|
|
518
|
+
const request = z
|
|
519
|
+
.object({
|
|
520
|
+
scope: z.enum(["workspace", "personal"]),
|
|
521
|
+
operationId: z.uuid(),
|
|
522
|
+
expectedVersion: z.number().int().nonnegative(),
|
|
523
|
+
})
|
|
524
|
+
.parse(input);
|
|
525
|
+
const settings = input.source
|
|
526
|
+
? AgentLearningOverridePatch.parse(input.settings)
|
|
527
|
+
: AgentLearningDefaults.parse(input.settings);
|
|
528
|
+
return SettingsRecord.parse(
|
|
529
|
+
await manageLearning(db, context, {
|
|
530
|
+
...request,
|
|
531
|
+
operation: "save",
|
|
532
|
+
contextKey: contextKey(input.source),
|
|
533
|
+
settings,
|
|
534
|
+
}),
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
export async function listAgentLearningOverrides(
|
|
538
|
+
db: Database,
|
|
539
|
+
context: KnowledgeContext,
|
|
540
|
+
scope: "workspace" | "personal",
|
|
541
|
+
) {
|
|
542
|
+
return z
|
|
543
|
+
.array(
|
|
544
|
+
z.object({
|
|
545
|
+
contextKey: z.string(),
|
|
546
|
+
version: z.number().int().positive(),
|
|
547
|
+
settings: AgentLearningOverridePatch,
|
|
548
|
+
updatedAt: z.string(),
|
|
549
|
+
label: z.string(),
|
|
550
|
+
}),
|
|
551
|
+
)
|
|
552
|
+
.parse(await manageLearning(db, context, { operation: "list", scope }));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function instructionOperation(
|
|
556
|
+
db: Database,
|
|
557
|
+
context: KnowledgeContext,
|
|
558
|
+
request: Record<string, unknown>,
|
|
559
|
+
) {
|
|
560
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
561
|
+
const [row] = await rawRows<{ result: unknown }>(
|
|
562
|
+
tx,
|
|
563
|
+
sql`SELECT agent_instruction_apply(${context.accountId}::uuid,
|
|
564
|
+
${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,${JSON.stringify(request)}::jsonb) AS result`,
|
|
565
|
+
);
|
|
566
|
+
return row?.result;
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
export async function saveAgentInstruction(
|
|
570
|
+
db: Database,
|
|
571
|
+
context: KnowledgeContext,
|
|
572
|
+
input: AgentInstructionSaveRequest,
|
|
573
|
+
) {
|
|
574
|
+
return AgentInstructionReceipt.parse(
|
|
575
|
+
await instructionOperation(db, context, {
|
|
576
|
+
...AgentInstructionSaveRequest.parse(input),
|
|
577
|
+
operation: "save",
|
|
578
|
+
}),
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export async function getAgentInstruction(
|
|
583
|
+
db: Database,
|
|
584
|
+
context: KnowledgeContext,
|
|
585
|
+
target: WorkspaceInstructionPolicyTarget,
|
|
586
|
+
) {
|
|
587
|
+
return z
|
|
588
|
+
.object({
|
|
589
|
+
target: WorkspaceInstructionPolicyTarget,
|
|
590
|
+
expectedCurrentRevisionId: z.uuid().nullable(),
|
|
591
|
+
expectedActivationVersion: z.number().int().nonnegative(),
|
|
592
|
+
content: z.string().nullable(),
|
|
593
|
+
})
|
|
594
|
+
.parse(
|
|
595
|
+
await instructionOperation(db, context, {
|
|
596
|
+
operation: "get",
|
|
597
|
+
target: WorkspaceInstructionPolicyTarget.parse(target),
|
|
598
|
+
}),
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
export async function reviewAgentInstruction(
|
|
602
|
+
db: Database,
|
|
603
|
+
context: KnowledgeContext,
|
|
604
|
+
input: AgentInstructionReviewRequest,
|
|
605
|
+
) {
|
|
606
|
+
const { decision, ...request } = AgentInstructionReviewRequest.parse(input);
|
|
607
|
+
return AgentInstructionReceipt.parse(
|
|
608
|
+
await instructionOperation(db, context, { ...request, operation: decision }),
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
export async function listAgentInstructionReviews(
|
|
612
|
+
db: Database,
|
|
613
|
+
context: KnowledgeContext,
|
|
614
|
+
cursor?: string,
|
|
615
|
+
) {
|
|
616
|
+
const request = z.object({ cursor: z.uuid().optional() }).parse({ cursor });
|
|
617
|
+
const rows = z
|
|
618
|
+
.array(AgentInstructionReviewItem)
|
|
619
|
+
.parse(await instructionOperation(db, context, { ...request, operation: "list" }));
|
|
620
|
+
return { entries: rows.slice(0, 50), nextCursor: rows.length > 50 ? rows[49]!.revisionId : null };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Host-only parser boundary. The file metadata is never returned by agent tools. */
|
|
624
|
+
export async function inspectKnowledgeFilePreparation(
|
|
625
|
+
db: Database,
|
|
626
|
+
context: KnowledgeContext,
|
|
627
|
+
fileId: string,
|
|
628
|
+
) {
|
|
629
|
+
const value = await knowledgeFilePreparationOperation(db, context, {
|
|
630
|
+
operation: "inspect",
|
|
631
|
+
fileId: z.uuid().parse(fileId),
|
|
632
|
+
});
|
|
633
|
+
if (value && typeof value === "object" && "status" in value && value.status === "prepare") {
|
|
634
|
+
const raw = z.object({ file: z.record(z.string(), z.unknown()) }).parse(value).file;
|
|
635
|
+
return {
|
|
636
|
+
status: "prepare" as const,
|
|
637
|
+
file: knowledgeOriginalFileAsset(raw),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return KnowledgeFilePreparationResult.parse(value);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Concurrent/recovered preparations converge on the first exact saved source. */
|
|
644
|
+
export async function completeKnowledgeFilePreparation(
|
|
645
|
+
db: Database,
|
|
646
|
+
context: KnowledgeContext,
|
|
647
|
+
input: { fileId: string; title: string; content: string; sourceVersion?: string },
|
|
648
|
+
) {
|
|
649
|
+
return KnowledgeFilePreparationResult.parse(
|
|
650
|
+
await knowledgeFilePreparationOperation(db, context, {
|
|
651
|
+
operation: "complete",
|
|
652
|
+
fileId: z.uuid().parse(input.fileId),
|
|
653
|
+
...(input.sourceVersion
|
|
654
|
+
? {
|
|
655
|
+
sourceVersion: z
|
|
656
|
+
.string()
|
|
657
|
+
.regex(/^[a-f0-9]{64}$/)
|
|
658
|
+
.parse(input.sourceVersion),
|
|
659
|
+
}
|
|
660
|
+
: {}),
|
|
661
|
+
title: toPostgresLosslessJson(input.title),
|
|
662
|
+
content: toPostgresLosslessJson(input.content),
|
|
663
|
+
preview: toPostgresLosslessText(input.content.slice(0, 512)),
|
|
664
|
+
searchText: toPostgresLosslessText(`${input.title}\n${input.content}`),
|
|
665
|
+
codecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
|
|
666
|
+
}),
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
async function knowledgeFilePreparationOperation(
|
|
670
|
+
db: Database,
|
|
671
|
+
context: KnowledgeContext,
|
|
672
|
+
request: Record<string, unknown>,
|
|
673
|
+
) {
|
|
674
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
675
|
+
const [row] = await rawRows<{ result: unknown }>(
|
|
676
|
+
tx,
|
|
677
|
+
sql`SELECT knowledge_entry_prepare_file(
|
|
678
|
+
${context.accountId}::uuid,${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,
|
|
679
|
+
${JSON.stringify(request)}::jsonb) AS result`,
|
|
680
|
+
);
|
|
681
|
+
return row?.result;
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export function knowledgeOriginalFileAsset(raw: Record<string, unknown>): FileAsset {
|
|
686
|
+
return FileAsset.parse({
|
|
687
|
+
id: raw.id,
|
|
688
|
+
workspaceId: raw.workspace_id,
|
|
689
|
+
status: raw.status,
|
|
690
|
+
filename: raw.filename,
|
|
691
|
+
safeFilename: raw.safe_filename,
|
|
692
|
+
contentType: raw.content_type,
|
|
693
|
+
sizeBytes: Number(raw.size_bytes),
|
|
694
|
+
sha256: raw.sha256,
|
|
695
|
+
bucket: raw.bucket,
|
|
696
|
+
objectKey: raw.object_key,
|
|
697
|
+
scope: raw.private_owner_subject_id ? "personal" : "workspace",
|
|
698
|
+
createdAt: new Date(String(raw.created_at)).toISOString(),
|
|
699
|
+
updatedAt: new Date(String(raw.updated_at)).toISOString(),
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Exact entry visibility first; origin workspace metadata is not an access grant. */
|
|
704
|
+
export async function getKnowledgeOriginalFile(
|
|
705
|
+
db: Database,
|
|
706
|
+
context: KnowledgeContext,
|
|
707
|
+
entryId: string,
|
|
708
|
+
revisionId?: string,
|
|
709
|
+
) {
|
|
710
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
711
|
+
const request = {
|
|
712
|
+
operation: "file",
|
|
713
|
+
entryId: z.uuid().parse(entryId),
|
|
714
|
+
...(revisionId ? { revisionId: z.uuid().parse(revisionId) } : {}),
|
|
715
|
+
};
|
|
716
|
+
const [row] = await rawRows<{ result: unknown }>(
|
|
717
|
+
tx,
|
|
718
|
+
sql`SELECT knowledge_entry_read(${context.accountId}::uuid,
|
|
719
|
+
${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,${JSON.stringify(request)}::jsonb) AS result`,
|
|
720
|
+
);
|
|
721
|
+
return row?.result
|
|
722
|
+
? knowledgeOriginalFileAsset(z.record(z.string(), z.unknown()).parse(row.result))
|
|
723
|
+
: null;
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** Finish only a pre-cutover, human-answered remember confirmation. */
|
|
728
|
+
export async function confirmLegacyKnowledge(
|
|
729
|
+
db: Database,
|
|
730
|
+
context: KnowledgeContext,
|
|
731
|
+
request: {
|
|
732
|
+
operationId: string;
|
|
733
|
+
claimId: string;
|
|
734
|
+
humanInputRequestId: string;
|
|
735
|
+
},
|
|
736
|
+
) {
|
|
737
|
+
const input = z
|
|
738
|
+
.object({ operationId: z.uuid(), claimId: z.uuid(), humanInputRequestId: z.uuid() })
|
|
739
|
+
.strict()
|
|
740
|
+
.parse(request);
|
|
741
|
+
if (context.actor.kind !== "agent")
|
|
742
|
+
throw new Error("Legacy confirmation requires an exact agent attempt");
|
|
743
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
744
|
+
const [row] = await rawRows<{ receipt: unknown }>(
|
|
745
|
+
tx,
|
|
746
|
+
sql`SELECT knowledge_entry_confirm_legacy(
|
|
747
|
+
${context.accountId}::uuid,${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,
|
|
748
|
+
${JSON.stringify(input)}::jsonb) AS receipt`,
|
|
749
|
+
);
|
|
750
|
+
return KnowledgeEntryWriteReceipt.parse(row?.receipt);
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** Compatibility only: finish an imported native instruction without rebaselining. */
|
|
755
|
+
export async function confirmLegacyInstruction(
|
|
756
|
+
db: Database,
|
|
757
|
+
context: KnowledgeContext,
|
|
758
|
+
request: {
|
|
759
|
+
operationId: string;
|
|
760
|
+
proposalId: string;
|
|
761
|
+
decisionReceiptId: string;
|
|
762
|
+
humanInputRequestId: string;
|
|
763
|
+
},
|
|
764
|
+
) {
|
|
765
|
+
const input = z
|
|
766
|
+
.object({
|
|
767
|
+
operationId: z.uuid(),
|
|
768
|
+
proposalId: z.uuid(),
|
|
769
|
+
decisionReceiptId: z.uuid(),
|
|
770
|
+
humanInputRequestId: z.uuid(),
|
|
771
|
+
})
|
|
772
|
+
.strict()
|
|
773
|
+
.parse(request);
|
|
774
|
+
if (context.actor.kind !== "agent")
|
|
775
|
+
throw new Error("Legacy confirmation requires an exact agent attempt");
|
|
776
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
777
|
+
const [row] = await rawRows<{ receipt: unknown }>(
|
|
778
|
+
tx,
|
|
779
|
+
sql`SELECT knowledge_entry_confirm_legacy(
|
|
780
|
+
${context.accountId}::uuid,${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,
|
|
781
|
+
${JSON.stringify(input)}::jsonb) AS receipt`,
|
|
782
|
+
);
|
|
783
|
+
return AgentInstructionReceipt.parse(row?.receipt);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** Protocol recovery runs before the model, including when the retired tool is no longer selected. */
|
|
788
|
+
export async function recoverLegacyKnowledgeConfirmations(db: Database, context: KnowledgeContext) {
|
|
789
|
+
if (context.actor.kind !== "agent")
|
|
790
|
+
throw new Error("Confirmation recovery requires an exact attempt");
|
|
791
|
+
return inKnowledgeContext(db, context, async (tx) => {
|
|
792
|
+
const [row] = await rawRows<{ result: unknown }>(
|
|
793
|
+
tx,
|
|
794
|
+
sql`SELECT knowledge_entry_confirm_legacy(
|
|
795
|
+
${context.accountId}::uuid,${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,
|
|
796
|
+
'{"operation":"recover"}'::jsonb) AS result`,
|
|
797
|
+
);
|
|
798
|
+
return z
|
|
799
|
+
.object({
|
|
800
|
+
receipts: z.array(KnowledgeEntryWriteReceipt),
|
|
801
|
+
instructionReceipts: z.array(AgentInstructionReceipt),
|
|
802
|
+
unavailable: z.number().int().nonnegative(),
|
|
803
|
+
})
|
|
804
|
+
.parse(row?.result);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** Explicit content from this agent's bound source, including its own pending batch.
|
|
809
|
+
* Ambient retrieval continues to return published knowledge only. */
|
|
810
|
+
export async function readScheduledKnowledgeSource(
|
|
811
|
+
db: Database,
|
|
812
|
+
context: KnowledgeContext,
|
|
813
|
+
input: {
|
|
814
|
+
entryId?: string;
|
|
815
|
+
afterId?: string;
|
|
816
|
+
offset?: number;
|
|
817
|
+
},
|
|
818
|
+
) {
|
|
819
|
+
const request = z
|
|
820
|
+
.object({
|
|
821
|
+
entryId: z.uuid().optional(),
|
|
822
|
+
afterId: z.uuid().optional(),
|
|
823
|
+
offset: z.number().int().min(0).max(100_000_000).optional(),
|
|
824
|
+
})
|
|
825
|
+
.strict()
|
|
826
|
+
.parse(input);
|
|
827
|
+
const rows = await read(db, context, {
|
|
828
|
+
operation: request.entryId ? "source_read" : "source_list",
|
|
829
|
+
limit: 20,
|
|
830
|
+
...(request.entryId ? { entryId: request.entryId } : {}),
|
|
831
|
+
...(request.afterId ? { afterId: request.afterId } : {}),
|
|
832
|
+
});
|
|
833
|
+
const records = rows.map((row) =>
|
|
834
|
+
z
|
|
835
|
+
.object({
|
|
836
|
+
entryId: z.uuid(),
|
|
837
|
+
revisionId: z.uuid(),
|
|
838
|
+
title: z.string(),
|
|
839
|
+
content: z.string().optional(),
|
|
840
|
+
codecVersion: z.number(),
|
|
841
|
+
pending: z.boolean(),
|
|
842
|
+
})
|
|
843
|
+
.parse(row),
|
|
844
|
+
);
|
|
845
|
+
const items = records.slice(0, 20).map((row) => {
|
|
846
|
+
const title = z.string().parse(fromPostgresLosslessJson(row.title, row.codecVersion));
|
|
847
|
+
if (row.content === undefined) return { ...row, title };
|
|
848
|
+
const content = z.string().parse(fromPostgresLosslessJson(row.content, row.codecVersion));
|
|
849
|
+
const offset = request.offset ?? 0;
|
|
850
|
+
return {
|
|
851
|
+
...row,
|
|
852
|
+
title,
|
|
853
|
+
content: content.slice(offset, offset + 16_000),
|
|
854
|
+
offset,
|
|
855
|
+
totalCharacters: content.length,
|
|
856
|
+
nextOffset: offset + 16_000 < content.length ? offset + 16_000 : null,
|
|
857
|
+
};
|
|
858
|
+
});
|
|
859
|
+
return { items, nextCursor: records.length > 20 ? (items.at(-1)?.entryId ?? null) : null };
|
|
860
|
+
}
|