@opengeni/core 3.0.1 → 3.1.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/README.md +4 -4
- package/dist/access/index.d.ts +131 -7
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +1 -1
- package/dist/application/connect-operation.d.ts +6 -0
- package/dist/application/external-workspace-members.d.ts +13 -1
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +29 -2
- package/dist/application/organization-privacy.d.ts +57 -0
- package/dist/application/prepared-mcp-connection.d.ts +31 -0
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +3 -0
- package/dist/application/session-tenancy.d.ts +13 -4
- package/dist/billing/agent-run-admission.d.ts +21 -0
- package/dist/billing/limits.d.ts +27 -1
- package/dist/billing/model-call-admission.d.ts +34 -0
- package/dist/billing/model-usage-settlement.d.ts +112 -0
- package/dist/canonical-human-identities.js +1 -1
- package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
- package/dist/chunk-7T2SZ7BI.js.map +1 -0
- package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
- package/dist/chunk-OMJFIIWK.js.map +1 -0
- package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
- package/dist/chunk-QI2M2B2K.js.map +1 -0
- package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
- package/dist/chunk-TTWTXQB2.js.map +1 -0
- package/dist/codex-model-availability.d.ts +55 -0
- package/dist/default-session-model.d.ts +192 -0
- package/dist/dependencies.d.ts +41 -7
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +8 -1
- package/dist/domain/capabilities.d.ts +112 -8
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +62 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +3 -1
- package/dist/domain/editable-artifacts/types.d.ts +1 -1
- package/dist/domain/encryption-key.d.ts +12 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +21 -3
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -76
- package/dist/domain/mcp-account-bindings.d.ts +17 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +73 -12
- package/dist/domain/pr-review.d.ts +121 -6
- package/dist/domain/realtime-voice-billing.d.ts +103 -0
- package/dist/domain/resources.d.ts +17 -1
- package/dist/domain/scheduled-task-access.d.ts +265 -0
- package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
- package/dist/domain/scheduled-task-audience.d.ts +70 -0
- package/dist/domain/scheduled-tasks.d.ts +61 -18
- package/dist/domain/session-connection-accounts.d.ts +53 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +102 -4
- package/dist/domain/sessions.d.ts +179 -15
- package/dist/domain/site-audience.d.ts +52 -0
- package/dist/domain/skills.d.ts +29 -1
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
- package/dist/domain/vaults/index.d.ts +7 -0
- package/dist/domain/vaults/mapping.d.ts +25 -0
- package/dist/domain/vaults/provider-registry.d.ts +72 -0
- package/dist/domain/vaults/provider-sync.d.ts +176 -0
- package/dist/domain/vaults/provider-writes.d.ts +57 -0
- package/dist/domain/vaults/secret-resolver.d.ts +34 -0
- package/dist/domain/vaults/types.d.ts +24 -0
- package/dist/domain/vaults/vault-service.d.ts +35 -0
- package/dist/domain/voice-input-billing.d.ts +77 -0
- package/dist/domain/web-search-billing.d.ts +51 -0
- package/dist/domain/workspace-credits.d.ts +7 -0
- package/dist/editable-artifact-live/application.d.ts +2 -1
- package/dist/editable-artifact-live/server.d.ts +5 -1
- package/dist/editable-artifact-live/ticket.d.ts +2 -1
- package/dist/editable-artifact-live/types.d.ts +9 -0
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -2
- package/dist/goal-admission.d.ts +627 -0
- package/dist/host-session-usage-limit.d.ts +15 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.js +22682 -11743
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +110 -7
- package/dist/model-call.d.ts +58 -0
- package/dist/model-catalog.d.ts +29 -3
- package/dist/model-provider-credentials.d.ts +43 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +1 -13
- package/dist/sandbox/fleet.d.ts +58 -4
- package/dist/sandbox/routing.d.ts +42 -2
- package/dist/sandbox/runtime-settings.d.ts +27 -17
- package/dist/session-authorization.d.ts +10 -0
- package/dist/session-usage-limit-metrics.d.ts +60 -0
- package/dist/session-usage-limits.d.ts +89 -0
- package/dist/site-agent-clamp.d.ts +71 -0
- package/dist/site-session-origin.d.ts +39 -2
- package/dist/transcription.d.ts +120 -3
- package/dist/turn-surface.d.ts +20 -0
- package/dist/user-presence.d.ts +32 -0
- package/dist/workflow-wake-contract.d.ts +13 -1
- package/package.json +15 -22
- package/src/access/index.ts +961 -38
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +9 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +3 -2
- package/src/application/connect-authority.ts +3 -3
- package/src/application/connect-operation.ts +37 -3
- package/src/application/external-continuation.ts +20 -3
- package/src/application/external-link-work-admission.ts +3 -0
- package/src/application/external-workspace-members.ts +51 -3
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +172 -55
- package/src/application/organization-privacy.ts +161 -0
- package/src/application/prepared-mcp-connection.ts +133 -0
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +73 -1
- package/src/application/session-mcp-credential-rotation.ts +61 -18
- package/src/application/session-tenancy.ts +262 -44
- package/src/application/user-resource-grants.ts +1 -29
- package/src/billing/agent-run-admission.ts +117 -0
- package/src/billing/limits.ts +138 -14
- package/src/billing/model-call-admission.ts +118 -0
- package/src/billing/model-usage-settlement.ts +472 -0
- package/src/codex-model-availability.ts +264 -0
- package/src/default-session-model.ts +692 -0
- package/src/dependencies.ts +48 -6
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +76 -2
- package/src/domain/capabilities.ts +830 -181
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +572 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +44 -6
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +20 -0
- package/src/domain/editable-artifacts/types.ts +1 -1
- package/src/domain/encryption-key.ts +26 -0
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/github-action-policies.ts +1 -1
- package/src/domain/github-skill-source.ts +3 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +167 -43
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +617 -5
- package/src/domain/knowledge-search.ts +240 -23
- package/src/domain/mcp-account-bindings.ts +169 -0
- package/src/domain/mcp-account-routes.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/personal-connection-delegations.ts +399 -257
- package/src/domain/pr-review.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +425 -0
- package/src/domain/resources.ts +50 -4
- package/src/domain/scheduled-task-access.ts +1089 -0
- package/src/domain/scheduled-task-account-audit.ts +90 -0
- package/src/domain/scheduled-task-audience.ts +249 -0
- package/src/domain/scheduled-tasks.ts +1313 -374
- package/src/domain/session-connection-accounts.ts +239 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +60 -0
- package/src/domain/session-tool-policy.ts +489 -14
- package/src/domain/sessions.ts +2254 -717
- package/src/domain/site-audience.ts +130 -0
- package/src/domain/skills.ts +9 -0
- package/src/domain/slack-bot.ts +141 -4
- package/src/domain/slack-publication-secret-safety.ts +1 -1
- package/src/domain/vaults/index.ts +19 -0
- package/src/domain/vaults/mapping.ts +190 -0
- package/src/domain/vaults/provider-registry.ts +201 -0
- package/src/domain/vaults/provider-sync.ts +531 -0
- package/src/domain/vaults/provider-writes.ts +288 -0
- package/src/domain/vaults/secret-resolver.ts +464 -0
- package/src/domain/vaults/types.ts +26 -0
- package/src/domain/vaults/vault-service.ts +1316 -0
- package/src/domain/voice-input-billing.ts +330 -0
- package/src/domain/web-search-billing.ts +169 -0
- package/src/domain/workspace-credits.ts +13 -0
- package/src/editable-artifact-live/application.ts +8 -1
- package/src/editable-artifact-live/server.ts +154 -6
- package/src/editable-artifact-live/ticket.ts +86 -1
- package/src/editable-artifact-live/types.ts +10 -0
- package/src/goal-admission.ts +220 -0
- package/src/host-session-usage-limit.ts +25 -0
- package/src/index.ts +75 -11
- package/src/managed-session.ts +137 -6
- package/src/model-call.ts +79 -0
- package/src/model-catalog.ts +345 -91
- package/src/model-provider-credentials.ts +394 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +23 -111
- package/src/sandbox/fleet.ts +253 -28
- package/src/sandbox/routing.ts +204 -43
- package/src/sandbox/runtime-settings.ts +159 -85
- package/src/session-authorization.ts +172 -23
- package/src/session-usage-limit-metrics.ts +95 -0
- package/src/session-usage-limits.ts +415 -0
- package/src/site-agent-clamp.ts +226 -0
- package/src/site-session-origin.ts +84 -5
- package/src/transcription.ts +123 -4
- package/src/turn-surface.ts +47 -0
- package/src/user-presence.ts +134 -0
- package/src/workflow-wake-contract.ts +14 -1
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/chunk-OF65T3PM.js.map +0 -1
- package/dist/chunk-VCZFFEPB.js.map +0 -1
- package/dist/chunk-ZYCNLYGH.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/environments.d.ts +0 -33
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/environments.ts +0 -120
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
package/src/domain/insights.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
countSessionsAttachedToGroups,
|
|
12
12
|
enumerateUtcDays,
|
|
13
13
|
enumerateUtcHours,
|
|
14
|
+
getSessionAccessProjection,
|
|
14
15
|
listFloorSessions,
|
|
15
16
|
listLiveWarmLeases,
|
|
16
17
|
listScheduledTasks,
|
|
@@ -20,24 +21,42 @@ import {
|
|
|
20
21
|
type Database,
|
|
21
22
|
} from "@opengeni/db";
|
|
22
23
|
|
|
24
|
+
import { measureInsightsPhase, type InsightsPhaseObserver } from "./insights-timing";
|
|
25
|
+
export { measureInsightsPhase, workspaceInsightsPhaseMetricObserver } from "./insights-timing";
|
|
26
|
+
export type { InsightsPhaseObservation, InsightsPhaseObserver } from "./insights-timing";
|
|
23
27
|
const MACHINE_HEARTBEAT_FRESH_MS = 120_000;
|
|
24
28
|
export const WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES = 256;
|
|
25
29
|
export const WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES = 512;
|
|
26
30
|
|
|
27
|
-
export type WorkspaceInsightsFilterField = "provider" | "model";
|
|
31
|
+
export type WorkspaceInsightsFilterField = "provider" | "model" | "rootSessionId" | "sessionId";
|
|
28
32
|
|
|
29
33
|
export class WorkspaceInsightsFilterValidationError extends Error {
|
|
30
34
|
readonly field: WorkspaceInsightsFilterField;
|
|
31
35
|
readonly maxUtf8Bytes: number;
|
|
32
36
|
|
|
33
|
-
constructor(field: WorkspaceInsightsFilterField, maxUtf8Bytes: number) {
|
|
34
|
-
super(`${field} must be at most ${maxUtf8Bytes} UTF-8 bytes`);
|
|
37
|
+
constructor(field: WorkspaceInsightsFilterField, maxUtf8Bytes: number, message?: string) {
|
|
38
|
+
super(message ?? `${field} must be at most ${maxUtf8Bytes} UTF-8 bytes`);
|
|
35
39
|
this.name = "WorkspaceInsightsFilterValidationError";
|
|
36
40
|
this.field = field;
|
|
37
41
|
this.maxUtf8Bytes = maxUtf8Bytes;
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
46
|
+
|
|
47
|
+
/** Normalize a session drilldown scope; only exact UUIDs are accepted. */
|
|
48
|
+
export function normalizeWorkspaceInsightsSessionScope(
|
|
49
|
+
value: string | null | undefined,
|
|
50
|
+
field: "rootSessionId" | "sessionId",
|
|
51
|
+
): string | null {
|
|
52
|
+
const normalized = value?.trim() || null;
|
|
53
|
+
if (normalized === null || normalized === "all") return null;
|
|
54
|
+
if (!UUID_PATTERN.test(normalized)) {
|
|
55
|
+
throw new WorkspaceInsightsFilterValidationError(field, 36, `${field} must be a UUID`);
|
|
56
|
+
}
|
|
57
|
+
return normalized.toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
/** Normalize the public filter sentinel and enforce the database authority envelope. */
|
|
42
61
|
export function normalizeWorkspaceInsightsFilter(
|
|
43
62
|
value: string | null | undefined,
|
|
@@ -60,6 +79,14 @@ export type GetWorkspaceInsightsInput = {
|
|
|
60
79
|
range: InsightsRange;
|
|
61
80
|
provider?: string | null;
|
|
62
81
|
model?: string | null;
|
|
82
|
+
rootSessionId?: string | null;
|
|
83
|
+
sessionId?: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* Resolves the reading person, only once the rollup runs. The per-schedule
|
|
86
|
+
* list leaves out other people's Only me schedules; their usage stays in the
|
|
87
|
+
* per-person private totals.
|
|
88
|
+
*/
|
|
89
|
+
viewerSubjectId?: () => Promise<string | null>;
|
|
63
90
|
now?: Date;
|
|
64
91
|
};
|
|
65
92
|
|
|
@@ -206,46 +233,100 @@ export function insightsSessionLabel(input: {
|
|
|
206
233
|
return `${(input.depth ?? 0) > 0 ? "Agent" : "Session"} ${input.id.slice(0, 8)}`;
|
|
207
234
|
}
|
|
208
235
|
|
|
236
|
+
export function insightsProjectLabel(input: {
|
|
237
|
+
kind: "project" | "other" | "unfiled" | "unavailable" | "deleted";
|
|
238
|
+
name: string | null;
|
|
239
|
+
projects: number;
|
|
240
|
+
}): string {
|
|
241
|
+
switch (input.kind) {
|
|
242
|
+
case "project":
|
|
243
|
+
return input.name?.trim() || "Untitled project";
|
|
244
|
+
case "other":
|
|
245
|
+
return `${input.projects.toLocaleString("en-US")} other ${input.projects === 1 ? "project" : "projects"}`;
|
|
246
|
+
case "unfiled":
|
|
247
|
+
return "No project";
|
|
248
|
+
case "unavailable":
|
|
249
|
+
return "Root session not visible";
|
|
250
|
+
case "deleted":
|
|
251
|
+
return "Deleted chats";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
209
255
|
export async function getWorkspaceInsights(
|
|
210
256
|
db: Database,
|
|
211
257
|
settings: Settings,
|
|
212
258
|
input: GetWorkspaceInsightsInput,
|
|
259
|
+
observePhase?: InsightsPhaseObserver,
|
|
213
260
|
): Promise<WorkspaceInsightsResponse> {
|
|
214
261
|
const provider = normalizeWorkspaceInsightsFilter(input.provider, "provider");
|
|
215
262
|
const model = normalizeWorkspaceInsightsFilter(input.model, "model");
|
|
216
|
-
|
|
263
|
+
const rootSessionId = normalizeWorkspaceInsightsSessionScope(
|
|
264
|
+
input.rootSessionId,
|
|
265
|
+
"rootSessionId",
|
|
266
|
+
);
|
|
267
|
+
const sessionId = normalizeWorkspaceInsightsSessionScope(input.sessionId, "sessionId");
|
|
268
|
+
await measureInsightsPhase(observePhase, "require_workspace", () =>
|
|
269
|
+
requireWorkspace(db, input.workspaceId),
|
|
270
|
+
);
|
|
271
|
+
const [visibleRootScope, visibleSessionScope] = await Promise.all([
|
|
272
|
+
rootSessionId ? getSessionAccessProjection(db, input.workspaceId, rootSessionId) : null,
|
|
273
|
+
sessionId ? getSessionAccessProjection(db, input.workspaceId, sessionId) : null,
|
|
274
|
+
]);
|
|
217
275
|
const now = input.now ?? new Date();
|
|
218
276
|
const window = resolveRangeWindow(input.range, now);
|
|
219
277
|
const modelFilterActive = Boolean(provider || model);
|
|
220
|
-
const
|
|
278
|
+
const scopeActive = Boolean(rootSessionId || sessionId);
|
|
279
|
+
const filter = { provider, model, rootSessionId, sessionId };
|
|
221
280
|
|
|
222
281
|
const [modelBundle, usageBundle, liveWarm, tasks, depth, floorRows, machinesOnline] =
|
|
223
282
|
await Promise.all([
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
283
|
+
measureInsightsPhase(observePhase, "model_bundle", () =>
|
|
284
|
+
readWorkspaceInsightsModelBundle(db, {
|
|
285
|
+
workspaceId: input.workspaceId,
|
|
286
|
+
since: window.since,
|
|
287
|
+
until: window.until,
|
|
288
|
+
priorSince: window.priorSince,
|
|
289
|
+
priorUntil: window.priorUntil,
|
|
290
|
+
granularity: input.range === "today" ? "hour" : "day",
|
|
291
|
+
...filter,
|
|
292
|
+
}),
|
|
293
|
+
),
|
|
294
|
+
measureInsightsPhase(observePhase, "usage_bundle", () =>
|
|
295
|
+
readWorkspaceInsightsUsageBundle(db, {
|
|
296
|
+
workspaceId: input.workspaceId,
|
|
297
|
+
since: window.since,
|
|
298
|
+
until: window.until,
|
|
299
|
+
priorSince: window.priorSince,
|
|
300
|
+
priorUntil: window.priorUntil,
|
|
301
|
+
monthSince: startOfUtcMonth(now),
|
|
302
|
+
granularity: input.range === "today" ? "hour" : "day",
|
|
303
|
+
warmGroupLimit: 24,
|
|
304
|
+
}),
|
|
305
|
+
),
|
|
306
|
+
measureInsightsPhase(observePhase, "live_warm", () =>
|
|
307
|
+
listLiveWarmLeases(db, input.workspaceId),
|
|
308
|
+
),
|
|
309
|
+
measureInsightsPhase(observePhase, "scheduled_tasks", () =>
|
|
310
|
+
(async () =>
|
|
311
|
+
await listScheduledTasks(
|
|
312
|
+
db,
|
|
313
|
+
input.workspaceId,
|
|
314
|
+
100,
|
|
315
|
+
0,
|
|
316
|
+
undefined,
|
|
317
|
+
input.viewerSubjectId ? await input.viewerSubjectId() : null,
|
|
318
|
+
))(),
|
|
319
|
+
),
|
|
320
|
+
measureInsightsPhase(observePhase, "session_depth", () =>
|
|
321
|
+
aggregateSessionDepth(db, input.workspaceId),
|
|
322
|
+
),
|
|
323
|
+
measureInsightsPhase(observePhase, "floor_sessions", () =>
|
|
324
|
+
listFloorSessions(db, input.workspaceId, 24),
|
|
325
|
+
),
|
|
247
326
|
settings.sandboxSelfhostedEnabled
|
|
248
|
-
?
|
|
327
|
+
? measureInsightsPhase(observePhase, "online_machines", () =>
|
|
328
|
+
countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now),
|
|
329
|
+
)
|
|
249
330
|
: Promise.resolve(0),
|
|
250
331
|
]);
|
|
251
332
|
|
|
@@ -255,10 +336,18 @@ export async function getWorkspaceInsights(
|
|
|
255
336
|
factBuckets: factDays,
|
|
256
337
|
rootDrivers,
|
|
257
338
|
priorRootDrivers,
|
|
339
|
+
projects,
|
|
340
|
+
privateChats,
|
|
341
|
+
privateChatsTruncated,
|
|
258
342
|
scheduleFacts,
|
|
259
343
|
facets,
|
|
260
344
|
recentCalls,
|
|
261
345
|
promptContributions,
|
|
346
|
+
dataThrough,
|
|
347
|
+
driverGroups,
|
|
348
|
+
driversTruncated,
|
|
349
|
+
facetsTruncated,
|
|
350
|
+
recentCallsTruncated,
|
|
262
351
|
} = modelBundle;
|
|
263
352
|
const {
|
|
264
353
|
workspaceCreditMicros,
|
|
@@ -271,17 +360,21 @@ export async function getWorkspaceInsights(
|
|
|
271
360
|
agentRunsUsed,
|
|
272
361
|
} = usageBundle;
|
|
273
362
|
const [attached, fireCounts] = await Promise.all([
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
363
|
+
measureInsightsPhase(observePhase, "attached_sessions", () =>
|
|
364
|
+
countSessionsAttachedToGroups(
|
|
365
|
+
db,
|
|
366
|
+
input.workspaceId,
|
|
367
|
+
warmGroups.map((group) => group.groupId),
|
|
368
|
+
),
|
|
369
|
+
),
|
|
370
|
+
measureInsightsPhase(observePhase, "scheduled_fires", () =>
|
|
371
|
+
countScheduledTaskFires(db, {
|
|
372
|
+
workspaceId: input.workspaceId,
|
|
373
|
+
since: window.since,
|
|
374
|
+
until: window.until,
|
|
375
|
+
taskIds: tasks.map((task) => task.id),
|
|
376
|
+
}),
|
|
278
377
|
),
|
|
279
|
-
countScheduledTaskFires(db, {
|
|
280
|
-
workspaceId: input.workspaceId,
|
|
281
|
-
since: window.since,
|
|
282
|
-
until: window.until,
|
|
283
|
-
taskIds: tasks.map((task) => task.id),
|
|
284
|
-
}),
|
|
285
378
|
]);
|
|
286
379
|
const backendByGroup = new Map(liveWarm.map((lease) => [lease.groupId, lease.backend]));
|
|
287
380
|
const warmSecondsByGroup = new Map(warmGroups.map((group) => [group.groupId, group.warmSeconds]));
|
|
@@ -345,6 +438,8 @@ export async function getWorkspaceInsights(
|
|
|
345
438
|
0,
|
|
346
439
|
);
|
|
347
440
|
const modelCalls = modelRows.reduce((sum, row) => sum + row.calls, 0);
|
|
441
|
+
const cachedTokens = modelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
442
|
+
const cacheInputTokens = modelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
|
|
348
443
|
const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
349
444
|
const priorTotalTokens = priorModelRows.reduce((sum, row) => sum + row.totalTokens, 0);
|
|
350
445
|
const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
@@ -373,9 +468,10 @@ export async function getWorkspaceInsights(
|
|
|
373
468
|
cacheKnownCalls: 0,
|
|
374
469
|
calls: 0,
|
|
375
470
|
};
|
|
376
|
-
const modelCostMicros =
|
|
377
|
-
|
|
378
|
-
|
|
471
|
+
const modelCostMicros =
|
|
472
|
+
modelFilterActive || scopeActive
|
|
473
|
+
? facts.costMicros
|
|
474
|
+
: (usageBuckets.get(bucket)?.costMicros ?? facts.costMicros);
|
|
379
475
|
return {
|
|
380
476
|
label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
|
|
381
477
|
modelCostUsd: microsToUsd(modelCostMicros),
|
|
@@ -428,6 +524,20 @@ export async function getWorkspaceInsights(
|
|
|
428
524
|
};
|
|
429
525
|
});
|
|
430
526
|
|
|
527
|
+
const projectRows = projects.map((row) => ({
|
|
528
|
+
id: row.kind === "project" && row.channelId ? `project:${row.channelId}` : row.kind,
|
|
529
|
+
kind: row.kind,
|
|
530
|
+
label: insightsProjectLabel(row),
|
|
531
|
+
projects: row.projects,
|
|
532
|
+
rootSessions: row.rootSessions,
|
|
533
|
+
calls: row.calls,
|
|
534
|
+
creditUsd: microsToUsd(row.pricedCostMicros),
|
|
535
|
+
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
536
|
+
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
537
|
+
tokens: row.totalTokens,
|
|
538
|
+
cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens),
|
|
539
|
+
}));
|
|
540
|
+
|
|
431
541
|
const scheduleFactById = new Map(scheduleFacts.map((row) => [row.scheduledTaskId, row] as const));
|
|
432
542
|
const schedules = tasks.map((task) => {
|
|
433
543
|
const fact = scheduleFactById.get(task.id);
|
|
@@ -488,6 +598,9 @@ export async function getWorkspaceInsights(
|
|
|
488
598
|
sessions: bucket.sessions,
|
|
489
599
|
})),
|
|
490
600
|
drivers,
|
|
601
|
+
projects: projectRows,
|
|
602
|
+
privateChats,
|
|
603
|
+
privateChatsTruncated,
|
|
491
604
|
schedules,
|
|
492
605
|
recentCalls: recentCalls.map((row) => ({
|
|
493
606
|
id: row.id,
|
|
@@ -558,6 +671,7 @@ export async function getWorkspaceInsights(
|
|
|
558
671
|
priorInputTokens,
|
|
559
672
|
priorTotalTokens,
|
|
560
673
|
priorCacheHitPct: cacheHitPct(priorCachedTokens, priorCacheInputTokens),
|
|
674
|
+
priorCacheInputTokens,
|
|
561
675
|
priorCalls,
|
|
562
676
|
goalsActive: depth.goalsActive,
|
|
563
677
|
goalsCompleted: depth.goalsCompleted,
|
|
@@ -577,7 +691,17 @@ export async function getWorkspaceInsights(
|
|
|
577
691
|
billableTokenCap: limits.maxMonthlyTokensPerWorkspace ?? null,
|
|
578
692
|
agentRunsUsed,
|
|
579
693
|
agentRunCap: limits.maxMonthlyAgentRunsPerWorkspace ?? null,
|
|
580
|
-
modelFilterActive,
|
|
694
|
+
modelFilterActive: modelFilterActive || scopeActive,
|
|
695
|
+
dataThrough: dataThrough?.toISOString() ?? null,
|
|
696
|
+
cacheHitPct: cacheHitPct(cachedTokens, cacheInputTokens),
|
|
697
|
+
scope: {
|
|
698
|
+
rootSessionId: visibleRootScope ? rootSessionId : null,
|
|
699
|
+
sessionId: visibleSessionScope ? sessionId : null,
|
|
700
|
+
},
|
|
701
|
+
driverGroups,
|
|
702
|
+
driversTruncated,
|
|
703
|
+
facetsTruncated,
|
|
704
|
+
recentCallsTruncated,
|
|
581
705
|
});
|
|
582
706
|
|
|
583
707
|
return { snapshot };
|
|
@@ -12,6 +12,7 @@ export async function prepareKnowledgeFile(
|
|
|
12
12
|
deps: Pick<ApiRouteDeps, "db" | "objectStorage" | "getDocumentServices">,
|
|
13
13
|
context: KnowledgeContext,
|
|
14
14
|
fileId: string,
|
|
15
|
+
purpose: "evidence" | "reference" = "evidence",
|
|
15
16
|
) {
|
|
16
17
|
const inspected = await inspectKnowledgeFilePreparation(deps.db, context, fileId);
|
|
17
18
|
if (inspected.status !== "prepare") return inspected;
|
|
@@ -27,13 +28,19 @@ export async function prepareKnowledgeFile(
|
|
|
27
28
|
) {
|
|
28
29
|
throw new Error("The original file no longer matches its retained metadata");
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
31
|
+
// A screenshot/diagram can support a visual finding without containing text.
|
|
32
|
+
// Keep its exact original; OCR is neither required evidence nor the finding.
|
|
33
|
+
const originalOnly = purpose === "evidence" && inspected.file.contentType.startsWith("image/");
|
|
34
|
+
const parsed = originalOnly
|
|
35
|
+
? { text: "" }
|
|
36
|
+
: await deps.getDocumentServices().parser.parse(object.bytes, inspected.file);
|
|
37
|
+
if (!originalOnly && !parsed.text.trim())
|
|
38
|
+
throw new Error("No searchable text could be extracted from this file");
|
|
33
39
|
return completeKnowledgeFilePreparation(deps.db, context, {
|
|
34
40
|
fileId,
|
|
35
41
|
title: inspected.file.filename,
|
|
36
42
|
sourceVersion,
|
|
37
43
|
content: parsed.text,
|
|
44
|
+
purpose,
|
|
38
45
|
});
|
|
39
46
|
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import type { KnowledgeEntryRecord, KnowledgeEntrySummary } from "@opengeni/contracts";
|
|
3
|
+
import { prepareKnowledgeSave } from "./knowledge-preparation";
|
|
4
|
+
|
|
5
|
+
const context = {
|
|
6
|
+
accountId: "account",
|
|
7
|
+
workspaceId: "workspace",
|
|
8
|
+
actor: { kind: "human", review: true },
|
|
9
|
+
} as never;
|
|
10
|
+
const db = {} as never;
|
|
11
|
+
const embedder = () => {
|
|
12
|
+
throw new Error("not used by fake search");
|
|
13
|
+
};
|
|
14
|
+
function group(title: string, parentIds: string[] = []): KnowledgeEntryRecord {
|
|
15
|
+
const id = crypto.randomUUID();
|
|
16
|
+
return {
|
|
17
|
+
id,
|
|
18
|
+
scope: "workspace",
|
|
19
|
+
version: 1,
|
|
20
|
+
publishedRevisionId: id,
|
|
21
|
+
latestRevisionId: id,
|
|
22
|
+
archived: false,
|
|
23
|
+
createdAt: "2026-09-14T00:00:00Z",
|
|
24
|
+
updatedAt: "2026-09-14T00:00:00Z",
|
|
25
|
+
revision: {
|
|
26
|
+
id,
|
|
27
|
+
entryId: id,
|
|
28
|
+
number: 1,
|
|
29
|
+
change: "upsert",
|
|
30
|
+
previousRevisionId: null,
|
|
31
|
+
restoredFromRevisionId: null,
|
|
32
|
+
createdAt: "2026-09-14T00:00:00Z",
|
|
33
|
+
createdBySessionId: null,
|
|
34
|
+
reviewBatchId: null,
|
|
35
|
+
outcome: "published",
|
|
36
|
+
entry: {
|
|
37
|
+
title,
|
|
38
|
+
kind: "group",
|
|
39
|
+
content: `Description of ${title}`,
|
|
40
|
+
groupIds: parentIds,
|
|
41
|
+
evidence: [],
|
|
42
|
+
relationships: [],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const summary = (record: KnowledgeEntryRecord) =>
|
|
48
|
+
({
|
|
49
|
+
...record,
|
|
50
|
+
revision: {
|
|
51
|
+
...record.revision,
|
|
52
|
+
title: record.revision.entry.title,
|
|
53
|
+
kind: "group",
|
|
54
|
+
preview: "preview",
|
|
55
|
+
groupIds: record.revision.entry.groupIds,
|
|
56
|
+
sourceKind: null,
|
|
57
|
+
},
|
|
58
|
+
excerpts: [],
|
|
59
|
+
}) as KnowledgeEntrySummary;
|
|
60
|
+
|
|
61
|
+
test("one read returns the complete paginated hierarchy and separately labeled duplicate candidates", async () => {
|
|
62
|
+
const parent = group("Engineering");
|
|
63
|
+
const child = group("Product UI", [parent.id]);
|
|
64
|
+
const pending = group("Proposed collection");
|
|
65
|
+
pending.revision.outcome = "pending";
|
|
66
|
+
const revoked = group("Revoked");
|
|
67
|
+
const calls: unknown[] = [];
|
|
68
|
+
const result = await prepareKnowledgeSave(
|
|
69
|
+
db,
|
|
70
|
+
context,
|
|
71
|
+
{ query: "plugin setup consistency" },
|
|
72
|
+
embedder,
|
|
73
|
+
{
|
|
74
|
+
list: async (actualDb, actualContext, request) => {
|
|
75
|
+
expect(actualDb).toBe(db);
|
|
76
|
+
expect(actualContext).toBe(context);
|
|
77
|
+
calls.push(request);
|
|
78
|
+
if (request?.view === "needs_review")
|
|
79
|
+
return { entries: [summary(pending)], nextCursor: null };
|
|
80
|
+
return request?.cursor
|
|
81
|
+
? { entries: [summary(child), summary(revoked)], nextCursor: null }
|
|
82
|
+
: { entries: [summary(parent)], nextCursor: "page2" };
|
|
83
|
+
},
|
|
84
|
+
get: async (_db, _context, id, options) => {
|
|
85
|
+
expect(options?.revisionId).toBeUndefined();
|
|
86
|
+
return [parent, child, pending].find((record) => record.id === id) ?? null;
|
|
87
|
+
},
|
|
88
|
+
search: async (_db, actualContext, request) => {
|
|
89
|
+
expect(actualContext).toBe(context);
|
|
90
|
+
expect(request.query).toBe("plugin setup consistency");
|
|
91
|
+
expect(request.groupId).toBeUndefined();
|
|
92
|
+
expect(request.scope).toBeUndefined();
|
|
93
|
+
return {
|
|
94
|
+
entries: [summary(request.view === "needs_review" ? pending : child)],
|
|
95
|
+
nextCursor: null,
|
|
96
|
+
searchMode: "hybrid",
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
expect(calls).toHaveLength(3);
|
|
102
|
+
expect(result.collections.complete).toBe(true);
|
|
103
|
+
expect(result.collections.entries.map((entry) => entry.title)).toEqual([
|
|
104
|
+
"Engineering",
|
|
105
|
+
"Product UI",
|
|
106
|
+
"Proposed collection",
|
|
107
|
+
]);
|
|
108
|
+
expect(result.collections.entries[1]?.parentIds).toEqual([parent.id]);
|
|
109
|
+
expect(result.collections.entries[0]?.description).toBe("Description of Engineering");
|
|
110
|
+
expect(result.collections.entries[2]?.view).toBe("needs_review");
|
|
111
|
+
expect(result.matches.published.entries[0]?.id).toBe(child.id);
|
|
112
|
+
expect(result.matches.needs_review.entries[0]?.id).toBe(pending.id);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("large collection maps explicitly continue and long descriptions report loss", async () => {
|
|
116
|
+
const record = group("Large collection");
|
|
117
|
+
record.revision.entry.content = "x".repeat(3000);
|
|
118
|
+
let count = 0;
|
|
119
|
+
const services: NonNullable<Parameters<typeof prepareKnowledgeSave>[4]> = {
|
|
120
|
+
list: async (_db, _context, request) => {
|
|
121
|
+
if (request?.view === "needs_review") return { entries: [], nextCursor: null };
|
|
122
|
+
return { entries: [summary(record)], nextCursor: `page${++count}` };
|
|
123
|
+
},
|
|
124
|
+
get: async () => record,
|
|
125
|
+
search: async () => ({ entries: [], nextCursor: null, searchMode: "keyword" }),
|
|
126
|
+
};
|
|
127
|
+
const result = await prepareKnowledgeSave(db, context, { query: "subject" }, embedder, services);
|
|
128
|
+
expect(result.collections.complete).toBe(false);
|
|
129
|
+
expect(result.collections.nextCursors.published).toBeTruthy();
|
|
130
|
+
expect(result.collections.nextCursors.needs_review).toBeNull();
|
|
131
|
+
expect(result.collections.entries[0]?.descriptionTruncated).toBe(true);
|
|
132
|
+
expect(result.collections.entries[0]?.description).toHaveLength(2000);
|
|
133
|
+
let requestedCursor: string | undefined;
|
|
134
|
+
await prepareKnowledgeSave(
|
|
135
|
+
db,
|
|
136
|
+
context,
|
|
137
|
+
{ query: "subject", collectionCursors: result.collections.nextCursors },
|
|
138
|
+
embedder,
|
|
139
|
+
{
|
|
140
|
+
...services,
|
|
141
|
+
list: async (_db, _context, request) => {
|
|
142
|
+
expect(request?.view).toBe("published");
|
|
143
|
+
requestedCursor = request?.cursor;
|
|
144
|
+
return { entries: [], nextCursor: null };
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
expect(requestedCursor).toBe(result.collections.nextCursors.published!);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("a catalog authorization failure is not disguised as an empty complete map", async () => {
|
|
152
|
+
await expect(
|
|
153
|
+
prepareKnowledgeSave(db, context, { query: "subject" }, embedder, {
|
|
154
|
+
list: async () => {
|
|
155
|
+
throw new Error("access revoked");
|
|
156
|
+
},
|
|
157
|
+
get: async () => null,
|
|
158
|
+
search: async () => ({ entries: [], nextCursor: null, searchMode: "keyword" }),
|
|
159
|
+
}),
|
|
160
|
+
).rejects.toThrow("access revoked");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("read-only principals without proposal access fail before searching or embedding", async () => {
|
|
164
|
+
let touched = false;
|
|
165
|
+
await expect(
|
|
166
|
+
prepareKnowledgeSave(
|
|
167
|
+
db,
|
|
168
|
+
{
|
|
169
|
+
accountId: "account",
|
|
170
|
+
workspaceId: "workspace",
|
|
171
|
+
actor: { kind: "service", review: false },
|
|
172
|
+
} as never,
|
|
173
|
+
{ query: "subject" },
|
|
174
|
+
() => {
|
|
175
|
+
touched = true;
|
|
176
|
+
throw new Error("must not embed");
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
list: async () => {
|
|
180
|
+
touched = true;
|
|
181
|
+
return { entries: [], nextCursor: null };
|
|
182
|
+
},
|
|
183
|
+
get: async () => {
|
|
184
|
+
touched = true;
|
|
185
|
+
return null;
|
|
186
|
+
},
|
|
187
|
+
search: async () => {
|
|
188
|
+
touched = true;
|
|
189
|
+
return { entries: [], nextCursor: null, searchMode: "keyword" };
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
),
|
|
193
|
+
).rejects.toMatchObject({ code: "42501" });
|
|
194
|
+
expect(touched).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("published and pending searches reuse one query embedding", async () => {
|
|
198
|
+
let calls = 0;
|
|
199
|
+
await prepareKnowledgeSave(
|
|
200
|
+
db,
|
|
201
|
+
context,
|
|
202
|
+
{ query: "plugin dialog" },
|
|
203
|
+
() => ({
|
|
204
|
+
model: "test",
|
|
205
|
+
dimensions: 2,
|
|
206
|
+
embedMany: async () => [],
|
|
207
|
+
embedQuery: async () => {
|
|
208
|
+
calls++;
|
|
209
|
+
return [1, 0];
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
{
|
|
213
|
+
list: async () => ({ entries: [], nextCursor: null }),
|
|
214
|
+
get: async () => null,
|
|
215
|
+
search: async (_db, _context, request, queryEmbedder) => {
|
|
216
|
+
expect(await queryEmbedder().embedQuery(request.query!)).toEqual([1, 0]);
|
|
217
|
+
return { entries: [], nextCursor: null, searchMode: "hybrid" };
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
expect(calls).toBe(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("a keyword fallback in the first view never retries an unfunded provider in the review view", async () => {
|
|
225
|
+
const searches: Array<{ mode: string | undefined; settingsProvided: boolean }> = [];
|
|
226
|
+
await prepareKnowledgeSave(
|
|
227
|
+
db,
|
|
228
|
+
context,
|
|
229
|
+
{ query: "Acme renewal" },
|
|
230
|
+
() => {
|
|
231
|
+
throw new Error("An unfunded provider was called");
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
list: async () => ({ entries: [], nextCursor: null }),
|
|
235
|
+
get: async () => null,
|
|
236
|
+
search: async (_db, _context, request, _embedder, settings) => {
|
|
237
|
+
searches.push({ mode: request.mode, settingsProvided: !!settings });
|
|
238
|
+
return { entries: [], nextCursor: null, searchMode: "keyword" };
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{ documentEmbeddingProvider: "openai", documentEmbeddingBillingMode: "credits" } as never,
|
|
242
|
+
);
|
|
243
|
+
expect(searches).toEqual([
|
|
244
|
+
{ mode: "keyword", settingsProvided: false },
|
|
245
|
+
{ mode: "keyword", settingsProvided: false },
|
|
246
|
+
]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("catalog rechecks current metadata after a collection changes", async () => {
|
|
250
|
+
const old = group("Old name");
|
|
251
|
+
const current = {
|
|
252
|
+
...old,
|
|
253
|
+
version: 2,
|
|
254
|
+
revision: {
|
|
255
|
+
...old.revision,
|
|
256
|
+
id: crypto.randomUUID(),
|
|
257
|
+
entry: { ...old.revision.entry, title: "New name" },
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
const result = await prepareKnowledgeSave(db, context, { query: "subject" }, embedder, {
|
|
261
|
+
list: async (_db, _context, request) => ({
|
|
262
|
+
entries: request?.view === "published" ? [summary(old)] : [],
|
|
263
|
+
nextCursor: null,
|
|
264
|
+
}),
|
|
265
|
+
get: async (_db, _context, _id, options) => {
|
|
266
|
+
expect(options?.revisionId).toBeUndefined();
|
|
267
|
+
return current;
|
|
268
|
+
},
|
|
269
|
+
search: async () => ({ entries: [], nextCursor: null, searchMode: "keyword" }),
|
|
270
|
+
});
|
|
271
|
+
expect(result.collections.entries[0]).toMatchObject({
|
|
272
|
+
title: "New name",
|
|
273
|
+
version: 2,
|
|
274
|
+
revisionId: current.revision.id,
|
|
275
|
+
});
|
|
276
|
+
});
|