@agent-native/dispatch 0.31.13 → 0.31.15
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/actions/list-dispatch-usage-metrics.js +9 -1
- package/dist/actions/list-dispatch-usage-metrics.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +12 -12
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +2 -2
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +17 -4
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +45 -20
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/other-apps-section.d.ts +5 -3
- package/dist/components/other-apps-section.d.ts.map +1 -1
- package/dist/components/other-apps-section.js +15 -3
- package/dist/components/other-apps-section.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +18 -1
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-host.d.ts +8 -2
- package/dist/components/workspace-app-host.d.ts.map +1 -1
- package/dist/components/workspace-app-host.js +46 -8
- package/dist/components/workspace-app-host.js.map +1 -1
- package/dist/lib/catch-all-target.d.ts.map +1 -1
- package/dist/lib/catch-all-target.js +4 -2
- package/dist/lib/catch-all-target.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +7 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +65 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/index.js +1 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +15 -4
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +16 -4
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/automations.d.ts.map +1 -1
- package/dist/routes/pages/automations.js +19 -16
- package/dist/routes/pages/automations.js.map +1 -1
- package/dist/server/lib/usage-metrics-store.d.ts +20 -0
- package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
- package/dist/server/lib/usage-metrics-store.js +86 -32
- package/dist/server/lib/usage-metrics-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +3 -0
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/index.spec.ts +17 -0
- package/src/actions/list-dispatch-usage-metrics.ts +9 -1
- package/src/components/dispatch-control-plane.spec.tsx +3 -1
- package/src/components/dispatch-control-plane.tsx +2 -1
- package/src/components/layout/Layout.spec.tsx +143 -25
- package/src/components/layout/Layout.tsx +79 -47
- package/src/components/other-apps-section.spec.ts +49 -0
- package/src/components/other-apps-section.tsx +31 -6
- package/src/components/workspace-app-card.tsx +27 -1
- package/src/components/workspace-app-host.spec.tsx +98 -0
- package/src/components/workspace-app-host.tsx +70 -4
- package/src/lib/catch-all-target.spec.ts +8 -0
- package/src/lib/catch-all-target.ts +4 -2
- package/src/lib/workspace-apps.spec.ts +40 -0
- package/src/lib/workspace-apps.ts +80 -0
- package/src/routes/index.spec.ts +10 -0
- package/src/routes/index.ts +1 -1
- package/src/routes/pages/apps.$appId.spec.tsx +50 -1
- package/src/routes/pages/apps.$appId.tsx +26 -3
- package/src/routes/pages/apps.tsx +42 -26
- package/src/routes/pages/automations.spec.tsx +240 -0
- package/src/routes/pages/automations.tsx +22 -16
- package/src/server/lib/usage-metrics-store.spec.ts +132 -1
- package/src/server/lib/usage-metrics-store.ts +173 -65
- package/src/server/plugins/agent-chat.spec.ts +2 -0
- package/src/server/plugins/agent-chat.ts +3 -0
- package/src/styles/dispatch-css.spec.ts +3 -0
- package/src/styles/dispatch.css +5 -0
|
@@ -2,7 +2,7 @@ import { detectEngineFromEnv, detectEngineFromUserSecrets, getAgentEngineEntry,
|
|
|
2
2
|
import { getDbExec } from "@agent-native/core/db";
|
|
3
3
|
import { getSetting } from "@agent-native/core/settings";
|
|
4
4
|
import { ForbiddenError } from "@agent-native/core/sharing";
|
|
5
|
-
import { getUsageSummary, usageBillingForEngine, } from "@agent-native/core/usage";
|
|
5
|
+
import { builderCreditsFromCostCents, getUsageSummary, usageBillingForEngine, } from "@agent-native/core/usage";
|
|
6
6
|
import { listWorkspaceApps, } from "./app-creation-store.js";
|
|
7
7
|
import { currentOrgId, currentOwnerEmail } from "./dispatch-store.js";
|
|
8
8
|
const DAY_MS = 86_400_000;
|
|
@@ -229,30 +229,6 @@ async function listOrgMembers(orgId) {
|
|
|
229
229
|
}))
|
|
230
230
|
.filter((member) => member.email);
|
|
231
231
|
}
|
|
232
|
-
async function listSignedInUsers() {
|
|
233
|
-
const authRows = await queryRows(`SELECT email, created_at AS joined_at FROM "user" ORDER BY created_at ASC`);
|
|
234
|
-
if (authRows.length > 0) {
|
|
235
|
-
return authRows
|
|
236
|
-
.map((row) => ({
|
|
237
|
-
email: stringField(row, "email").trim(),
|
|
238
|
-
role: null,
|
|
239
|
-
joinedAt: nullableNumberField(row, "joined_at"),
|
|
240
|
-
}))
|
|
241
|
-
.filter((member) => member.email);
|
|
242
|
-
}
|
|
243
|
-
const usageRows = await queryRows(`SELECT DISTINCT owner_email AS email FROM token_usage`);
|
|
244
|
-
const threadRows = await queryRows(`SELECT DISTINCT owner_email AS email FROM chat_threads`);
|
|
245
|
-
const emails = new Set();
|
|
246
|
-
for (const row of [...usageRows, ...threadRows]) {
|
|
247
|
-
if (row.email)
|
|
248
|
-
emails.add(row.email);
|
|
249
|
-
}
|
|
250
|
-
return [...emails].sort().map((email) => ({
|
|
251
|
-
email,
|
|
252
|
-
role: null,
|
|
253
|
-
joinedAt: null,
|
|
254
|
-
}));
|
|
255
|
-
}
|
|
256
232
|
function usageScope(sinceMs, memberEmails) {
|
|
257
233
|
if (memberEmails.length === 0) {
|
|
258
234
|
return { where: "created_at >= ?", args: [sinceMs] };
|
|
@@ -285,6 +261,19 @@ function ownerThreadScope(sinceMs, ownerEmail) {
|
|
|
285
261
|
args: [sinceMs, ownerEmail.toLowerCase()],
|
|
286
262
|
};
|
|
287
263
|
}
|
|
264
|
+
function workspaceAppCreationScope(sinceMs, orgId, memberEmails) {
|
|
265
|
+
const filters = ["created_at >= ?", "action = ?"];
|
|
266
|
+
const args = [sinceMs, "workspace-app.pending"];
|
|
267
|
+
if (orgId) {
|
|
268
|
+
filters.push("org_id = ?");
|
|
269
|
+
args.push(orgId);
|
|
270
|
+
}
|
|
271
|
+
if (memberEmails.length > 0) {
|
|
272
|
+
filters.push(`LOWER(owner_email) IN (${memberEmails.map(() => "?").join(", ")})`);
|
|
273
|
+
args.push(...memberEmails.map((email) => email.toLowerCase()));
|
|
274
|
+
}
|
|
275
|
+
return { where: filters.join(" AND "), args };
|
|
276
|
+
}
|
|
288
277
|
function bucketFromRow(row) {
|
|
289
278
|
const key = stringField(row, "k");
|
|
290
279
|
return {
|
|
@@ -362,7 +351,7 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
362
351
|
? [{ email: viewerEmail, role, joinedAt: null }]
|
|
363
352
|
: orgId
|
|
364
353
|
? await listOrgMembers(orgId)
|
|
365
|
-
:
|
|
354
|
+
: [{ email: viewerEmail, role: null, joinedAt: null }];
|
|
366
355
|
const members = viewScope === "workspace" && orgId && rawMembers.length === 0
|
|
367
356
|
? [{ email: viewerEmail, role, joinedAt: null }]
|
|
368
357
|
: rawMembers;
|
|
@@ -377,9 +366,7 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
377
366
|
}
|
|
378
367
|
const memberEmails = selectedUserEmail
|
|
379
368
|
? [selectedUserEmail]
|
|
380
|
-
:
|
|
381
|
-
? members.map((member) => member.email)
|
|
382
|
-
: [];
|
|
369
|
+
: members.map((member) => member.email);
|
|
383
370
|
const memberByEmail = new Map(members.map((member) => [member.email.toLowerCase(), member]));
|
|
384
371
|
const usage = selectedUserEmail
|
|
385
372
|
? ownerScope(sinceMs, selectedUserEmail)
|
|
@@ -387,7 +374,8 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
387
374
|
const threads = selectedUserEmail
|
|
388
375
|
? ownerThreadScope(sinceMs, selectedUserEmail)
|
|
389
376
|
: threadScope(sinceMs, memberEmails);
|
|
390
|
-
const
|
|
377
|
+
const workspaceAppCreation = workspaceAppCreationScope(sinceMs, orgId, memberEmails);
|
|
378
|
+
const [apps, totalsRows, byApp, byUserBase, byLabel, byModel, chatStats, workspaceAppCreationRows,] = await Promise.all([
|
|
391
379
|
listWorkspaceApps({ includeAgentCards: false }),
|
|
392
380
|
queryRows(`SELECT
|
|
393
381
|
COALESCE(SUM(cost_cents_x100), 0) AS cost_x100,
|
|
@@ -405,6 +393,10 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
405
393
|
usageBuckets(`COALESCE(NULLIF(label, ''), 'chat')`, usage.where, usage.args, 20),
|
|
406
394
|
usageBuckets(`COALESCE(NULLIF(model, ''), 'unknown')`, usage.where, usage.args, 20),
|
|
407
395
|
loadChatStats(threads.where, threads.args),
|
|
396
|
+
queryRows(`SELECT owner_email, actor, target_id, created_at
|
|
397
|
+
FROM dispatch_audit_events
|
|
398
|
+
WHERE ${workspaceAppCreation.where}
|
|
399
|
+
ORDER BY created_at ASC`, workspaceAppCreation.args),
|
|
408
400
|
]);
|
|
409
401
|
const topAppRows = await queryRows(`SELECT owner_email AS owner_email,
|
|
410
402
|
COALESCE(NULLIF(app, ''), 'unattributed') AS app,
|
|
@@ -463,15 +455,19 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
463
455
|
role: member?.role ?? null,
|
|
464
456
|
});
|
|
465
457
|
}
|
|
466
|
-
const dayRows = await queryRows(`SELECT created_at, owner_email, label,
|
|
458
|
+
const dayRows = await queryRows(`SELECT created_at, owner_email, label, input_tokens, output_tokens,
|
|
459
|
+
cache_read_tokens, cache_write_tokens, cost_cents_x100
|
|
467
460
|
FROM token_usage
|
|
468
461
|
WHERE ${usage.where}
|
|
469
462
|
ORDER BY created_at ASC`, usage.args);
|
|
470
463
|
const dailyMap = new Map();
|
|
464
|
+
const monthlyByUserMap = new Map();
|
|
471
465
|
for (const row of dayRows) {
|
|
472
466
|
const date = new Date(numberField(row, "created_at"))
|
|
473
467
|
.toISOString()
|
|
474
468
|
.slice(0, 10);
|
|
469
|
+
const month = date.slice(0, 7);
|
|
470
|
+
const ownerEmail = stringField(row, "owner_email");
|
|
475
471
|
const current = dailyMap.get(date) ?? {
|
|
476
472
|
costX100: 0,
|
|
477
473
|
calls: 0,
|
|
@@ -482,8 +478,29 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
482
478
|
current.calls += 1;
|
|
483
479
|
if (stringField(row, "label") === "chat")
|
|
484
480
|
current.chatCalls += 1;
|
|
485
|
-
current.users.add(
|
|
481
|
+
current.users.add(ownerEmail);
|
|
486
482
|
dailyMap.set(date, current);
|
|
483
|
+
const monthlyKey = `${ownerEmail}\u0000${month}`;
|
|
484
|
+
const monthly = monthlyByUserMap.get(monthlyKey) ?? {
|
|
485
|
+
month,
|
|
486
|
+
ownerEmail,
|
|
487
|
+
costCents: 0,
|
|
488
|
+
calls: 0,
|
|
489
|
+
chatCalls: 0,
|
|
490
|
+
inputTokens: 0,
|
|
491
|
+
outputTokens: 0,
|
|
492
|
+
cacheReadTokens: 0,
|
|
493
|
+
cacheWriteTokens: 0,
|
|
494
|
+
};
|
|
495
|
+
monthly.costCents += numberField(row, "cost_cents_x100") / 100;
|
|
496
|
+
monthly.calls += 1;
|
|
497
|
+
if (stringField(row, "label") === "chat")
|
|
498
|
+
monthly.chatCalls += 1;
|
|
499
|
+
monthly.inputTokens += numberField(row, "input_tokens");
|
|
500
|
+
monthly.outputTokens += numberField(row, "output_tokens");
|
|
501
|
+
monthly.cacheReadTokens += numberField(row, "cache_read_tokens");
|
|
502
|
+
monthly.cacheWriteTokens += numberField(row, "cache_write_tokens");
|
|
503
|
+
monthlyByUserMap.set(monthlyKey, monthly);
|
|
487
504
|
}
|
|
488
505
|
const daily = [...dailyMap.entries()]
|
|
489
506
|
.map(([date, value]) => ({
|
|
@@ -494,6 +511,41 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
494
511
|
activeUsers: value.users.size,
|
|
495
512
|
}))
|
|
496
513
|
.sort((a, b) => a.date.localeCompare(b.date));
|
|
514
|
+
const monthlyByUser = [...monthlyByUserMap.values()]
|
|
515
|
+
.map((row) => ({
|
|
516
|
+
...row,
|
|
517
|
+
credits: builderCreditsFromCostCents(row.costCents),
|
|
518
|
+
}))
|
|
519
|
+
.sort((a, b) => a.month.localeCompare(b.month) ||
|
|
520
|
+
a.ownerEmail.localeCompare(b.ownerEmail));
|
|
521
|
+
const workspaceAppCreationMap = new Map();
|
|
522
|
+
for (const row of workspaceAppCreationRows) {
|
|
523
|
+
const month = new Date(numberField(row, "created_at"))
|
|
524
|
+
.toISOString()
|
|
525
|
+
.slice(0, 7);
|
|
526
|
+
const ownerEmail = stringField(row, "owner_email") || stringField(row, "actor");
|
|
527
|
+
if (!ownerEmail)
|
|
528
|
+
continue;
|
|
529
|
+
const key = `${ownerEmail}\u0000${month}`;
|
|
530
|
+
const current = workspaceAppCreationMap.get(key) ?? {
|
|
531
|
+
month,
|
|
532
|
+
ownerEmail,
|
|
533
|
+
count: 0,
|
|
534
|
+
appIds: new Set(),
|
|
535
|
+
};
|
|
536
|
+
current.count += 1;
|
|
537
|
+
const appId = stringField(row, "target_id");
|
|
538
|
+
if (appId)
|
|
539
|
+
current.appIds.add(appId);
|
|
540
|
+
workspaceAppCreationMap.set(key, current);
|
|
541
|
+
}
|
|
542
|
+
const workspaceAppCreationsByUserMonth = [...workspaceAppCreationMap.values()]
|
|
543
|
+
.map(({ appIds, ...row }) => ({
|
|
544
|
+
...row,
|
|
545
|
+
appIds: [...appIds].sort(),
|
|
546
|
+
}))
|
|
547
|
+
.sort((a, b) => a.month.localeCompare(b.month) ||
|
|
548
|
+
a.ownerEmail.localeCompare(b.ownerEmail));
|
|
497
549
|
const recentRows = await queryRows(`SELECT id, created_at, owner_email, app, label, model,
|
|
498
550
|
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
499
551
|
cost_cents_x100, thread_id, run_id, task_id, source_platform, source_id
|
|
@@ -573,6 +625,8 @@ export async function listDispatchUsageMetrics(input) {
|
|
|
573
625
|
byLabel,
|
|
574
626
|
byModel,
|
|
575
627
|
daily,
|
|
628
|
+
monthlyByUser,
|
|
629
|
+
workspaceAppCreationsByUserMonth,
|
|
576
630
|
appAccess,
|
|
577
631
|
recent,
|
|
578
632
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage-metrics-store.js","sourceRoot":"","sources":["../../../src/server/lib/usage-metrics-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,mBAAmB,EACnB,8BAA8B,EAC9B,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EACL,eAAe,EACf,qBAAqB,GAEtB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,iBAAiB,GAElB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,MAAM,GAAG,UAAU,CAAC;AAE1B,sBAAsB,EAAE,CAAC;AA+HzB,SAAS,WAAW,CAAC,GAA4B,EAAE,GAAW;IAC5D,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,WAAW,CAAC,GAA4B,EAAE,GAAW;IAC5D,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,mBAAmB,CAC1B,GAA4B,EAC5B,GAAW;IAEX,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC;AAED,SAAS,mBAAmB,CAC1B,GAA4B,EAC5B,GAAW;IAEX,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,IAAI,IAAI,CAAC;AACzB,CAAC;AAOD,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YACzC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAiC,EAAE;YAChE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,IAA+B,CAAC;QAC7C,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAC1D,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAClB,CAAC,CAAC,EAAE,CAAC;IACT,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC;SACV,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,cAAc,CAAC,KAAa,EAAE,SAAS,GAAG,GAAG;IACpD,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC;AACvD,CAAC;AAED,SAAS,eAAe,CAAC,UAAmB;IAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QAClD,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;YAClD,CAAC,CAAE,MAAM,CAAC,OAAmC;YAC7C,CAAC,CAAC,MAAM,CAAC;QACb,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO;YAAE,SAAS;QAClD,MAAM,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,IAAI;YAAE,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAQD,KAAK,UAAU,oBAAoB,CACjC,IAAoC;IAEpC,MAAM,SAAS,GAAG;QAChB,GAAG,IAAI,GAAG,CACR,IAAI;aACD,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;aACnD,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACtD;KACF,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACnD,IAAI,sBAAsB,GAAG,KAAK,CAAC;IAEnC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC,OAAO,CAAC;gBACvC,GAAG,EAAE,kEAAkE,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;gBAC7G,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;YACH,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAyB,EAAE,CAAC;gBACnD,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpD,IAAI,EAAE;oBAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB,GAAG,IAAI,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACtB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,MAAM,OAAO,GACX,OAAO,MAAM,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,YAAY,GAAG,MAAM;YACzB,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,OAAO;gBACP,CAAC,CAAC,gBAAgB;gBAClB,CAAC,CAAC,sBAAsB,IAAI,QAAQ;oBAClC,CAAC,CAAC,aAAa;oBACf,CAAC,CAAC,cAAc,CAAC;QAEvB,OAAO;YACL,EAAE,EAAE,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;YAC1B,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC;YACzC,UAAU,EAAE,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC;YAC3C,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,cAAc;YAC9C,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM;YAC1C,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,SAAS;YAC7C,WAAW,EAAE,WAAW,CAAC,GAAG,EAAE,cAAc,CAAC;YAC7C,YAAY,EAAE,WAAW,CAAC,GAAG,EAAE,eAAe,CAAC;YAC/C,eAAe,EAAE,WAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC;YACtD,gBAAgB,EAAE,WAAW,CAAC,GAAG,EAAE,oBAAoB,CAAC;YACxD,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE,iBAAiB,CAAC,GAAG,GAAG;YACpD,MAAM,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5D,YAAY;YACZ,QAAQ;YACR,KAAK,EAAE,mBAAmB,CAAC,GAAG,EAAE,QAAQ,CAAC;YACzC,MAAM,EAAE,mBAAmB,CAAC,GAAG,EAAE,SAAS,CAAC;YAC3C,cAAc,EAAE,mBAAmB,CAAC,GAAG,EAAE,iBAAiB,CAAC;YAC3D,QAAQ,EAAE,mBAAmB,CAAC,GAAG,EAAE,WAAW,CAAC;SACpB,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,IAAI,cAAc,CAAC;AACnC,CAAC;AAED,SAAS,eAAe,CAAC,KAAgC;IACvD,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/C,IAAI,CAAC,GAAG;QAAE,OAAO,cAAc,CAAC;IAChC,OAAO,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC7B,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;SAC1C,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO;QACL,GAAG,SAAS,CAAC,uBAAuB,CAAC;QACrC,GAAG,SAAS,CAAC,uBAAuB,CAAC;QACrC,GAAG,SAAS,CAAC,8BAA8B,CAAC;KAC7C,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AACzB,CAAC;AAED,KAAK,UAAU,qBAAqB;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,MAAM,UAAU,CAAC,cAAc,CAAC,CAExC,CAAC;QACT,IAAI,8BAA8B,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3C,OAAQ,MAA6B,CAAC,MAAM,CAAC;QAC/C,CAAC;QACD,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACjD,IAAI,KAAK,IAAI,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;gBACjD,OAAO,MAAM,CAAC,MAAM,CAAC;YACvB,CAAC;QACH,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,2BAA2B,EAAE,CAAC;QAC7D,IAAI,gBAAgB;YAAE,OAAO,gBAAgB,CAAC,IAAI,CAAC;QAEnD,OAAO,mBAAmB,EAAE,EAAE,IAAI,IAAI,IAAI,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,GAAW,EACX,IAAI,GAAc,EAAE;IAEpB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,OAAO,MAAM,CAAC,IAAW,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,2BAA2B,CAAC,OAAe;IACxD,IAAI,CAAC;QACH,oEAAoE;QACpE,wEAAwE;QACxE,MAAM,eAAe,CAAC,EAAE,UAAU,EAAE,2BAA2B,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,2EAA2E;IAC7E,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,KAAoB,EACpB,KAAa;IAEb,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,4EAA4E,EAC5E,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAC7B,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;IAC3B,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,KAAoB;IAChD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,qGAAqG,EACrG,CAAC,KAAK,CAAC,CACR,CAAC;IACF,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACb,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE;QACvC,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI;QACtC,QAAQ,EAAE,mBAAmB,CAAC,GAAG,EAAE,WAAW,CAAC;KAChD,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC9B,MAAM,QAAQ,GAAG,MAAM,SAAS,CAC9B,2EAA2E,CAC5E,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,QAAQ;aACZ,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACb,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE;YACvC,IAAI,EAAE,IAAI;YACV,QAAQ,EAAE,mBAAmB,CAAC,GAAG,EAAE,WAAW,CAAC;SAChD,CAAC,CAAC;aACF,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,SAAS,CAC/B,uDAAuD,CACxD,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,SAAS,CAChC,wDAAwD,CACzD,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC;QAChD,IAAI,GAAG,CAAC,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxC,KAAK;QACL,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,UAAU,CACjB,OAAe,EACf,YAAsB;IAEtB,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO;QACL,KAAK,EAAE,8CAA8C,YAAY,GAAG;QACpE,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,OAAe,EACf,YAAsB;IAEtB,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO;QACL,KAAK,EAAE,8CAA8C,YAAY,GAAG;QACpE,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CACjB,OAAe,EACf,UAAkB;IAKlB,OAAO;QACL,KAAK,EAAE,4CAA4C;QACnD,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,OAAe,EACf,UAAkB;IAKlB,OAAO;QACL,KAAK,EAAE,4CAA4C;QACnD,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAA4B;IACjD,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAClC,OAAO;QACL,GAAG;QACH,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC;QACvB,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,GAAG;QAC9C,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC;QAChC,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC;QACzC,WAAW,EAAE,WAAW,CAAC,GAAG,EAAE,cAAc,CAAC;QAC7C,YAAY,EAAE,WAAW,CAAC,GAAG,EAAE,eAAe,CAAC;QAC/C,eAAe,EAAE,WAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC;QACtD,gBAAgB,EAAE,WAAW,CAAC,GAAG,EAAE,oBAAoB,CAAC;QACxD,WAAW,EAAE,WAAW,CAAC,GAAG,EAAE,cAAc,CAAC;QAC7C,YAAY,EAAE,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,gBAAwB,EACxB,KAAa,EACb,IAAe,EACf,KAAa;IAEb,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,UAAU,gBAAgB;;;;;;;;;;;cAWhB,KAAK;iBACF,gBAAgB;;cAEnB,EACV,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CACjB,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AACjC,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,KAAa,EACb,IAAe;IAEf,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B;;;;;cAKU,KAAK;2BACQ,EACvB,IAAI,CACL,CAAC;IACF,OAAO,IAAI,GAAG,CACZ,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;QAChB,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC;QAC/B;YACE,OAAO,EAAE,WAAW,CAAC,GAAG,EAAE,SAAS,CAAC;YACpC,QAAQ,EAAE,WAAW,CAAC,GAAG,EAAE,UAAU,CAAC;YACtC,UAAU,EAAE,mBAAmB,CAAC,GAAG,EAAE,cAAc,CAAC;SACrD;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,SAA4B;IAK9D,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IACxD,IACE,SAAS,KAAK,IAAI;QAClB,UAAU,CAAC,WAAW,CAAC;QACvB,IAAI,KAAK,OAAO;QAChB,IAAI,KAAK,OAAO,EAChB,CAAC;QACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;IACD,MAAM,IAAI,cAAc,CACtB,uEAAuE,CACxE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,KAI9C;IACC,MAAM,SAAS,GACb,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;IAC5C,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,MAAM,CAAC;IAChD,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,qBAAqB,EAAE,CAAC,CAAC;IAErE,MAAM,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAE3C,MAAM,UAAU,GACd,SAAS,KAAK,IAAI;QAChB,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAChD,CAAC,CAAC,KAAK;YACL,CAAC,CAAC,MAAM,cAAc,CAAC,KAAK,CAAC;YAC7B,CAAC,CAAC,MAAM,iBAAiB,EAAE,CAAC;IAClC,MAAM,OAAO,GACX,SAAS,KAAK,WAAW,IAAI,KAAK,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAC3D,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAChD,CAAC,CAAC,UAAU,CAAC;IACjB,MAAM,kBAAkB,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;IAC3D,MAAM,iBAAiB,GACrB,SAAS,KAAK,IAAI;QAChB,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,kBAAkB;YAClB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CACX,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,kBAAkB,CAAC,WAAW,EAAE,CAClE,EAAE,KAAK,IAAI,IAAI,CAAC;YACnB,CAAC,CAAC,IAAI,CAAC;IACb,IAAI,SAAS,KAAK,WAAW,IAAI,kBAAkB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC1E,MAAM,IAAI,cAAc,CACtB,uDAAuD,CACxD,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,iBAAiB;QACpC,CAAC,CAAC,CAAC,iBAAiB,CAAC;QACrB,CAAC,CAAC,KAAK;YACL,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;YACvC,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC,CAC9D,CAAC;IACF,MAAM,KAAK,GAAG,iBAAiB;QAC7B,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC;QACxC,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,iBAAiB;QAC/B,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,CAAC;QAC9C,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAEvC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,GACtE,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC;QAC/C,SAAS,CACP;;;;;;;;;;kBAUU,KAAK,CAAC,KAAK,EAAE,EACvB,KAAK,CAAC,IAAI,CACX;QACD,YAAY,CACV,2CAA2C,EAC3C,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,IAAI,EACV,EAAE,CACH;QACD,YAAY,CAAC,aAAa,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QACxD,YAAY,CACV,qCAAqC,EACrC,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,IAAI,EACV,EAAE,CACH;QACD,YAAY,CACV,wCAAwC,EACxC,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,IAAI,EACV,EAAE,CACH;QACD,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC;KAC3C,CAAC,CAAC;IAEL,MAAM,UAAU,GAAG,MAAM,SAAS,CAChC;;;;cAIU,KAAK,CAAC,KAAK;;+CAEsB,EAC3C,KAAK,CAAC,IAAI,CACX,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAC9C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;IACrD,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC;QAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI;YACzC,OAAO,EAAE,CAAC;YACV,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,IAAI;SACjB,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3D,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE;YACxB,GAAG,MAAM;YACT,UAAU;YACV,WAAW,EAAE,KAAK,CAAC,OAAO;YAC1B,YAAY,EAAE,KAAK,CAAC,QAAQ;YAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI;YAC5C,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,IAAI;SAC3B,CAAC,CAAC;IACL,CAAC;IACD,KAAK,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;QAC5C,IAAI,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;YAAE,SAAS;QACxC,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3D,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE;YACxB,GAAG,EAAE,UAAU;YACf,KAAK,EAAE,UAAU;YACjB,UAAU;YACV,SAAS,EAAE,CAAC;YACZ,KAAK,EAAE,CAAC;YACR,SAAS,EAAE,CAAC;YACZ,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;YACf,eAAe,EAAE,CAAC;YAClB,gBAAgB,EAAE,CAAC;YACnB,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,KAAK,CAAC,UAAU;YAC9B,WAAW,EAAE,KAAK,CAAC,OAAO;YAC1B,YAAY,EAAE,KAAK,CAAC,QAAQ;YAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,IAAI;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,SAAS,CAC7B;;cAEU,KAAK,CAAC,KAAK;8BACK,EAC1B,KAAK,CAAC,IAAI,CACX,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAGrB,CAAC;IACJ,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;aAClD,WAAW,EAAE;aACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI;YACpC,QAAQ,EAAE,CAAC;YACX,KAAK,EAAE,CAAC;YACR,SAAS,EAAE,CAAC;YACZ,KAAK,EAAE,IAAI,GAAG,EAAU;SACzB,CAAC;QACF,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QACxD,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;QACnB,IAAI,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,MAAM;YAAE,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC;QACnD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACvB,IAAI;QACJ,SAAS,EAAE,KAAK,CAAC,QAAQ,GAAG,GAAG;QAC/B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI;KAC9B,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhD,MAAM,UAAU,GAAG,MAAM,SAAS,CAChC;;;;cAIU,KAAK,CAAC,KAAK;;eAEV,EACX,KAAK,CAAC,IAAI,CACX,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAEtD,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAC7D,CAAC;IACF,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC;IACrD,MAAM,WAAW,GACf,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,MAAM,WAAW,GACf,SAAS,KAAK,IAAI;QAChB,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,KAAK;YACL,CAAC,CAAC,mBAAmB;YACrB,CAAC,CAAC,iBAAiB,CAAC;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACjC,MAAM,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,WAAW;YACX,WAAW;YACX,WAAW;YACX,cAAc,EAAE,WAAW,EAAE,WAAW,IAAI,CAAC;YAC7C,UAAU,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;YACnC,SAAS,EAAE,WAAW,EAAE,SAAS,IAAI,CAAC;YACtC,SAAS,EAAE,WAAW,EAAE,SAAS,IAAI,CAAC;YACtC,YAAY,EAAE,WAAW,EAAE,YAAY,IAAI,IAAI;SACtB,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,gBAAgB,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CACrD,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO;QACpC,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ;KACxC,CAAC,EACF,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAC5B,CAAC;IAEF,OAAO;QACL,OAAO;QACP,SAAS;QACT,iBAAiB;QACjB,cAAc,EAAE,OAAO;aACpB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;aAC3C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACjD,OAAO;QACP,SAAS;QACT,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;QACvB,MAAM,EAAE;YACN,WAAW;YACX,KAAK;YACL,IAAI;YACJ,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM;YACtC,UAAU,EAAE,WAAW;SACxB;QACD,MAAM,EAAE;YACN,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,GAAG;YACjD,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC;YACnC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC;YAC5C,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC;YAChD,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC;YAClD,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC;YACzD,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC;YAC3D,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC;YAChD,WAAW,EAAE,gBAAgB,CAAC,OAAO;YACrC,YAAY,EAAE,gBAAgB,CAAC,QAAQ;YACvC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM;SAC5D;QACD,KAAK;QACL,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5C,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;gBAAE,OAAO,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;YAClE,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC;QACF,OAAO;QACP,OAAO;QACP,KAAK;QACL,SAAS;QACT,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["import {\n detectEngineFromEnv,\n detectEngineFromUserSecrets,\n getAgentEngineEntry,\n isAgentEngineSettingConfigured,\n isStoredEngineUsable,\n registerBuiltinEngines,\n} from \"@agent-native/core/agent/engine\";\nimport { getDbExec } from \"@agent-native/core/db\";\nimport { getSetting } from \"@agent-native/core/settings\";\nimport { ForbiddenError } from \"@agent-native/core/sharing\";\nimport {\n getUsageSummary,\n usageBillingForEngine,\n type UsageBillingMode,\n} from \"@agent-native/core/usage\";\n\nimport {\n listWorkspaceApps,\n type WorkspaceAppSummary,\n} from \"./app-creation-store.js\";\nimport { currentOrgId, currentOwnerEmail } from \"./dispatch-store.js\";\n\nconst DAY_MS = 86_400_000;\n\nregisterBuiltinEngines();\n\nexport interface UsageMetricBucket {\n key: string;\n label: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n activeUsers: number;\n lastActiveAt: number | null;\n}\n\nexport interface UserUsageMetric extends UsageMetricBucket {\n ownerEmail: string;\n chatThreads: number;\n chatMessages: number;\n lastChatAt: number | null;\n topApp: string | null;\n role: string | null;\n}\n\nexport interface AppAccessMetric {\n id: string;\n name: string;\n path: string;\n status: WorkspaceAppSummary[\"status\"];\n statusLabel?: string;\n isDispatch: boolean;\n accessModel: \"workspace\" | \"solo\";\n accessLabel: string;\n accessUsers: number;\n usersWithUsage: number;\n usageCalls: number;\n chatCalls: number;\n costCents: number;\n lastActiveAt: number | null;\n}\n\nexport interface DailyUsageMetric {\n date: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n activeUsers: number;\n}\n\nexport interface RecentUsageMetric {\n id: number;\n createdAt: number;\n ownerEmail: string;\n app: string;\n label: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n costCents: number;\n prompt: string | null;\n promptSource: \"thread\" | \"thread-preview\" | \"not-captured\" | \"unavailable\";\n threadId: string | null;\n runId: string | null;\n taskId: string | null;\n sourcePlatform: string | null;\n sourceId: string | null;\n}\n\nexport type UsageMetricsScope = \"me\" | \"workspace\";\n\nexport interface UsageUserOption {\n email: string;\n role: string | null;\n}\n\nexport interface DispatchUsageMetrics {\n billing: UsageBillingMode;\n viewScope: UsageMetricsScope;\n selectedUserEmail: string | null;\n availableUsers: UsageUserOption[];\n sinceMs: number;\n sinceDays: number;\n generatedAt: number;\n access: {\n viewerEmail: string;\n orgId: string | null;\n role: string | null;\n scope: \"organization\" | \"solo\";\n totalUsers: number;\n };\n totals: {\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n activeUsers: number;\n chatThreads: number;\n chatMessages: number;\n workspaceApps: number;\n };\n byApp: UsageMetricBucket[];\n byUser: UserUsageMetric[];\n byLabel: UsageMetricBucket[];\n byModel: UsageMetricBucket[];\n daily: DailyUsageMetric[];\n appAccess: AppAccessMetric[];\n recent: RecentUsageMetric[];\n}\n\ninterface MemberRecord {\n email: string;\n role: string | null;\n joinedAt: number | null;\n}\n\ninterface ChatStats {\n threads: number;\n messages: number;\n lastChatAt: number | null;\n}\n\nfunction numberField(row: Record<string, unknown>, key: string): number {\n return Number(row[key] ?? 0) || 0;\n}\n\nfunction stringField(row: Record<string, unknown>, key: string): string {\n return String(row[key] ?? \"\");\n}\n\nfunction nullableNumberField(\n row: Record<string, unknown>,\n key: string,\n): number | null {\n const value = row[key];\n if (value == null) return null;\n const numberValue = Number(value);\n return Number.isFinite(numberValue) ? numberValue : null;\n}\n\nfunction nullableStringField(\n row: Record<string, unknown>,\n key: string,\n): string | null {\n const value = row[key];\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\ntype ParsedThreadData =\n | { status: \"absent\"; value: null }\n | { status: \"invalid\"; value: null }\n | { status: \"parsed\"; value: Record<string, unknown> };\n\nfunction parseJson(value: unknown): ParsedThreadData {\n if (typeof value !== \"string\" || !value.trim()) {\n return { status: \"absent\", value: null };\n }\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === \"object\"\n ? { status: \"parsed\", value: parsed as Record<string, unknown> }\n : { status: \"invalid\", value: null };\n } catch {\n return { status: \"invalid\", value: null };\n }\n}\n\nfunction textFromPromptContent(value: unknown): string {\n if (typeof value === \"string\") return value.trim();\n if (!Array.isArray(value)) return \"\";\n return value\n .map((part) => {\n if (!part || typeof part !== \"object\") return \"\";\n const item = part as Record<string, unknown>;\n return item.type === \"text\" && typeof item.text === \"string\"\n ? item.text.trim()\n : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\")\n .trim();\n}\n\nfunction truncatePrompt(value: string, maxLength = 360): string {\n if (value.length <= maxLength) return value;\n return `${value.slice(0, maxLength - 1).trimEnd()}…`;\n}\n\nfunction firstUserPrompt(threadData: unknown): string | null {\n const parsed = parseJson(threadData);\n if (parsed.status !== \"parsed\") return null;\n const messages = parsed.value.messages;\n if (!Array.isArray(messages)) return null;\n\n for (const entry of messages) {\n if (!entry || typeof entry !== \"object\") continue;\n const record = entry as Record<string, unknown>;\n const message =\n record.message && typeof record.message === \"object\"\n ? (record.message as Record<string, unknown>)\n : record;\n const role = typeof message.role === \"string\" ? message.role : \"\";\n if (role !== \"user\" && role !== \"human\") continue;\n const text = textFromPromptContent(message.content);\n if (text) return truncatePrompt(text);\n }\n return null;\n}\n\ninterface ThreadPromptRow {\n id?: unknown;\n preview?: unknown;\n thread_data?: unknown;\n}\n\nasync function hydrateRecentPrompts(\n rows: Array<Record<string, unknown>>,\n): Promise<RecentUsageMetric[]> {\n const threadIds = [\n ...new Set(\n rows\n .map((row) => nullableStringField(row, \"thread_id\"))\n .filter((value): value is string => Boolean(value)),\n ),\n ];\n const threads = new Map<string, ThreadPromptRow>();\n let threadQueryUnavailable = false;\n\n if (threadIds.length > 0) {\n try {\n const result = await getDbExec().execute({\n sql: `SELECT id, preview, thread_data FROM chat_threads WHERE id IN (${threadIds.map(() => \"?\").join(\", \")})`,\n args: threadIds,\n });\n for (const row of result.rows as ThreadPromptRow[]) {\n const id = typeof row.id === \"string\" ? row.id : \"\";\n if (id) threads.set(id, row);\n }\n } catch {\n threadQueryUnavailable = true;\n }\n }\n\n return rows.map((row) => {\n const threadId = nullableStringField(row, \"thread_id\");\n const thread = threadId ? threads.get(threadId) : undefined;\n const prompt = thread ? firstUserPrompt(thread.thread_data) : null;\n const preview =\n typeof thread?.preview === \"string\" ? thread.preview.trim() : \"\";\n const promptSource = prompt\n ? \"thread\"\n : preview\n ? \"thread-preview\"\n : threadQueryUnavailable && threadId\n ? \"unavailable\"\n : \"not-captured\";\n\n return {\n id: numberField(row, \"id\"),\n createdAt: numberField(row, \"created_at\"),\n ownerEmail: stringField(row, \"owner_email\"),\n app: stringField(row, \"app\") || \"unattributed\",\n label: stringField(row, \"label\") || \"chat\",\n model: stringField(row, \"model\") || \"unknown\",\n inputTokens: numberField(row, \"input_tokens\"),\n outputTokens: numberField(row, \"output_tokens\"),\n cacheReadTokens: numberField(row, \"cache_read_tokens\"),\n cacheWriteTokens: numberField(row, \"cache_write_tokens\"),\n costCents: numberField(row, \"cost_cents_x100\") / 100,\n prompt: prompt ?? (preview ? truncatePrompt(preview) : null),\n promptSource,\n threadId,\n runId: nullableStringField(row, \"run_id\"),\n taskId: nullableStringField(row, \"task_id\"),\n sourcePlatform: nullableStringField(row, \"source_platform\"),\n sourceId: nullableStringField(row, \"source_id\"),\n } satisfies RecentUsageMetric;\n });\n}\n\nfunction labelForKey(value: string): string {\n const trimmed = value.trim();\n return trimmed || \"Unattributed\";\n}\n\nfunction normalizeAppKey(value: string | null | undefined): string {\n const raw = (value ?? \"\").trim().toLowerCase();\n if (!raw) return \"unattributed\";\n return raw.replace(/^agent-native-/, \"\");\n}\n\nfunction envEmails(name: string): string[] {\n return (process.env[name] ?? \"\")\n .split(\",\")\n .map((value) => value.trim().toLowerCase())\n .filter(Boolean);\n}\n\nfunction isEnvAdmin(email: string): boolean {\n const normalized = email.trim().toLowerCase();\n return [\n ...envEmails(\"DISPATCH_ADMIN_EMAILS\"),\n ...envEmails(\"WORKSPACE_OWNER_EMAIL\"),\n ...envEmails(\"DISPATCH_DEFAULT_OWNER_EMAIL\"),\n ].includes(normalized);\n}\n\nasync function detectUsageEngineName(): Promise<string | null> {\n try {\n const stored = (await getSetting(\"agent-engine\")) as {\n engine?: string;\n } | null;\n if (isAgentEngineSettingConfigured(stored)) {\n return (stored as { engine: string }).engine;\n }\n if (stored && typeof stored.engine === \"string\") {\n const entry = getAgentEngineEntry(stored.engine);\n if (entry && isStoredEngineUsable(stored, entry)) {\n return stored.engine;\n }\n }\n\n const detectedFromUser = await detectEngineFromUserSecrets();\n if (detectedFromUser) return detectedFromUser.name;\n\n return detectEngineFromEnv()?.name ?? null;\n } catch {\n return null;\n }\n}\n\nasync function queryRows<T extends Record<string, unknown>>(\n sql: string,\n args: unknown[] = [],\n): Promise<T[]> {\n try {\n const result = await getDbExec().execute({ sql, args });\n return result.rows as T[];\n } catch {\n return [];\n }\n}\n\nasync function initializeUsageMetricsTable(sinceMs: number): Promise<void> {\n try {\n // Initializes token_usage on fresh deployments before the read-only\n // aggregate queries below. The fake owner avoids changing visible data.\n await getUsageSummary({ ownerEmail: \"__dispatch_metrics_init__\", sinceMs });\n } catch {\n // Metrics should still render an empty state if usage storage is locked,\n // stale, or unavailable; each aggregate read below is already best-effort.\n }\n}\n\nasync function getViewerOrgRole(\n orgId: string | null,\n email: string,\n): Promise<string | null> {\n if (!orgId) return null;\n const rows = await queryRows<{ role?: string }>(\n `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,\n [orgId, email.toLowerCase()],\n );\n const role = rows[0]?.role;\n return typeof role === \"string\" ? role : null;\n}\n\nasync function listOrgMembers(orgId: string | null): Promise<MemberRecord[]> {\n if (!orgId) return [];\n const rows = await queryRows<Record<string, unknown>>(\n `SELECT email, role, joined_at AS joined_at FROM org_members WHERE org_id = ? ORDER BY joined_at ASC`,\n [orgId],\n );\n return rows\n .map((row) => ({\n email: stringField(row, \"email\").trim(),\n role: stringField(row, \"role\") || null,\n joinedAt: nullableNumberField(row, \"joined_at\"),\n }))\n .filter((member) => member.email);\n}\n\nasync function listSignedInUsers(): Promise<MemberRecord[]> {\n const authRows = await queryRows<Record<string, unknown>>(\n `SELECT email, created_at AS joined_at FROM \"user\" ORDER BY created_at ASC`,\n );\n if (authRows.length > 0) {\n return authRows\n .map((row) => ({\n email: stringField(row, \"email\").trim(),\n role: null,\n joinedAt: nullableNumberField(row, \"joined_at\"),\n }))\n .filter((member) => member.email);\n }\n\n const usageRows = await queryRows<{ email?: string }>(\n `SELECT DISTINCT owner_email AS email FROM token_usage`,\n );\n const threadRows = await queryRows<{ email?: string }>(\n `SELECT DISTINCT owner_email AS email FROM chat_threads`,\n );\n const emails = new Set<string>();\n for (const row of [...usageRows, ...threadRows]) {\n if (row.email) emails.add(row.email);\n }\n return [...emails].sort().map((email) => ({\n email,\n role: null,\n joinedAt: null,\n }));\n}\n\nfunction usageScope(\n sinceMs: number,\n memberEmails: string[],\n): { where: string; args: unknown[] } {\n if (memberEmails.length === 0) {\n return { where: \"created_at >= ?\", args: [sinceMs] };\n }\n const placeholders = memberEmails.map(() => \"?\").join(\", \");\n return {\n where: `created_at >= ? AND LOWER(owner_email) IN (${placeholders})`,\n args: [sinceMs, ...memberEmails.map((email) => email.toLowerCase())],\n };\n}\n\nfunction threadScope(\n sinceMs: number,\n memberEmails: string[],\n): { where: string; args: unknown[] } {\n if (memberEmails.length === 0) {\n return { where: \"updated_at >= ?\", args: [sinceMs] };\n }\n const placeholders = memberEmails.map(() => \"?\").join(\", \");\n return {\n where: `updated_at >= ? AND LOWER(owner_email) IN (${placeholders})`,\n args: [sinceMs, ...memberEmails.map((email) => email.toLowerCase())],\n };\n}\n\nfunction ownerScope(\n sinceMs: number,\n ownerEmail: string,\n): {\n where: string;\n args: unknown[];\n} {\n return {\n where: \"created_at >= ? AND LOWER(owner_email) = ?\",\n args: [sinceMs, ownerEmail.toLowerCase()],\n };\n}\n\nfunction ownerThreadScope(\n sinceMs: number,\n ownerEmail: string,\n): {\n where: string;\n args: unknown[];\n} {\n return {\n where: \"updated_at >= ? AND LOWER(owner_email) = ?\",\n args: [sinceMs, ownerEmail.toLowerCase()],\n };\n}\n\nfunction bucketFromRow(row: Record<string, unknown>): UsageMetricBucket {\n const key = stringField(row, \"k\");\n return {\n key,\n label: labelForKey(key),\n costCents: numberField(row, \"cost_x100\") / 100,\n calls: numberField(row, \"calls\"),\n chatCalls: numberField(row, \"chat_calls\"),\n inputTokens: numberField(row, \"input_tokens\"),\n outputTokens: numberField(row, \"output_tokens\"),\n cacheReadTokens: numberField(row, \"cache_read_tokens\"),\n cacheWriteTokens: numberField(row, \"cache_write_tokens\"),\n activeUsers: numberField(row, \"active_users\"),\n lastActiveAt: nullableNumberField(row, \"last_active_at\"),\n };\n}\n\nasync function usageBuckets(\n columnExpression: string,\n where: string,\n args: unknown[],\n limit: number,\n): Promise<UsageMetricBucket[]> {\n const rows = await queryRows<Record<string, unknown>>(\n `SELECT ${columnExpression} AS k,\n COALESCE(SUM(cost_cents_x100), 0) AS cost_x100,\n COUNT(*) AS calls,\n SUM(CASE WHEN label = 'chat' THEN 1 ELSE 0 END) AS chat_calls,\n COALESCE(SUM(input_tokens), 0) AS input_tokens,\n COALESCE(SUM(output_tokens), 0) AS output_tokens,\n COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,\n COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,\n COUNT(DISTINCT owner_email) AS active_users,\n MAX(created_at) AS last_active_at\n FROM token_usage\n WHERE ${where}\n GROUP BY ${columnExpression}\n ORDER BY cost_x100 DESC\n LIMIT ?`,\n [...args, limit],\n );\n return rows.map(bucketFromRow);\n}\n\nasync function loadChatStats(\n where: string,\n args: unknown[],\n): Promise<Map<string, ChatStats>> {\n const rows = await queryRows<Record<string, unknown>>(\n `SELECT owner_email AS owner_email,\n COUNT(*) AS threads,\n COALESCE(SUM(message_count), 0) AS messages,\n MAX(updated_at) AS last_chat_at\n FROM chat_threads\n WHERE ${where}\n GROUP BY owner_email`,\n args,\n );\n return new Map(\n rows.map((row) => [\n stringField(row, \"owner_email\"),\n {\n threads: numberField(row, \"threads\"),\n messages: numberField(row, \"messages\"),\n lastChatAt: nullableNumberField(row, \"last_chat_at\"),\n },\n ]),\n );\n}\n\nasync function assertCanViewMetrics(viewScope: UsageMetricsScope): Promise<{\n viewerEmail: string;\n orgId: string | null;\n role: string | null;\n}> {\n const viewerEmail = currentOwnerEmail();\n const orgId = currentOrgId();\n const role = await getViewerOrgRole(orgId, viewerEmail);\n if (\n viewScope === \"me\" ||\n isEnvAdmin(viewerEmail) ||\n role === \"owner\" ||\n role === \"admin\"\n ) {\n return { viewerEmail, orgId, role };\n }\n if (!orgId) {\n return { viewerEmail, orgId, role };\n }\n throw new ForbiddenError(\n \"Only organization owners and admins can view workspace usage metrics.\",\n );\n}\n\nexport async function listDispatchUsageMetrics(input: {\n sinceDays?: number;\n scope?: UsageMetricsScope;\n userEmail?: string | null;\n}): Promise<DispatchUsageMetrics> {\n const viewScope: UsageMetricsScope =\n input.scope === \"me\" ? \"me\" : \"workspace\";\n const { viewerEmail, orgId, role } = await assertCanViewMetrics(viewScope);\n const sinceDays = Math.max(1, Math.min(365, input.sinceDays ?? 30));\n const sinceMs = Date.now() - sinceDays * DAY_MS;\n const billing = usageBillingForEngine(await detectUsageEngineName());\n\n await initializeUsageMetricsTable(sinceMs);\n\n const rawMembers =\n viewScope === \"me\"\n ? [{ email: viewerEmail, role, joinedAt: null }]\n : orgId\n ? await listOrgMembers(orgId)\n : await listSignedInUsers();\n const members =\n viewScope === \"workspace\" && orgId && rawMembers.length === 0\n ? [{ email: viewerEmail, role, joinedAt: null }]\n : rawMembers;\n const requestedUserEmail = input.userEmail?.trim() || null;\n const selectedUserEmail =\n viewScope === \"me\"\n ? viewerEmail\n : requestedUserEmail\n ? (members.find(\n (member) =>\n member.email.toLowerCase() === requestedUserEmail.toLowerCase(),\n )?.email ?? null)\n : null;\n if (viewScope === \"workspace\" && requestedUserEmail && !selectedUserEmail) {\n throw new ForbiddenError(\n \"The selected user is not available in this workspace.\",\n );\n }\n const memberEmails = selectedUserEmail\n ? [selectedUserEmail]\n : orgId\n ? members.map((member) => member.email)\n : [];\n const memberByEmail = new Map(\n members.map((member) => [member.email.toLowerCase(), member]),\n );\n const usage = selectedUserEmail\n ? ownerScope(sinceMs, selectedUserEmail)\n : usageScope(sinceMs, memberEmails);\n const threads = selectedUserEmail\n ? ownerThreadScope(sinceMs, selectedUserEmail)\n : threadScope(sinceMs, memberEmails);\n\n const [apps, totalsRows, byApp, byUserBase, byLabel, byModel, chatStats] =\n await Promise.all([\n listWorkspaceApps({ includeAgentCards: false }),\n queryRows<Record<string, unknown>>(\n `SELECT\n COALESCE(SUM(cost_cents_x100), 0) AS cost_x100,\n COUNT(*) AS calls,\n SUM(CASE WHEN label = 'chat' THEN 1 ELSE 0 END) AS chat_calls,\n COALESCE(SUM(input_tokens), 0) AS input_tokens,\n COALESCE(SUM(output_tokens), 0) AS output_tokens,\n COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,\n COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,\n COUNT(DISTINCT owner_email) AS active_users\n FROM token_usage\n WHERE ${usage.where}`,\n usage.args,\n ),\n usageBuckets(\n `COALESCE(NULLIF(app, ''), 'unattributed')`,\n usage.where,\n usage.args,\n 20,\n ),\n usageBuckets(\"owner_email\", usage.where, usage.args, 50),\n usageBuckets(\n `COALESCE(NULLIF(label, ''), 'chat')`,\n usage.where,\n usage.args,\n 20,\n ),\n usageBuckets(\n `COALESCE(NULLIF(model, ''), 'unknown')`,\n usage.where,\n usage.args,\n 20,\n ),\n loadChatStats(threads.where, threads.args),\n ]);\n\n const topAppRows = await queryRows<Record<string, unknown>>(\n `SELECT owner_email AS owner_email,\n COALESCE(NULLIF(app, ''), 'unattributed') AS app,\n COALESCE(SUM(cost_cents_x100), 0) AS cost_x100\n FROM token_usage\n WHERE ${usage.where}\n GROUP BY owner_email, COALESCE(NULLIF(app, ''), 'unattributed')\n ORDER BY owner_email ASC, cost_x100 DESC`,\n usage.args,\n );\n const topAppByUser = new Map<string, string>();\n for (const row of topAppRows) {\n const email = stringField(row, \"owner_email\");\n if (!topAppByUser.has(email)) {\n topAppByUser.set(email, stringField(row, \"app\"));\n }\n }\n\n const byUserMap = new Map<string, UserUsageMetric>();\n for (const bucket of byUserBase) {\n const ownerEmail = bucket.key;\n const stats = chatStats.get(ownerEmail) ?? {\n threads: 0,\n messages: 0,\n lastChatAt: null,\n };\n const member = memberByEmail.get(ownerEmail.toLowerCase());\n byUserMap.set(ownerEmail, {\n ...bucket,\n ownerEmail,\n chatThreads: stats.threads,\n chatMessages: stats.messages,\n lastChatAt: stats.lastChatAt,\n topApp: topAppByUser.get(ownerEmail) ?? null,\n role: member?.role ?? null,\n });\n }\n for (const [ownerEmail, stats] of chatStats) {\n if (byUserMap.has(ownerEmail)) continue;\n const member = memberByEmail.get(ownerEmail.toLowerCase());\n byUserMap.set(ownerEmail, {\n key: ownerEmail,\n label: ownerEmail,\n ownerEmail,\n costCents: 0,\n calls: 0,\n chatCalls: 0,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n activeUsers: 1,\n lastActiveAt: stats.lastChatAt,\n chatThreads: stats.threads,\n chatMessages: stats.messages,\n lastChatAt: stats.lastChatAt,\n topApp: null,\n role: member?.role ?? null,\n });\n }\n\n const dayRows = await queryRows<Record<string, unknown>>(\n `SELECT created_at, owner_email, label, cost_cents_x100\n FROM token_usage\n WHERE ${usage.where}\n ORDER BY created_at ASC`,\n usage.args,\n );\n const dailyMap = new Map<\n string,\n { costX100: number; calls: number; chatCalls: number; users: Set<string> }\n >();\n for (const row of dayRows) {\n const date = new Date(numberField(row, \"created_at\"))\n .toISOString()\n .slice(0, 10);\n const current = dailyMap.get(date) ?? {\n costX100: 0,\n calls: 0,\n chatCalls: 0,\n users: new Set<string>(),\n };\n current.costX100 += numberField(row, \"cost_cents_x100\");\n current.calls += 1;\n if (stringField(row, \"label\") === \"chat\") current.chatCalls += 1;\n current.users.add(stringField(row, \"owner_email\"));\n dailyMap.set(date, current);\n }\n const daily = [...dailyMap.entries()]\n .map(([date, value]) => ({\n date,\n costCents: value.costX100 / 100,\n calls: value.calls,\n chatCalls: value.chatCalls,\n activeUsers: value.users.size,\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n const recentRows = await queryRows<Record<string, unknown>>(\n `SELECT id, created_at, owner_email, app, label, model,\n input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,\n cost_cents_x100, thread_id, run_id, task_id, source_platform, source_id\n FROM token_usage\n WHERE ${usage.where}\n ORDER BY created_at DESC\n LIMIT 50`,\n usage.args,\n );\n const recent = await hydrateRecentPrompts(recentRows);\n\n const appUsageByKey = new Map(\n byApp.map((bucket) => [normalizeAppKey(bucket.key), bucket]),\n );\n const accessUsers = members.length || byUserMap.size;\n const accessModel =\n viewScope === \"me\" ? \"solo\" : orgId ? \"workspace\" : \"solo\";\n const accessLabel =\n viewScope === \"me\"\n ? \"Your account\"\n : orgId\n ? \"Workspace members\"\n : \"Signed-in users\";\n const appAccess = apps.map((app) => {\n const usageBucket = appUsageByKey.get(normalizeAppKey(app.id));\n return {\n id: app.id,\n name: app.name,\n path: app.path,\n status: app.status,\n statusLabel: app.statusLabel,\n isDispatch: app.isDispatch,\n accessModel,\n accessLabel,\n accessUsers,\n usersWithUsage: usageBucket?.activeUsers ?? 0,\n usageCalls: usageBucket?.calls ?? 0,\n chatCalls: usageBucket?.chatCalls ?? 0,\n costCents: usageBucket?.costCents ?? 0,\n lastActiveAt: usageBucket?.lastActiveAt ?? null,\n } satisfies AppAccessMetric;\n });\n\n const totals = totalsRows[0] ?? {};\n const chatThreadTotals = [...chatStats.values()].reduce(\n (acc, value) => ({\n threads: acc.threads + value.threads,\n messages: acc.messages + value.messages,\n }),\n { threads: 0, messages: 0 },\n );\n\n return {\n billing,\n viewScope,\n selectedUserEmail,\n availableUsers: members\n .map(({ email, role }) => ({ email, role }))\n .sort((a, b) => a.email.localeCompare(b.email)),\n sinceMs,\n sinceDays,\n generatedAt: Date.now(),\n access: {\n viewerEmail,\n orgId,\n role,\n scope: orgId ? \"organization\" : \"solo\",\n totalUsers: accessUsers,\n },\n totals: {\n costCents: numberField(totals, \"cost_x100\") / 100,\n calls: numberField(totals, \"calls\"),\n chatCalls: numberField(totals, \"chat_calls\"),\n inputTokens: numberField(totals, \"input_tokens\"),\n outputTokens: numberField(totals, \"output_tokens\"),\n cacheReadTokens: numberField(totals, \"cache_read_tokens\"),\n cacheWriteTokens: numberField(totals, \"cache_write_tokens\"),\n activeUsers: numberField(totals, \"active_users\"),\n chatThreads: chatThreadTotals.threads,\n chatMessages: chatThreadTotals.messages,\n workspaceApps: apps.filter((app) => !app.isDispatch).length,\n },\n byApp,\n byUser: [...byUserMap.values()].sort((a, b) => {\n if (b.costCents !== a.costCents) return b.costCents - a.costCents;\n return (b.lastActiveAt ?? 0) - (a.lastActiveAt ?? 0);\n }),\n byLabel,\n byModel,\n daily,\n appAccess,\n recent,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"usage-metrics-store.js","sourceRoot":"","sources":["../../../src/server/lib/usage-metrics-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n detectEngineFromEnv,\n detectEngineFromUserSecrets,\n getAgentEngineEntry,\n isAgentEngineSettingConfigured,\n isStoredEngineUsable,\n registerBuiltinEngines,\n} from \"@agent-native/core/agent/engine\";\nimport { getDbExec } from \"@agent-native/core/db\";\nimport { getSetting } from \"@agent-native/core/settings\";\nimport { ForbiddenError } from \"@agent-native/core/sharing\";\nimport {\n builderCreditsFromCostCents,\n getUsageSummary,\n usageBillingForEngine,\n type UsageBillingMode,\n} from \"@agent-native/core/usage\";\n\nimport {\n listWorkspaceApps,\n type WorkspaceAppSummary,\n} from \"./app-creation-store.js\";\nimport { currentOrgId, currentOwnerEmail } from \"./dispatch-store.js\";\n\nconst DAY_MS = 86_400_000;\n\nregisterBuiltinEngines();\n\nexport interface UsageMetricBucket {\n key: string;\n label: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n activeUsers: number;\n lastActiveAt: number | null;\n}\n\nexport interface UserUsageMetric extends UsageMetricBucket {\n ownerEmail: string;\n chatThreads: number;\n chatMessages: number;\n lastChatAt: number | null;\n topApp: string | null;\n role: string | null;\n}\n\nexport interface AppAccessMetric {\n id: string;\n name: string;\n path: string;\n status: WorkspaceAppSummary[\"status\"];\n statusLabel?: string;\n isDispatch: boolean;\n accessModel: \"workspace\" | \"solo\";\n accessLabel: string;\n accessUsers: number;\n usersWithUsage: number;\n usageCalls: number;\n chatCalls: number;\n costCents: number;\n lastActiveAt: number | null;\n}\n\nexport interface DailyUsageMetric {\n date: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n activeUsers: number;\n}\n\nexport interface MonthlyUserUsageMetric {\n month: string;\n ownerEmail: string;\n costCents: number;\n credits: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}\n\nexport interface WorkspaceAppCreationMetric {\n month: string;\n ownerEmail: string;\n count: number;\n appIds: string[];\n}\n\nexport interface RecentUsageMetric {\n id: number;\n createdAt: number;\n ownerEmail: string;\n app: string;\n label: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n costCents: number;\n prompt: string | null;\n promptSource: \"thread\" | \"thread-preview\" | \"not-captured\" | \"unavailable\";\n threadId: string | null;\n runId: string | null;\n taskId: string | null;\n sourcePlatform: string | null;\n sourceId: string | null;\n}\n\nexport type UsageMetricsScope = \"me\" | \"workspace\";\n\nexport interface UsageUserOption {\n email: string;\n role: string | null;\n}\n\nexport interface DispatchUsageMetrics {\n billing: UsageBillingMode;\n viewScope: UsageMetricsScope;\n selectedUserEmail: string | null;\n availableUsers: UsageUserOption[];\n sinceMs: number;\n sinceDays: number;\n generatedAt: number;\n access: {\n viewerEmail: string;\n orgId: string | null;\n role: string | null;\n scope: \"organization\" | \"solo\";\n totalUsers: number;\n };\n totals: {\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n activeUsers: number;\n chatThreads: number;\n chatMessages: number;\n workspaceApps: number;\n };\n byApp: UsageMetricBucket[];\n byUser: UserUsageMetric[];\n byLabel: UsageMetricBucket[];\n byModel: UsageMetricBucket[];\n daily: DailyUsageMetric[];\n monthlyByUser: MonthlyUserUsageMetric[];\n workspaceAppCreationsByUserMonth: WorkspaceAppCreationMetric[];\n appAccess: AppAccessMetric[];\n recent: RecentUsageMetric[];\n}\n\ninterface MemberRecord {\n email: string;\n role: string | null;\n joinedAt: number | null;\n}\n\ninterface ChatStats {\n threads: number;\n messages: number;\n lastChatAt: number | null;\n}\n\nfunction numberField(row: Record<string, unknown>, key: string): number {\n return Number(row[key] ?? 0) || 0;\n}\n\nfunction stringField(row: Record<string, unknown>, key: string): string {\n return String(row[key] ?? \"\");\n}\n\nfunction nullableNumberField(\n row: Record<string, unknown>,\n key: string,\n): number | null {\n const value = row[key];\n if (value == null) return null;\n const numberValue = Number(value);\n return Number.isFinite(numberValue) ? numberValue : null;\n}\n\nfunction nullableStringField(\n row: Record<string, unknown>,\n key: string,\n): string | null {\n const value = row[key];\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\ntype ParsedThreadData =\n | { status: \"absent\"; value: null }\n | { status: \"invalid\"; value: null }\n | { status: \"parsed\"; value: Record<string, unknown> };\n\nfunction parseJson(value: unknown): ParsedThreadData {\n if (typeof value !== \"string\" || !value.trim()) {\n return { status: \"absent\", value: null };\n }\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === \"object\"\n ? { status: \"parsed\", value: parsed as Record<string, unknown> }\n : { status: \"invalid\", value: null };\n } catch {\n return { status: \"invalid\", value: null };\n }\n}\n\nfunction textFromPromptContent(value: unknown): string {\n if (typeof value === \"string\") return value.trim();\n if (!Array.isArray(value)) return \"\";\n return value\n .map((part) => {\n if (!part || typeof part !== \"object\") return \"\";\n const item = part as Record<string, unknown>;\n return item.type === \"text\" && typeof item.text === \"string\"\n ? item.text.trim()\n : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\")\n .trim();\n}\n\nfunction truncatePrompt(value: string, maxLength = 360): string {\n if (value.length <= maxLength) return value;\n return `${value.slice(0, maxLength - 1).trimEnd()}…`;\n}\n\nfunction firstUserPrompt(threadData: unknown): string | null {\n const parsed = parseJson(threadData);\n if (parsed.status !== \"parsed\") return null;\n const messages = parsed.value.messages;\n if (!Array.isArray(messages)) return null;\n\n for (const entry of messages) {\n if (!entry || typeof entry !== \"object\") continue;\n const record = entry as Record<string, unknown>;\n const message =\n record.message && typeof record.message === \"object\"\n ? (record.message as Record<string, unknown>)\n : record;\n const role = typeof message.role === \"string\" ? message.role : \"\";\n if (role !== \"user\" && role !== \"human\") continue;\n const text = textFromPromptContent(message.content);\n if (text) return truncatePrompt(text);\n }\n return null;\n}\n\ninterface ThreadPromptRow {\n id?: unknown;\n preview?: unknown;\n thread_data?: unknown;\n}\n\nasync function hydrateRecentPrompts(\n rows: Array<Record<string, unknown>>,\n): Promise<RecentUsageMetric[]> {\n const threadIds = [\n ...new Set(\n rows\n .map((row) => nullableStringField(row, \"thread_id\"))\n .filter((value): value is string => Boolean(value)),\n ),\n ];\n const threads = new Map<string, ThreadPromptRow>();\n let threadQueryUnavailable = false;\n\n if (threadIds.length > 0) {\n try {\n const result = await getDbExec().execute({\n sql: `SELECT id, preview, thread_data FROM chat_threads WHERE id IN (${threadIds.map(() => \"?\").join(\", \")})`,\n args: threadIds,\n });\n for (const row of result.rows as ThreadPromptRow[]) {\n const id = typeof row.id === \"string\" ? row.id : \"\";\n if (id) threads.set(id, row);\n }\n } catch {\n threadQueryUnavailable = true;\n }\n }\n\n return rows.map((row) => {\n const threadId = nullableStringField(row, \"thread_id\");\n const thread = threadId ? threads.get(threadId) : undefined;\n const prompt = thread ? firstUserPrompt(thread.thread_data) : null;\n const preview =\n typeof thread?.preview === \"string\" ? thread.preview.trim() : \"\";\n const promptSource = prompt\n ? \"thread\"\n : preview\n ? \"thread-preview\"\n : threadQueryUnavailable && threadId\n ? \"unavailable\"\n : \"not-captured\";\n\n return {\n id: numberField(row, \"id\"),\n createdAt: numberField(row, \"created_at\"),\n ownerEmail: stringField(row, \"owner_email\"),\n app: stringField(row, \"app\") || \"unattributed\",\n label: stringField(row, \"label\") || \"chat\",\n model: stringField(row, \"model\") || \"unknown\",\n inputTokens: numberField(row, \"input_tokens\"),\n outputTokens: numberField(row, \"output_tokens\"),\n cacheReadTokens: numberField(row, \"cache_read_tokens\"),\n cacheWriteTokens: numberField(row, \"cache_write_tokens\"),\n costCents: numberField(row, \"cost_cents_x100\") / 100,\n prompt: prompt ?? (preview ? truncatePrompt(preview) : null),\n promptSource,\n threadId,\n runId: nullableStringField(row, \"run_id\"),\n taskId: nullableStringField(row, \"task_id\"),\n sourcePlatform: nullableStringField(row, \"source_platform\"),\n sourceId: nullableStringField(row, \"source_id\"),\n } satisfies RecentUsageMetric;\n });\n}\n\nfunction labelForKey(value: string): string {\n const trimmed = value.trim();\n return trimmed || \"Unattributed\";\n}\n\nfunction normalizeAppKey(value: string | null | undefined): string {\n const raw = (value ?? \"\").trim().toLowerCase();\n if (!raw) return \"unattributed\";\n return raw.replace(/^agent-native-/, \"\");\n}\n\nfunction envEmails(name: string): string[] {\n return (process.env[name] ?? \"\")\n .split(\",\")\n .map((value) => value.trim().toLowerCase())\n .filter(Boolean);\n}\n\nfunction isEnvAdmin(email: string): boolean {\n const normalized = email.trim().toLowerCase();\n return [\n ...envEmails(\"DISPATCH_ADMIN_EMAILS\"),\n ...envEmails(\"WORKSPACE_OWNER_EMAIL\"),\n ...envEmails(\"DISPATCH_DEFAULT_OWNER_EMAIL\"),\n ].includes(normalized);\n}\n\nasync function detectUsageEngineName(): Promise<string | null> {\n try {\n const stored = (await getSetting(\"agent-engine\")) as {\n engine?: string;\n } | null;\n if (isAgentEngineSettingConfigured(stored)) {\n return (stored as { engine: string }).engine;\n }\n if (stored && typeof stored.engine === \"string\") {\n const entry = getAgentEngineEntry(stored.engine);\n if (entry && isStoredEngineUsable(stored, entry)) {\n return stored.engine;\n }\n }\n\n const detectedFromUser = await detectEngineFromUserSecrets();\n if (detectedFromUser) return detectedFromUser.name;\n\n return detectEngineFromEnv()?.name ?? null;\n } catch {\n return null;\n }\n}\n\nasync function queryRows<T extends Record<string, unknown>>(\n sql: string,\n args: unknown[] = [],\n): Promise<T[]> {\n try {\n const result = await getDbExec().execute({ sql, args });\n return result.rows as T[];\n } catch {\n return [];\n }\n}\n\nasync function initializeUsageMetricsTable(sinceMs: number): Promise<void> {\n try {\n // Initializes token_usage on fresh deployments before the read-only\n // aggregate queries below. The fake owner avoids changing visible data.\n await getUsageSummary({ ownerEmail: \"__dispatch_metrics_init__\", sinceMs });\n } catch {\n // Metrics should still render an empty state if usage storage is locked,\n // stale, or unavailable; each aggregate read below is already best-effort.\n }\n}\n\nasync function getViewerOrgRole(\n orgId: string | null,\n email: string,\n): Promise<string | null> {\n if (!orgId) return null;\n const rows = await queryRows<{ role?: string }>(\n `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,\n [orgId, email.toLowerCase()],\n );\n const role = rows[0]?.role;\n return typeof role === \"string\" ? role : null;\n}\n\nasync function listOrgMembers(orgId: string | null): Promise<MemberRecord[]> {\n if (!orgId) return [];\n const rows = await queryRows<Record<string, unknown>>(\n `SELECT email, role, joined_at AS joined_at FROM org_members WHERE org_id = ? ORDER BY joined_at ASC`,\n [orgId],\n );\n return rows\n .map((row) => ({\n email: stringField(row, \"email\").trim(),\n role: stringField(row, \"role\") || null,\n joinedAt: nullableNumberField(row, \"joined_at\"),\n }))\n .filter((member) => member.email);\n}\n\nfunction usageScope(\n sinceMs: number,\n memberEmails: string[],\n): { where: string; args: unknown[] } {\n if (memberEmails.length === 0) {\n return { where: \"created_at >= ?\", args: [sinceMs] };\n }\n const placeholders = memberEmails.map(() => \"?\").join(\", \");\n return {\n where: `created_at >= ? AND LOWER(owner_email) IN (${placeholders})`,\n args: [sinceMs, ...memberEmails.map((email) => email.toLowerCase())],\n };\n}\n\nfunction threadScope(\n sinceMs: number,\n memberEmails: string[],\n): { where: string; args: unknown[] } {\n if (memberEmails.length === 0) {\n return { where: \"updated_at >= ?\", args: [sinceMs] };\n }\n const placeholders = memberEmails.map(() => \"?\").join(\", \");\n return {\n where: `updated_at >= ? AND LOWER(owner_email) IN (${placeholders})`,\n args: [sinceMs, ...memberEmails.map((email) => email.toLowerCase())],\n };\n}\n\nfunction ownerScope(\n sinceMs: number,\n ownerEmail: string,\n): {\n where: string;\n args: unknown[];\n} {\n return {\n where: \"created_at >= ? AND LOWER(owner_email) = ?\",\n args: [sinceMs, ownerEmail.toLowerCase()],\n };\n}\n\nfunction ownerThreadScope(\n sinceMs: number,\n ownerEmail: string,\n): {\n where: string;\n args: unknown[];\n} {\n return {\n where: \"updated_at >= ? AND LOWER(owner_email) = ?\",\n args: [sinceMs, ownerEmail.toLowerCase()],\n };\n}\n\nfunction workspaceAppCreationScope(\n sinceMs: number,\n orgId: string | null,\n memberEmails: string[],\n): { where: string; args: unknown[] } {\n const filters = [\"created_at >= ?\", \"action = ?\"];\n const args: unknown[] = [sinceMs, \"workspace-app.pending\"];\n\n if (orgId) {\n filters.push(\"org_id = ?\");\n args.push(orgId);\n }\n if (memberEmails.length > 0) {\n filters.push(\n `LOWER(owner_email) IN (${memberEmails.map(() => \"?\").join(\", \")})`,\n );\n args.push(...memberEmails.map((email) => email.toLowerCase()));\n }\n\n return { where: filters.join(\" AND \"), args };\n}\n\nfunction bucketFromRow(row: Record<string, unknown>): UsageMetricBucket {\n const key = stringField(row, \"k\");\n return {\n key,\n label: labelForKey(key),\n costCents: numberField(row, \"cost_x100\") / 100,\n calls: numberField(row, \"calls\"),\n chatCalls: numberField(row, \"chat_calls\"),\n inputTokens: numberField(row, \"input_tokens\"),\n outputTokens: numberField(row, \"output_tokens\"),\n cacheReadTokens: numberField(row, \"cache_read_tokens\"),\n cacheWriteTokens: numberField(row, \"cache_write_tokens\"),\n activeUsers: numberField(row, \"active_users\"),\n lastActiveAt: nullableNumberField(row, \"last_active_at\"),\n };\n}\n\nasync function usageBuckets(\n columnExpression: string,\n where: string,\n args: unknown[],\n limit: number,\n): Promise<UsageMetricBucket[]> {\n const rows = await queryRows<Record<string, unknown>>(\n `SELECT ${columnExpression} AS k,\n COALESCE(SUM(cost_cents_x100), 0) AS cost_x100,\n COUNT(*) AS calls,\n SUM(CASE WHEN label = 'chat' THEN 1 ELSE 0 END) AS chat_calls,\n COALESCE(SUM(input_tokens), 0) AS input_tokens,\n COALESCE(SUM(output_tokens), 0) AS output_tokens,\n COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,\n COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,\n COUNT(DISTINCT owner_email) AS active_users,\n MAX(created_at) AS last_active_at\n FROM token_usage\n WHERE ${where}\n GROUP BY ${columnExpression}\n ORDER BY cost_x100 DESC\n LIMIT ?`,\n [...args, limit],\n );\n return rows.map(bucketFromRow);\n}\n\nasync function loadChatStats(\n where: string,\n args: unknown[],\n): Promise<Map<string, ChatStats>> {\n const rows = await queryRows<Record<string, unknown>>(\n `SELECT owner_email AS owner_email,\n COUNT(*) AS threads,\n COALESCE(SUM(message_count), 0) AS messages,\n MAX(updated_at) AS last_chat_at\n FROM chat_threads\n WHERE ${where}\n GROUP BY owner_email`,\n args,\n );\n return new Map(\n rows.map((row) => [\n stringField(row, \"owner_email\"),\n {\n threads: numberField(row, \"threads\"),\n messages: numberField(row, \"messages\"),\n lastChatAt: nullableNumberField(row, \"last_chat_at\"),\n },\n ]),\n );\n}\n\nasync function assertCanViewMetrics(viewScope: UsageMetricsScope): Promise<{\n viewerEmail: string;\n orgId: string | null;\n role: string | null;\n}> {\n const viewerEmail = currentOwnerEmail();\n const orgId = currentOrgId();\n const role = await getViewerOrgRole(orgId, viewerEmail);\n if (\n viewScope === \"me\" ||\n isEnvAdmin(viewerEmail) ||\n role === \"owner\" ||\n role === \"admin\"\n ) {\n return { viewerEmail, orgId, role };\n }\n if (!orgId) {\n return { viewerEmail, orgId, role };\n }\n throw new ForbiddenError(\n \"Only organization owners and admins can view workspace usage metrics.\",\n );\n}\n\nexport async function listDispatchUsageMetrics(input: {\n sinceDays?: number;\n scope?: UsageMetricsScope;\n userEmail?: string | null;\n}): Promise<DispatchUsageMetrics> {\n const viewScope: UsageMetricsScope =\n input.scope === \"me\" ? \"me\" : \"workspace\";\n const { viewerEmail, orgId, role } = await assertCanViewMetrics(viewScope);\n const sinceDays = Math.max(1, Math.min(365, input.sinceDays ?? 30));\n const sinceMs = Date.now() - sinceDays * DAY_MS;\n const billing = usageBillingForEngine(await detectUsageEngineName());\n\n await initializeUsageMetricsTable(sinceMs);\n\n const rawMembers =\n viewScope === \"me\"\n ? [{ email: viewerEmail, role, joinedAt: null }]\n : orgId\n ? await listOrgMembers(orgId)\n : [{ email: viewerEmail, role: null, joinedAt: null }];\n const members =\n viewScope === \"workspace\" && orgId && rawMembers.length === 0\n ? [{ email: viewerEmail, role, joinedAt: null }]\n : rawMembers;\n const requestedUserEmail = input.userEmail?.trim() || null;\n const selectedUserEmail =\n viewScope === \"me\"\n ? viewerEmail\n : requestedUserEmail\n ? (members.find(\n (member) =>\n member.email.toLowerCase() === requestedUserEmail.toLowerCase(),\n )?.email ?? null)\n : null;\n if (viewScope === \"workspace\" && requestedUserEmail && !selectedUserEmail) {\n throw new ForbiddenError(\n \"The selected user is not available in this workspace.\",\n );\n }\n const memberEmails = selectedUserEmail\n ? [selectedUserEmail]\n : members.map((member) => member.email);\n const memberByEmail = new Map(\n members.map((member) => [member.email.toLowerCase(), member]),\n );\n const usage = selectedUserEmail\n ? ownerScope(sinceMs, selectedUserEmail)\n : usageScope(sinceMs, memberEmails);\n const threads = selectedUserEmail\n ? ownerThreadScope(sinceMs, selectedUserEmail)\n : threadScope(sinceMs, memberEmails);\n\n const workspaceAppCreation = workspaceAppCreationScope(\n sinceMs,\n orgId,\n memberEmails,\n );\n\n const [\n apps,\n totalsRows,\n byApp,\n byUserBase,\n byLabel,\n byModel,\n chatStats,\n workspaceAppCreationRows,\n ] = await Promise.all([\n listWorkspaceApps({ includeAgentCards: false }),\n queryRows<Record<string, unknown>>(\n `SELECT\n COALESCE(SUM(cost_cents_x100), 0) AS cost_x100,\n COUNT(*) AS calls,\n SUM(CASE WHEN label = 'chat' THEN 1 ELSE 0 END) AS chat_calls,\n COALESCE(SUM(input_tokens), 0) AS input_tokens,\n COALESCE(SUM(output_tokens), 0) AS output_tokens,\n COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,\n COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,\n COUNT(DISTINCT owner_email) AS active_users\n FROM token_usage\n WHERE ${usage.where}`,\n usage.args,\n ),\n usageBuckets(\n `COALESCE(NULLIF(app, ''), 'unattributed')`,\n usage.where,\n usage.args,\n 20,\n ),\n usageBuckets(\"owner_email\", usage.where, usage.args, 50),\n usageBuckets(\n `COALESCE(NULLIF(label, ''), 'chat')`,\n usage.where,\n usage.args,\n 20,\n ),\n usageBuckets(\n `COALESCE(NULLIF(model, ''), 'unknown')`,\n usage.where,\n usage.args,\n 20,\n ),\n loadChatStats(threads.where, threads.args),\n queryRows<Record<string, unknown>>(\n `SELECT owner_email, actor, target_id, created_at\n FROM dispatch_audit_events\n WHERE ${workspaceAppCreation.where}\n ORDER BY created_at ASC`,\n workspaceAppCreation.args,\n ),\n ]);\n\n const topAppRows = await queryRows<Record<string, unknown>>(\n `SELECT owner_email AS owner_email,\n COALESCE(NULLIF(app, ''), 'unattributed') AS app,\n COALESCE(SUM(cost_cents_x100), 0) AS cost_x100\n FROM token_usage\n WHERE ${usage.where}\n GROUP BY owner_email, COALESCE(NULLIF(app, ''), 'unattributed')\n ORDER BY owner_email ASC, cost_x100 DESC`,\n usage.args,\n );\n const topAppByUser = new Map<string, string>();\n for (const row of topAppRows) {\n const email = stringField(row, \"owner_email\");\n if (!topAppByUser.has(email)) {\n topAppByUser.set(email, stringField(row, \"app\"));\n }\n }\n\n const byUserMap = new Map<string, UserUsageMetric>();\n for (const bucket of byUserBase) {\n const ownerEmail = bucket.key;\n const stats = chatStats.get(ownerEmail) ?? {\n threads: 0,\n messages: 0,\n lastChatAt: null,\n };\n const member = memberByEmail.get(ownerEmail.toLowerCase());\n byUserMap.set(ownerEmail, {\n ...bucket,\n ownerEmail,\n chatThreads: stats.threads,\n chatMessages: stats.messages,\n lastChatAt: stats.lastChatAt,\n topApp: topAppByUser.get(ownerEmail) ?? null,\n role: member?.role ?? null,\n });\n }\n for (const [ownerEmail, stats] of chatStats) {\n if (byUserMap.has(ownerEmail)) continue;\n const member = memberByEmail.get(ownerEmail.toLowerCase());\n byUserMap.set(ownerEmail, {\n key: ownerEmail,\n label: ownerEmail,\n ownerEmail,\n costCents: 0,\n calls: 0,\n chatCalls: 0,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n activeUsers: 1,\n lastActiveAt: stats.lastChatAt,\n chatThreads: stats.threads,\n chatMessages: stats.messages,\n lastChatAt: stats.lastChatAt,\n topApp: null,\n role: member?.role ?? null,\n });\n }\n\n const dayRows = await queryRows<Record<string, unknown>>(\n `SELECT created_at, owner_email, label, input_tokens, output_tokens,\n cache_read_tokens, cache_write_tokens, cost_cents_x100\n FROM token_usage\n WHERE ${usage.where}\n ORDER BY created_at ASC`,\n usage.args,\n );\n const dailyMap = new Map<\n string,\n { costX100: number; calls: number; chatCalls: number; users: Set<string> }\n >();\n const monthlyByUserMap = new Map<\n string,\n Omit<MonthlyUserUsageMetric, \"credits\">\n >();\n for (const row of dayRows) {\n const date = new Date(numberField(row, \"created_at\"))\n .toISOString()\n .slice(0, 10);\n const month = date.slice(0, 7);\n const ownerEmail = stringField(row, \"owner_email\");\n const current = dailyMap.get(date) ?? {\n costX100: 0,\n calls: 0,\n chatCalls: 0,\n users: new Set<string>(),\n };\n current.costX100 += numberField(row, \"cost_cents_x100\");\n current.calls += 1;\n if (stringField(row, \"label\") === \"chat\") current.chatCalls += 1;\n current.users.add(ownerEmail);\n dailyMap.set(date, current);\n\n const monthlyKey = `${ownerEmail}\\u0000${month}`;\n const monthly = monthlyByUserMap.get(monthlyKey) ?? {\n month,\n ownerEmail,\n costCents: 0,\n calls: 0,\n chatCalls: 0,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n };\n monthly.costCents += numberField(row, \"cost_cents_x100\") / 100;\n monthly.calls += 1;\n if (stringField(row, \"label\") === \"chat\") monthly.chatCalls += 1;\n monthly.inputTokens += numberField(row, \"input_tokens\");\n monthly.outputTokens += numberField(row, \"output_tokens\");\n monthly.cacheReadTokens += numberField(row, \"cache_read_tokens\");\n monthly.cacheWriteTokens += numberField(row, \"cache_write_tokens\");\n monthlyByUserMap.set(monthlyKey, monthly);\n }\n const daily = [...dailyMap.entries()]\n .map(([date, value]) => ({\n date,\n costCents: value.costX100 / 100,\n calls: value.calls,\n chatCalls: value.chatCalls,\n activeUsers: value.users.size,\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n const monthlyByUser = [...monthlyByUserMap.values()]\n .map((row) => ({\n ...row,\n credits: builderCreditsFromCostCents(row.costCents),\n }))\n .sort(\n (a, b) =>\n a.month.localeCompare(b.month) ||\n a.ownerEmail.localeCompare(b.ownerEmail),\n );\n\n const workspaceAppCreationMap = new Map<\n string,\n { month: string; ownerEmail: string; count: number; appIds: Set<string> }\n >();\n for (const row of workspaceAppCreationRows) {\n const month = new Date(numberField(row, \"created_at\"))\n .toISOString()\n .slice(0, 7);\n const ownerEmail =\n stringField(row, \"owner_email\") || stringField(row, \"actor\");\n if (!ownerEmail) continue;\n const key = `${ownerEmail}\\u0000${month}`;\n const current = workspaceAppCreationMap.get(key) ?? {\n month,\n ownerEmail,\n count: 0,\n appIds: new Set<string>(),\n };\n current.count += 1;\n const appId = stringField(row, \"target_id\");\n if (appId) current.appIds.add(appId);\n workspaceAppCreationMap.set(key, current);\n }\n const workspaceAppCreationsByUserMonth = [...workspaceAppCreationMap.values()]\n .map(({ appIds, ...row }) => ({\n ...row,\n appIds: [...appIds].sort(),\n }))\n .sort(\n (a, b) =>\n a.month.localeCompare(b.month) ||\n a.ownerEmail.localeCompare(b.ownerEmail),\n );\n\n const recentRows = await queryRows<Record<string, unknown>>(\n `SELECT id, created_at, owner_email, app, label, model,\n input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,\n cost_cents_x100, thread_id, run_id, task_id, source_platform, source_id\n FROM token_usage\n WHERE ${usage.where}\n ORDER BY created_at DESC\n LIMIT 50`,\n usage.args,\n );\n const recent = await hydrateRecentPrompts(recentRows);\n\n const appUsageByKey = new Map(\n byApp.map((bucket) => [normalizeAppKey(bucket.key), bucket]),\n );\n const accessUsers = members.length || byUserMap.size;\n const accessModel =\n viewScope === \"me\" ? \"solo\" : orgId ? \"workspace\" : \"solo\";\n const accessLabel =\n viewScope === \"me\"\n ? \"Your account\"\n : orgId\n ? \"Workspace members\"\n : \"Signed-in users\";\n const appAccess = apps.map((app) => {\n const usageBucket = appUsageByKey.get(normalizeAppKey(app.id));\n return {\n id: app.id,\n name: app.name,\n path: app.path,\n status: app.status,\n statusLabel: app.statusLabel,\n isDispatch: app.isDispatch,\n accessModel,\n accessLabel,\n accessUsers,\n usersWithUsage: usageBucket?.activeUsers ?? 0,\n usageCalls: usageBucket?.calls ?? 0,\n chatCalls: usageBucket?.chatCalls ?? 0,\n costCents: usageBucket?.costCents ?? 0,\n lastActiveAt: usageBucket?.lastActiveAt ?? null,\n } satisfies AppAccessMetric;\n });\n\n const totals = totalsRows[0] ?? {};\n const chatThreadTotals = [...chatStats.values()].reduce(\n (acc, value) => ({\n threads: acc.threads + value.threads,\n messages: acc.messages + value.messages,\n }),\n { threads: 0, messages: 0 },\n );\n\n return {\n billing,\n viewScope,\n selectedUserEmail,\n availableUsers: members\n .map(({ email, role }) => ({ email, role }))\n .sort((a, b) => a.email.localeCompare(b.email)),\n sinceMs,\n sinceDays,\n generatedAt: Date.now(),\n access: {\n viewerEmail,\n orgId,\n role,\n scope: orgId ? \"organization\" : \"solo\",\n totalUsers: accessUsers,\n },\n totals: {\n costCents: numberField(totals, \"cost_x100\") / 100,\n calls: numberField(totals, \"calls\"),\n chatCalls: numberField(totals, \"chat_calls\"),\n inputTokens: numberField(totals, \"input_tokens\"),\n outputTokens: numberField(totals, \"output_tokens\"),\n cacheReadTokens: numberField(totals, \"cache_read_tokens\"),\n cacheWriteTokens: numberField(totals, \"cache_write_tokens\"),\n activeUsers: numberField(totals, \"active_users\"),\n chatThreads: chatThreadTotals.threads,\n chatMessages: chatThreadTotals.messages,\n workspaceApps: apps.filter((app) => !app.isDispatch).length,\n },\n byApp,\n byUser: [...byUserMap.values()].sort((a, b) => {\n if (b.costCents !== a.costCents) return b.costCents - a.costCents;\n return (b.lastActiveAt ?? 0) - (a.lastActiveAt ?? 0);\n }),\n byLabel,\n byModel,\n daily,\n monthlyByUser,\n workspaceAppCreationsByUserMonth,\n appAccess,\n recent,\n };\n}\n"]}
|
|
@@ -23,6 +23,7 @@ const INITIAL_TOOL_NAMES = [
|
|
|
23
23
|
"upsert-destination",
|
|
24
24
|
"list-dreams",
|
|
25
25
|
"start-workspace-app-creation",
|
|
26
|
+
"list-dispatch-usage-metrics",
|
|
26
27
|
"list-available-workspace-templates",
|
|
27
28
|
"provider-api-catalog",
|
|
28
29
|
"provider-api-docs",
|
|
@@ -38,6 +39,7 @@ export default createAgentChatPlugin({
|
|
|
38
39
|
connectorCatalog: [
|
|
39
40
|
"resolve-integration-source-context",
|
|
40
41
|
"start-workspace-app-creation",
|
|
42
|
+
"list-dispatch-usage-metrics",
|
|
41
43
|
],
|
|
42
44
|
},
|
|
43
45
|
// Without this, AGENT_ORG_ID is never set on agent action calls and every
|
|
@@ -85,6 +87,7 @@ Use the standard workspace primitives:
|
|
|
85
87
|
- Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
|
|
86
88
|
- Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.
|
|
87
89
|
- For broad provider searches, joins, classification, corpus counts, or absence claims, fetch every relevant page or an explicitly bounded cohort, stage/save large responses with stageAs/saveToFile/fetchAllPages, and reduce them with query-staged-dataset or run-code. Report source, filters, row counts, pagination, truncation, failed pages, and uncovered gaps.
|
|
90
|
+
- For Builder.io or AI credit spend, LLM usage by workspace member or month, or workspace app/Builder branch creation history, call list-dispatch-usage-metrics with scope=workspace and the requested sinceDays. Its monthlyByUser and workspaceAppCreationsByUserMonth fields are the authoritative shared-database result; do not ask for a user export or BigQuery schema.
|
|
88
91
|
|
|
89
92
|
When a user asks for something like a digest, reminder, routing rule, or saved behavior:
|
|
90
93
|
- First decide whether it should be a resource, a recurring job, a destination, or a delegated task.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-chat.js","sourceRoot":"","sources":["../../../src/server/plugins/agent-chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,qCAAqC,CAAC;AAE7C,MAAM,kBAAkB,GAAG;IACzB,aAAa;IACb,qBAAqB;IACrB,+BAA+B;IAC/B,uBAAuB;IACvB,SAAS;IACT,UAAU;IACV,0BAA0B;IAC1B,2BAA2B;IAC3B,2BAA2B;IAC3B,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,wBAAwB;IACxB,oBAAoB;IACpB,sBAAsB;IACtB,qBAAqB;IACrB,mBAAmB;IACnB,oBAAoB;IACpB,aAAa;IACb,8BAA8B;IAC9B,oCAAoC;IACpC,sBAAsB;IACtB,mBAAmB;IACnB,sBAAsB;IACtB,sBAAsB;IACtB,UAAU;CACX,CAAC;AAEF,eAAe,qBAAqB,CAAC;IACnC,KAAK,EAAE,UAAU;IACjB,qBAAqB,EAAE,IAAI;IAC3B,gBAAgB,EAAE,kBAAkB;IACpC,GAAG,EAAE;QACH,gBAAgB,EAAE;YAChB,oCAAoC;YACpC,8BAA8B;
|
|
1
|
+
{"version":3,"file":"agent-chat.js","sourceRoot":"","sources":["../../../src/server/plugins/agent-chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,qCAAqC,CAAC;AAE7C,MAAM,kBAAkB,GAAG;IACzB,aAAa;IACb,qBAAqB;IACrB,+BAA+B;IAC/B,uBAAuB;IACvB,SAAS;IACT,UAAU;IACV,0BAA0B;IAC1B,2BAA2B;IAC3B,2BAA2B;IAC3B,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,wBAAwB;IACxB,oBAAoB;IACpB,sBAAsB;IACtB,qBAAqB;IACrB,mBAAmB;IACnB,oBAAoB;IACpB,aAAa;IACb,8BAA8B;IAC9B,6BAA6B;IAC7B,oCAAoC;IACpC,sBAAsB;IACtB,mBAAmB;IACnB,sBAAsB;IACtB,sBAAsB;IACtB,UAAU;CACX,CAAC;AAEF,eAAe,qBAAqB,CAAC;IACnC,KAAK,EAAE,UAAU;IACjB,qBAAqB,EAAE,IAAI;IAC3B,gBAAgB,EAAE,kBAAkB;IACpC,GAAG,EAAE;QACH,gBAAgB,EAAE;YAChB,oCAAoC;YACpC,8BAA8B;YAC9B,6BAA6B;SAC9B;KACF;IACD,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC5B,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC,KAAK,CAAC;IACnB,CAAC;IACD,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,OAAO,EAAE,eAAe;IACxB,eAAe,EAAE,2BAA2B;IAC5C,sBAAsB,EAAE,CAAC,0BAA0B,CAAC;IACpD,aAAa,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE;IAC1C,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4EAoC4D;CAC3E,CAAC,CAAC","sourcesContent":["import { getOrgContext } from \"@agent-native/core/org\";\nimport { createAgentChatPlugin } from \"@agent-native/core/server\";\n\nimport { dispatchActions } from \"../../actions/index.js\";\nimport {\n workspaceAppActionRouteAuth,\n WORKSPACE_APPS_ACTION_PATH,\n} from \"../lib/workspace-app-action-auth.js\";\n\nconst INITIAL_TOOL_NAMES = [\n \"view-screen\",\n \"list-workspace-apps\",\n \"update-workspace-app-metadata\",\n \"list-connected-agents\",\n \"ask_app\",\n \"open_app\",\n \"list-workspace-resources\",\n \"create-workspace-resource\",\n \"update-workspace-resource\",\n \"import-agent\",\n \"import-agent-pack\",\n \"list-agent-pack\",\n \"connect-external-agent\",\n \"list-vault-secrets\",\n \"request-vault-secret\",\n \"create-vault-secret\",\n \"list-destinations\",\n \"upsert-destination\",\n \"list-dreams\",\n \"start-workspace-app-creation\",\n \"list-dispatch-usage-metrics\",\n \"list-available-workspace-templates\",\n \"provider-api-catalog\",\n \"provider-api-docs\",\n \"provider-api-request\",\n \"query-staged-dataset\",\n \"navigate\",\n];\n\nexport default createAgentChatPlugin({\n appId: \"dispatch\",\n durableBackgroundRuns: true,\n initialToolNames: INITIAL_TOOL_NAMES,\n mcp: {\n connectorCatalog: [\n \"resolve-integration-source-context\",\n \"start-workspace-app-creation\",\n \"list-dispatch-usage-metrics\",\n ],\n },\n // Without this, AGENT_ORG_ID is never set on agent action calls and every\n // row written through the frontend (vault secrets, destinations, workspace\n // resources) lands with org_id=NULL — breaking data isolation across orgs.\n resolveOrgId: async (event) => {\n const ctx = await getOrgContext(event);\n return ctx.orgId;\n },\n // Read actions directly from the package's own action map rather than from\n // a build-time-generated `.generated/actions-registry.ts` (the latter is a\n // template-only construct that the Vite plugin emits next to actions/).\n actions: dispatchActions,\n actionRouteAuth: workspaceAppActionRouteAuth,\n actionRoutePublicPaths: [WORKSPACE_APPS_ACTION_PATH],\n codeExecution: { production: \"sandboxed\" },\n systemPrompt: `You are the central dispatch for this workspace.\n\nDefault posture:\n- Treat Slack and Telegram as shared entrypoints into the workspace.\n- Heavily delegate domain work to specialized agents through A2A when another app owns the job.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Prefer replying in the current external thread unless the user explicitly asks you to send to a saved destination.\n\nUse the standard workspace primitives:\n- Read and update resources like AGENTS.md, LEARNINGS.md, jobs/*.md, agents/*.md, and remote-agents/*.json when appropriate.\n- Use recurring jobs for scheduled behavior.\n- Use custom agent profiles in agents/*.md for local spawned work and remote-agents/*.json for remote A2A apps.\n- For a Claude-style Markdown or JSON agent setup, use import-agent so the\n profile is normalized into agents/<slug>.md. Never import credentials, hooks,\n shell commands, or local environment settings.\n- For a folder of agent instructions, references, or skills, use\n import-agent-pack. It preserves the text files under agents/<slug>/ and\n creates the normalized profile alongside them. Use list-agent-pack to inspect\n the pack before editing or handing it off to an app.\n- For an existing HTTP/A2A endpoint, use connect-external-agent and let the\n normal A2A/MCP connection flow handle authentication.\n- You receive a compact available-apps block with sibling workspace app names and descriptions. Use it to pick the right A2A target, and call list-connected-agents or tool-search only when you need fresh details.\n- Hosted/connected A2A neighbors such as Analytics and Content come from the available-apps context or list-connected-agents. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.\n- When answering whether a mounted workspace app exposes an agent card or A2A endpoint, call list-workspace-apps with includeAgentCards=true. If you have not requested that probe, absence of agent-card fields means unchecked, not unavailable.\n- When creating a new workspace app, create a separate app under apps/<app-id> with apps/<app-id>/package.json including a concise generated description, mount it at /<app-id>, use relative /<app-id> links, never hardcode localhost or dev ports, use shadcn/ui with @tabler/icons-react rather than lucide-react, and ensure the React Router client entry preserves APP_BASE_PATH/VITE_APP_BASE_PATH via appBasePath(). There is no separate workspace app registry to edit.\n- When a user asks to rename an existing workspace app or change its Dispatch title/description, call update-workspace-app-metadata with the existing appId from list-workspace-apps. This is a metadata-only edit — never call start-workspace-app-creation, which creates a new app and Builder branch.\n- When an explicit app-creation request already includes a source brief or a concrete repeatable workflow, call start-workspace-app-creation without asking non-blocking product or UX questions. Choose recommended defaults, let the Builder handoff record assumptions, and ask only for authorization, credentials, a destructive action, an ambiguous target workspace, or a genuinely missing workflow.\n- If the chat template is used, treat it as scaffolding only: the finished app must be branded as the requested app with its own home screen/navigation/package metadata/manifest, and must not leave visible \"Chat\", \"Starter\", \"Blank app\", or \"New app\" UI behind.\n- Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.\n- Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.\n- For broad provider searches, joins, classification, corpus counts, or absence claims, fetch every relevant page or an explicitly bounded cohort, stage/save large responses with stageAs/saveToFile/fetchAllPages, and reduce them with query-staged-dataset or run-code. Report source, filters, row counts, pagination, truncation, failed pages, and uncovered gaps.\n- For Builder.io or AI credit spend, LLM usage by workspace member or month, or workspace app/Builder branch creation history, call list-dispatch-usage-metrics with scope=workspace and the requested sinceDays. Its monthlyByUser and workspaceAppCreationsByUserMonth fields are the authoritative shared-database result; do not ask for a user export or BigQuery schema.\n\nWhen a user asks for something like a digest, reminder, routing rule, or saved behavior:\n- First decide whether it should be a resource, a recurring job, a destination, or a delegated task.\n- Keep responses concise and operational.\n- Avoid inventing integrations or destinations that are not configured yet.`,\n});\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.15",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"tailwind-merge": "^3.5.0",
|
|
88
88
|
"vaul": "^1.1.2",
|
|
89
89
|
"zod": "^4.3.6",
|
|
90
|
-
"@agent-native/toolkit": "0.16.
|
|
90
|
+
"@agent-native/toolkit": "0.16.11"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@react-router/dev": "^8.1.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
102
102
|
"vite": "8.1.0",
|
|
103
103
|
"vitest": "^4.1.5",
|
|
104
|
-
"@agent-native/core": "0.
|
|
104
|
+
"@agent-native/core": "0.170.0"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
107
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -18,6 +18,7 @@ describe("dispatch action registry", () => {
|
|
|
18
18
|
"create-workspace-app-embed-session",
|
|
19
19
|
);
|
|
20
20
|
expect(dispatchActions).toHaveProperty("read-slack-thread-context");
|
|
21
|
+
expect(dispatchActions).toHaveProperty("list-dispatch-usage-metrics");
|
|
21
22
|
expect(dispatchActions).toHaveProperty(
|
|
22
23
|
"get-workspace-resource-effective-context",
|
|
23
24
|
);
|
|
@@ -56,4 +57,20 @@ describe("dispatch action registry", () => {
|
|
|
56
57
|
"A2A delegation",
|
|
57
58
|
);
|
|
58
59
|
});
|
|
60
|
+
|
|
61
|
+
it("exposes shared usage metrics as an authenticated read", () => {
|
|
62
|
+
const action = dispatchActions["list-dispatch-usage-metrics"];
|
|
63
|
+
|
|
64
|
+
expect(action.readOnly).toBe(true);
|
|
65
|
+
expect(action.parallelSafe).toBe(true);
|
|
66
|
+
expect(action.publicAgent).toEqual({
|
|
67
|
+
expose: true,
|
|
68
|
+
readOnly: true,
|
|
69
|
+
requiresAuth: true,
|
|
70
|
+
isConsequential: false,
|
|
71
|
+
});
|
|
72
|
+
expect(action.tool.description).toContain(
|
|
73
|
+
"workspaceAppCreationsByUserMonth",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
59
76
|
});
|
|
@@ -5,7 +5,7 @@ import { listDispatchUsageMetrics } from "../server/lib/usage-metrics-store.js";
|
|
|
5
5
|
|
|
6
6
|
export default defineAction({
|
|
7
7
|
description:
|
|
8
|
-
"Get personal or workspace LLM usage, spend or Builder.io credit spend, app attribution, prompt previews, and recent activity metrics for Dispatch.",
|
|
8
|
+
"Get personal or workspace LLM usage, spend or Builder.io credit spend, app attribution, prompt previews, and recent activity metrics for Dispatch. Workspace results also include monthlyByUser credit rows and workspaceAppCreationsByUserMonth rows from the shared token_usage and Dispatch audit tables.",
|
|
9
9
|
schema: z.object({
|
|
10
10
|
sinceDays: z.coerce
|
|
11
11
|
.number()
|
|
@@ -26,5 +26,13 @@ export default defineAction({
|
|
|
26
26
|
.describe("Optional workspace member email to filter all usage to."),
|
|
27
27
|
}),
|
|
28
28
|
http: { method: "GET" },
|
|
29
|
+
readOnly: true,
|
|
30
|
+
parallelSafe: true,
|
|
31
|
+
publicAgent: {
|
|
32
|
+
expose: true,
|
|
33
|
+
readOnly: true,
|
|
34
|
+
requiresAuth: true,
|
|
35
|
+
isConsequential: false,
|
|
36
|
+
},
|
|
29
37
|
run: async (args) => listDispatchUsageMetrics(args),
|
|
30
38
|
});
|
|
@@ -29,6 +29,8 @@ const clientState = vi.hoisted(() => ({
|
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
32
|
+
chatModelSelectionStorageKey: (namespace: string) =>
|
|
33
|
+
`agent-native:chat-models:selection:${namespace}`,
|
|
32
34
|
navigateWithAgentChatViewTransition: (
|
|
33
35
|
navigate: unknown,
|
|
34
36
|
path: string,
|
|
@@ -161,7 +163,7 @@ describe("DispatchControlPlane", () => {
|
|
|
161
163
|
container.querySelector('[data-placeholder="Ask Dispatch anything..."]'),
|
|
162
164
|
).not.toBeNull();
|
|
163
165
|
expect(clientState.useChatModels).toHaveBeenCalledWith({
|
|
164
|
-
storageKey: "dispatch",
|
|
166
|
+
storageKey: "agent-native:chat-models:selection:dispatch",
|
|
165
167
|
});
|
|
166
168
|
expect(clientState.promptComposerProps).toMatchObject({
|
|
167
169
|
availableModels: [],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
chatModelSelectionStorageKey,
|
|
2
3
|
navigateWithAgentChatViewTransition,
|
|
3
4
|
useChatModels,
|
|
4
5
|
} from "@agent-native/core/client/agent-chat";
|
|
@@ -71,7 +72,7 @@ function CommandPanel() {
|
|
|
71
72
|
selectedEffort,
|
|
72
73
|
selectedEngine,
|
|
73
74
|
selectedModel,
|
|
74
|
-
} = useChatModels({ storageKey: "dispatch" });
|
|
75
|
+
} = useChatModels({ storageKey: chatModelSelectionStorageKey("dispatch") });
|
|
75
76
|
const navigate = useNavigate();
|
|
76
77
|
const promptSuggestions = [
|
|
77
78
|
t("dispatch.pages.suggestionOnboardingApp", {
|