@capybara-hq/kernel 0.1.0-rc.19 → 0.1.0-rc.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin-agents.controller.d.ts +28 -0
  3. package/dist/admin/admin-agents.controller.js +79 -0
  4. package/dist/admin/admin-connectors.controller.d.ts +29 -0
  5. package/dist/admin/admin-connectors.controller.js +79 -0
  6. package/dist/admin/admin-items.schemas.d.ts +42 -0
  7. package/dist/admin/admin-items.schemas.js +48 -0
  8. package/dist/admin/admin.controller.d.ts +11 -20
  9. package/dist/admin/admin.controller.js +28 -34
  10. package/dist/admin/admin.module.d.ts +4 -1
  11. package/dist/admin/admin.module.js +50 -4
  12. package/dist/admin/admin.schemas.d.ts +4 -0
  13. package/dist/admin/admin.schemas.js +4 -1
  14. package/dist/admin/audit.controller.d.ts +14 -0
  15. package/dist/admin/audit.controller.js +42 -0
  16. package/dist/admin/audit.schemas.d.ts +8 -0
  17. package/dist/admin/audit.schemas.js +17 -0
  18. package/dist/admin/health.service.d.ts +104 -0
  19. package/dist/admin/health.service.js +246 -0
  20. package/dist/admin/overview.controller.d.ts +31 -0
  21. package/dist/admin/overview.controller.js +78 -0
  22. package/dist/admin/overview.schemas.d.ts +8 -0
  23. package/dist/admin/overview.schemas.js +13 -0
  24. package/dist/admin/overview.service.d.ts +112 -0
  25. package/dist/admin/overview.service.js +223 -0
  26. package/dist/admin/platform-audit.service.d.ts +41 -0
  27. package/dist/admin/platform-audit.service.js +70 -0
  28. package/dist/admin/usage.service.d.ts +149 -0
  29. package/dist/admin/usage.service.js +321 -0
  30. package/dist/agents/admin-agents.d.ts +92 -0
  31. package/dist/agents/admin-agents.js +170 -0
  32. package/dist/agents/agents.module.d.ts +4 -3
  33. package/dist/agents/agents.module.js +7 -3
  34. package/dist/agents/agents.service.d.ts +41 -3
  35. package/dist/agents/agents.service.js +113 -27
  36. package/dist/agents/definition.d.ts +16 -0
  37. package/dist/agents/definition.js +32 -2
  38. package/dist/agents/org-templates.js +4 -0
  39. package/dist/background/launch.d.ts +1 -1
  40. package/dist/background/launch.js +3 -3
  41. package/dist/background/tasks.service.d.ts +35 -3
  42. package/dist/background/tasks.service.js +91 -36
  43. package/dist/catalog/catalog.module.js +8 -2
  44. package/dist/channels/channels.module.js +11 -2
  45. package/dist/channels/supervisor.d.ts +2 -0
  46. package/dist/channels/supervisor.js +4 -0
  47. package/dist/conversations/events.js +1 -1
  48. package/dist/conversations/titles.js +4 -8
  49. package/dist/db/kernel-schema.d.ts +8 -1
  50. package/dist/devices/devices.module.js +5 -2
  51. package/dist/errors.d.ts +22 -2
  52. package/dist/errors.js +22 -2
  53. package/dist/evals/evals.module.js +11 -2
  54. package/dist/extensions/permission-view.js +3 -3
  55. package/dist/http/api.module.js +10 -1
  56. package/dist/identity/settings.js +3 -2
  57. package/dist/identity/write-actor.d.ts +11 -0
  58. package/dist/identity/write-actor.js +7 -0
  59. package/dist/inbox/inbox.service.d.ts +2 -1
  60. package/dist/inbox/platform-inbox.service.d.ts +7 -1
  61. package/dist/inbox/platform-inbox.service.js +20 -2
  62. package/dist/infra/presence/presence.module.d.ts +6 -0
  63. package/dist/infra/presence/presence.module.js +22 -0
  64. package/dist/infra/presence/replica-presence.d.ts +87 -0
  65. package/dist/infra/presence/replica-presence.js +189 -0
  66. package/dist/kernel.module.js +3 -0
  67. package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
  68. package/dist/lifecycle/kernel-lifecycle.js +17 -3
  69. package/dist/local-devices/local-device-gateway.d.ts +6 -0
  70. package/dist/local-devices/local-device-gateway.js +12 -0
  71. package/dist/overrides.d.ts +7 -1
  72. package/dist/overrides.js +4 -0
  73. package/dist/platform/exclusions.js +15 -0
  74. package/dist/platform/operation.d.ts +1 -1
  75. package/dist/platform/operation.js +1 -1
  76. package/dist/platform/ops/admin-items.ops.d.ts +37 -0
  77. package/dist/platform/ops/admin-items.ops.js +278 -0
  78. package/dist/platform/ops/admin.ops.d.ts +10 -10
  79. package/dist/platform/ops/admin.ops.js +111 -42
  80. package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
  81. package/dist/platform/ops/agent-skills.ops.js +290 -7
  82. package/dist/platform/ops/inbox.ops.js +7 -4
  83. package/dist/platform/ops/platform.ops.js +1 -1
  84. package/dist/platform/ops/skills.ops.js +1 -57
  85. package/dist/platform/platform-ops.module.d.ts +2 -1
  86. package/dist/platform/platform-ops.module.js +5 -0
  87. package/dist/platform/route-schemas.js +8 -0
  88. package/dist/roles/gateway-role.d.ts +4 -0
  89. package/dist/roles/gateway-role.js +8 -0
  90. package/dist/runtime/run-jobs.js +4 -2
  91. package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
  92. package/dist/sandbox/sandbox-file-reader.js +98 -0
  93. package/dist/skills/agent-skills.controller.d.ts +48 -0
  94. package/dist/skills/agent-skills.controller.js +131 -0
  95. package/dist/skills/agent-skills.service.d.ts +262 -0
  96. package/dist/skills/agent-skills.service.js +996 -0
  97. package/dist/skills/catalog.d.ts +8 -6
  98. package/dist/skills/catalog.js +8 -4
  99. package/dist/skills/changes.controller.d.ts +5 -22
  100. package/dist/skills/changes.controller.js +7 -55
  101. package/dist/skills/changes.service.d.ts +69 -115
  102. package/dist/skills/changes.service.js +249 -599
  103. package/dist/skills/conversation-catalog.d.ts +53 -0
  104. package/dist/skills/conversation-catalog.js +149 -0
  105. package/dist/skills/invocation-keys.d.ts +1 -1
  106. package/dist/skills/invocation-keys.js +1 -1
  107. package/dist/skills/package.d.ts +0 -5
  108. package/dist/skills/package.js +0 -14
  109. package/dist/skills/skills.controller.js +1 -10
  110. package/dist/skills/skills.module.js +15 -2
  111. package/dist/skills/skills.schemas.d.ts +11 -15
  112. package/dist/skills/skills.schemas.js +27 -20
  113. package/dist/skills/skills.service.d.ts +37 -30
  114. package/dist/skills/skills.service.js +107 -59
  115. package/dist/skills/usable.d.ts +4 -3
  116. package/dist/skills/usable.js +13 -4
  117. package/dist/tools/admin-connectors.d.ts +69 -0
  118. package/dist/tools/admin-connectors.js +135 -0
  119. package/dist/tools/builtin-names.d.ts +9 -14
  120. package/dist/tools/builtin-names.js +13 -22
  121. package/dist/tools/builtins/builtin-tools.d.ts +2 -3
  122. package/dist/tools/builtins/builtin-tools.js +21 -26
  123. package/dist/tools/builtins/sandbox/bash.js +2 -2
  124. package/dist/tools/builtins/skill-load.js +1 -0
  125. package/dist/tools/connectors.controller.d.ts +5 -1
  126. package/dist/tools/connectors.controller.js +17 -3
  127. package/dist/tools/connectors.service.d.ts +20 -1
  128. package/dist/tools/connectors.service.js +61 -0
  129. package/dist/tools/legacy-keys.js +0 -1
  130. package/dist/tools/org-tool-switches.d.ts +3 -2
  131. package/dist/tools/org-tool-switches.js +4 -4
  132. package/dist/tools/policy.d.ts +7 -9
  133. package/dist/tools/policy.js +11 -22
  134. package/dist/tools/tools.module.js +3 -0
  135. package/package.json +1 -1
  136. package/src/migrations/0109_audit_platform_idx.sql +5 -0
  137. package/src/migrations/0110_usage_time_idx.sql +8 -0
  138. package/src/migrations/0111_agent_skill_sharing.sql +22 -0
  139. package/src/migrations/0112_skill_share_requests.sql +4 -0
  140. package/dist/tools/builtins/skill-write.d.ts +0 -59
  141. package/dist/tools/builtins/skill-write.js +0 -244
@@ -0,0 +1,321 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable, Optional } from "@nestjs/common";
11
+ import { sql } from "kysely";
12
+ import { ApiError } from "../errors.js";
13
+ import { SettingsService } from "../identity/settings.js";
14
+ import { CacheService } from "../infra/cache/cache.service.js";
15
+ import { Cacheable } from "../infra/cache/cacheable.decorator.js";
16
+ import { KernelDb } from "../infra/database/kernel-db.js";
17
+ import { runOutsideTransaction, TransactionHost } from "../infra/database/transaction-host.js";
18
+ import { Transactional } from "../infra/database/transactional.decorator.js";
19
+ import { costOf, PricesService, priceKey } from "../models/prices.js";
20
+ /** Seam token of the kernel's own tests: a shorter statement timeout and a fixed clock. */
21
+ export const ADMIN_USAGE_TIMINGS = Symbol("ADMIN_USAGE_TIMINGS");
22
+ /** Days in the window: today and the 6 days before. */
23
+ export const USAGE_WINDOW_DAYS = 7;
24
+ /** Orgs listed by name; the rest are summed in `otherOrgs`. */
25
+ export const USAGE_TOP_ORGS = 10;
26
+ /** Seconds one computed week stays cached (shared by every replica, Redis). */
27
+ export const USAGE_CACHE_TTL = 300;
28
+ const DEFAULT_STATEMENT_TIMEOUT_MS = 10_000;
29
+ /** SQLSTATE `query_canceled`: what `statement_timeout` raises. */
30
+ const QUERY_CANCELED = "57014";
31
+ /** SQLSTATE `invalid_parameter_value`: a zone `Intl` knows but PG's tzdata does not. */
32
+ const INVALID_PARAMETER_VALUE = "22023";
33
+ const sqlState = (err) => err?.code;
34
+ /** `YYYY-MM-DD` of `at` in `tz`. */
35
+ export function dateIn(tz, at) {
36
+ const parts = new Intl.DateTimeFormat("en-US", {
37
+ timeZone: tz,
38
+ year: "numeric",
39
+ month: "2-digit",
40
+ day: "2-digit",
41
+ }).formatToParts(new Date(at));
42
+ const get = (type) => parts.find((p) => p.type === type)?.value ?? "";
43
+ return `${get("year")}-${get("month")}-${get("day")}`;
44
+ }
45
+ /** The calendar date `days` after `date` (`YYYY-MM-DD`; date arithmetic, no time zone involved). */
46
+ export function addDays(date, days) {
47
+ const [y, m, d] = date.split("-").map(Number);
48
+ return new Date(Date.UTC(y, m - 1, d + days)).toISOString().slice(0, 10);
49
+ }
50
+ const newCost = () => ({ priced: 0, anyPriced: false, unpriced: false });
51
+ const addCost = (acc, cost, tokens) => {
52
+ if (cost !== null) {
53
+ acc.priced += cost;
54
+ acc.anyPriced = true;
55
+ }
56
+ else if (tokens > 0) {
57
+ acc.unpriced = true;
58
+ }
59
+ };
60
+ /** Rounded to 1e-6 (float noise); null when tokens were used and none was priced. */
61
+ const costValue = (acc) => !acc.anyPriced && acc.unpriced ? null : Math.round(acc.priced * 1e6) / 1e6;
62
+ /**
63
+ * The admin overview's usage block (spec §4.3, §5.2, §5.4). Three grouped queries over a bounded
64
+ * window (`conversation_events_usage_idx`, `runs_created_idx`, `model_usage_time_idx`) in one
65
+ * read-only transaction with a statement timeout; priced here with the model price table. The
66
+ * answer is cached in Redis for 300 s per time zone and first day, so every replica serves the
67
+ * same answer and a zone is recomputed at most once per 5 minutes (prices or currency changed in
68
+ * between show after the TTL). Aggregates only: no account, agent or conversation, no content.
69
+ */
70
+ let AdminUsageService = class AdminUsageService {
71
+ db;
72
+ txHost;
73
+ cacheService;
74
+ prices;
75
+ settings;
76
+ statementTimeoutMs;
77
+ now;
78
+ constructor(db, txHost, cacheService, prices, settings, timings = {}) {
79
+ this.db = db;
80
+ this.txHost = txHost;
81
+ this.cacheService = cacheService;
82
+ this.prices = prices;
83
+ this.settings = settings;
84
+ this.statementTimeoutMs = timings.statementTimeoutMs ?? DEFAULT_STATEMENT_TIMEOUT_MS;
85
+ this.now = timings.now ?? Date.now;
86
+ }
87
+ /**
88
+ * The week ending today in `tz` (an IANA zone the caller validated, `IanaTimeZone`; normalized to
89
+ * its canonical spelling, which the answer echoes and the cache key holds). Throws
90
+ * `admin.usage_unavailable` (503) when the aggregate ran past its statement timeout, and
91
+ * `validation_failed` (400) for a zone PG does not know.
92
+ *
93
+ * Never call it inside a transaction: it leaves the caller's context on purpose (inside one,
94
+ * `@Cacheable` would be bypassed and the read-only transaction would hold a second connection).
95
+ */
96
+ async week(tz) {
97
+ const zone = new Intl.DateTimeFormat("en-US", { timeZone: tz }).resolvedOptions().timeZone;
98
+ const firstDay = addDays(dateIn(zone, this.now()), -(USAGE_WINDOW_DAYS - 1));
99
+ return runOutsideTransaction(() => this.cachedWeek(zone, firstDay));
100
+ }
101
+ /** The cache key holds the zone and the first day: a new day starts a new entry. */
102
+ async cachedWeek(tz, firstDay) {
103
+ try {
104
+ return await this.aggregate(tz, firstDay);
105
+ }
106
+ catch (err) {
107
+ if (sqlState(err) === QUERY_CANCELED) {
108
+ throw new ApiError("admin.usage_unavailable", "The usage aggregate took too long; try again later");
109
+ }
110
+ if (sqlState(err) === INVALID_PARAMETER_VALUE) {
111
+ throw new ApiError("validation_failed", `Unknown time zone ${tz}; pass an IANA time zone name, such as Asia/Shanghai`);
112
+ }
113
+ throw err;
114
+ }
115
+ }
116
+ /** The window's queries, in their own read-only transaction (the timeout must not leak into a caller's). */
117
+ async aggregate(tz, firstDay) {
118
+ const db = this.db.db;
119
+ // a SET takes no bind parameters; the value is an integer from config or the test seam
120
+ await sql
121
+ .raw(`SET LOCAL statement_timeout = ${Math.max(1, Math.trunc(this.statementTimeoutMs))}`)
122
+ .execute(db);
123
+ const endDay = addDays(firstDay, USAGE_WINDOW_DAYS);
124
+ // the zone's midnights as instants: PG applies the zone's rules, DST days included
125
+ const bounds = await sql `
126
+ SELECT (${firstDay}::date)::timestamp AT TIME ZONE ${tz} AS "from",
127
+ (${endDay}::date)::timestamp AT TIME ZONE ${tz} AS "to"
128
+ `.execute(db);
129
+ const { from, to } = bounds.rows[0];
130
+ const q = usageQueries(tz, from, to);
131
+ const tokens = await q.tokens.execute(db);
132
+ const runs = await q.runs.execute(db);
133
+ const embeddings = await q.embeddings.execute(db);
134
+ const [prices, settings] = await Promise.all([this.prices.table(), this.settings.get()]);
135
+ const result = summarize({
136
+ firstDay,
137
+ tokens: tokens.rows,
138
+ runs: runs.rows,
139
+ embeddings: embeddings.rows,
140
+ prices,
141
+ });
142
+ const orgIds = result.top.map((o) => o.orgId);
143
+ const names = orgIds.length === 0
144
+ ? []
145
+ : await this.db
146
+ .selectFrom("orgs")
147
+ .select(["id", "name"])
148
+ .where("id", "in", orgIds)
149
+ .execute();
150
+ const nameOf = new Map(names.map((n) => [n.id, n.name]));
151
+ return {
152
+ from: from.toISOString(),
153
+ to: to.toISOString(),
154
+ tz,
155
+ currency: settings.priceCurrency,
156
+ days: result.days,
157
+ costPartial: result.costPartial,
158
+ orgs: result.top.map((o) => ({ ...o, name: nameOf.get(o.orgId) ?? o.orgId })),
159
+ otherOrgs: result.otherOrgs,
160
+ };
161
+ }
162
+ };
163
+ __decorate([
164
+ Cacheable({ key: "admin-usage:#{0}:#{1}", ttl: USAGE_CACHE_TTL })
165
+ ], AdminUsageService.prototype, "cachedWeek", null);
166
+ __decorate([
167
+ Transactional({ propagation: "requires_new", accessMode: "read only" })
168
+ ], AdminUsageService.prototype, "aggregate", null);
169
+ AdminUsageService = __decorate([
170
+ Injectable(),
171
+ __param(0, Inject(KernelDb)),
172
+ __param(1, Inject(TransactionHost)),
173
+ __param(2, Inject(CacheService)),
174
+ __param(3, Inject(PricesService)),
175
+ __param(4, Inject(SettingsService)),
176
+ __param(5, Optional()),
177
+ __param(5, Inject(ADMIN_USAGE_TIMINGS))
178
+ ], AdminUsageService);
179
+ export { AdminUsageService };
180
+ /**
181
+ * The three grouped queries over `[from, to)` (spec §5.4), each on its time index:
182
+ * `conversation_events_usage_idx`, `runs_created_idx`, `model_usage_time_idx`. Rows are bounded by
183
+ * days × orgs (× models), not by events. Days are calendar days in `tz` (`YYYY-MM-DD` text, so no
184
+ * driver turns them into local-time dates).
185
+ */
186
+ export function usageQueries(tz, from, to) {
187
+ // a malformed payload value (a string, null) counts 0 and a fraction is rounded, so one bad
188
+ // event cannot fail the aggregate for its 7 days
189
+ const tokenSum = (field) => sql `coalesce(sum(CASE WHEN jsonb_typeof(e.payload->${sql.lit(field)}) = 'number'
190
+ THEN round((e.payload->>${sql.lit(field)})::numeric) END), 0)`;
191
+ const day = (col) => sql `to_char((${sql.ref(col)} AT TIME ZONE ${tz})::date, 'YYYY-MM-DD')`;
192
+ return {
193
+ // chat tokens: every `usage` event (steps, compaction, titles, web fetch), by day × org × model
194
+ tokens: sql `
195
+ SELECT ${day("e.created_at")} AS day, c.org_id,
196
+ coalesce(e.payload->>'provider', '') AS provider,
197
+ coalesce(e.payload->>'model', '') AS model,
198
+ ${tokenSum("inputTokens")} AS input,
199
+ ${tokenSum("outputTokens")} AS output,
200
+ ${tokenSum("cacheReadTokens")} AS cache_read,
201
+ ${tokenSum("cacheWriteTokens")} AS cache_write
202
+ FROM capybara.conversation_events e
203
+ JOIN capybara.conversations c ON c.id = e.conversation_id
204
+ WHERE e.type = 'usage' AND e.created_at >= ${from} AND e.created_at < ${to}
205
+ GROUP BY 1, 2, 3, 4
206
+ `,
207
+ // runs: every run (sub-agent and eval replay runs included, D6), by day × org
208
+ runs: sql `
209
+ SELECT ${day("r.created_at")} AS day, c.org_id, count(*) AS runs
210
+ FROM capybara.runs r
211
+ JOIN capybara.conversations c ON c.id = r.conversation_id
212
+ WHERE r.created_at >= ${from} AND r.created_at < ${to}
213
+ GROUP BY 1, 2
214
+ `,
215
+ // embeddings (`ctx.models.embed`), by day × org × model
216
+ embeddings: sql `
217
+ SELECT ${day("u.occurred_at")} AS day, u.org_id, u.provider, u.model,
218
+ coalesce(sum(u.input_tokens), 0) AS input
219
+ FROM capybara.model_usage u
220
+ WHERE u.occurred_at >= ${from} AND u.occurred_at < ${to}
221
+ GROUP BY 1, 2, 3, 4
222
+ `,
223
+ };
224
+ }
225
+ /** Folds the grouped rows into days and orgs, pricing each (provider, model) group. */
226
+ function summarize(input) {
227
+ const days = new Map();
228
+ for (let i = 0; i < USAGE_WINDOW_DAYS; i++) {
229
+ const date = addDays(input.firstDay, i);
230
+ days.set(date, {
231
+ day: {
232
+ date,
233
+ runs: 0,
234
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, embedding: 0 },
235
+ },
236
+ cost: newCost(),
237
+ });
238
+ }
239
+ const orgs = new Map();
240
+ const org = (orgId) => {
241
+ let o = orgs.get(orgId);
242
+ if (!o) {
243
+ o = { orgId, runs: 0, tokens: 0, cost: newCost() };
244
+ orgs.set(orgId, o);
245
+ }
246
+ return o;
247
+ };
248
+ let costPartial = false;
249
+ const price = (provider, model) => input.prices.get(priceKey(provider, model));
250
+ for (const r of input.tokens) {
251
+ const d = days.get(r.day);
252
+ if (!d)
253
+ continue; // outside the window (cannot happen: bounded by the same midnights)
254
+ const usage = {
255
+ inputTokens: Number(r.input),
256
+ outputTokens: Number(r.output),
257
+ cacheReadTokens: Number(r.cache_read),
258
+ cacheWriteTokens: Number(r.cache_write),
259
+ };
260
+ const total = usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
261
+ const cost = costOf(usage, price(r.provider, r.model));
262
+ if (cost === null && total > 0)
263
+ costPartial = true;
264
+ d.day.tokens.input += usage.inputTokens;
265
+ d.day.tokens.output += usage.outputTokens;
266
+ d.day.tokens.cacheRead += usage.cacheReadTokens;
267
+ d.day.tokens.cacheWrite += usage.cacheWriteTokens;
268
+ addCost(d.cost, cost, total);
269
+ const o = org(r.org_id);
270
+ o.tokens += total;
271
+ addCost(o.cost, cost, total);
272
+ }
273
+ for (const r of input.embeddings) {
274
+ const d = days.get(r.day);
275
+ if (!d)
276
+ continue;
277
+ const n = Number(r.input);
278
+ const cost = costOf({ inputTokens: n, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, price(r.provider, r.model));
279
+ if (cost === null && n > 0)
280
+ costPartial = true;
281
+ d.day.tokens.embedding += n;
282
+ addCost(d.cost, cost, n);
283
+ const o = org(r.org_id);
284
+ o.tokens += n;
285
+ addCost(o.cost, cost, n);
286
+ }
287
+ for (const r of input.runs) {
288
+ const d = days.get(r.day);
289
+ if (!d)
290
+ continue;
291
+ const n = Number(r.runs);
292
+ d.day.runs += n;
293
+ org(r.org_id).runs += n;
294
+ }
295
+ const ranked = [...orgs.values()].sort((a, b) => b.tokens - a.tokens ||
296
+ b.runs - a.runs ||
297
+ (a.orgId < b.orgId ? -1 : a.orgId > b.orgId ? 1 : 0));
298
+ const rest = ranked.slice(USAGE_TOP_ORGS);
299
+ const restCost = newCost();
300
+ for (const o of rest) {
301
+ restCost.priced += o.cost.priced;
302
+ restCost.anyPriced ||= o.cost.anyPriced;
303
+ restCost.unpriced ||= o.cost.unpriced;
304
+ }
305
+ return {
306
+ days: [...days.values()].map((d) => ({ ...d.day, cost: costValue(d.cost) })),
307
+ costPartial,
308
+ top: ranked.slice(0, USAGE_TOP_ORGS).map((o) => ({
309
+ orgId: o.orgId,
310
+ runs: o.runs,
311
+ tokens: o.tokens,
312
+ cost: costValue(o.cost),
313
+ })),
314
+ otherOrgs: {
315
+ count: rest.length,
316
+ runs: rest.reduce((s, o) => s + o.runs, 0),
317
+ tokens: rest.reduce((s, o) => s + o.tokens, 0),
318
+ cost: costValue(restCost),
319
+ },
320
+ };
321
+ }
@@ -0,0 +1,92 @@
1
+ import { KernelDb } from "../infra/database/kernel-db.js";
2
+ import { AgentLookup, type AgentVisibility } from "./agent-lookup.js";
3
+ import type { AgentDefinition } from "./definition.js";
4
+ import type { AgentAvatar } from "./profile.js";
5
+ /** One row of the super admin's cross-org agent list (admin agents spec §3.2). */
6
+ export interface AdminAgentRow {
7
+ id: string;
8
+ orgId: string;
9
+ orgName: string;
10
+ /** The current version's name. */
11
+ name: string;
12
+ avatar: AgentAvatar;
13
+ ownerAccountId: string;
14
+ ownerName: string;
15
+ needsReassignment: boolean;
16
+ visibility: AgentVisibility;
17
+ createdFrom: {
18
+ templateId: string;
19
+ name: string | null;
20
+ } | null;
21
+ updatedAt: Date;
22
+ }
23
+ /**
24
+ * The super admin's read-only agent detail (admin agents spec §3.3): the instructions in full,
25
+ * quick actions by label only; never conversations, runs or credentials.
26
+ */
27
+ export interface AdminAgentDetail extends AdminAgentRow {
28
+ profile: {
29
+ description: string | null;
30
+ quickActions: {
31
+ label: string;
32
+ }[];
33
+ };
34
+ memberIds: string[] | null;
35
+ /** The current version's definition, instructions included. */
36
+ definition: AgentDefinition;
37
+ /** How many versions the agent has. */
38
+ versions: number;
39
+ createdAt: Date;
40
+ /**
41
+ * The skills it uses by name: the selected org skills that resolve in the org (a deleted one is
42
+ * left out) and its own skills (`scope: "agent"`). Never a skill body.
43
+ */
44
+ skills: {
45
+ id: string;
46
+ name: string;
47
+ scope: "org" | "agent";
48
+ }[];
49
+ /**
50
+ * The org's live connectors it can use by name (tools default-on): every one but those whose
51
+ * every tool it turns off. Never auth, credentials or a member's state.
52
+ */
53
+ connectors: AdminAgentConnector[];
54
+ }
55
+ export interface AdminAgentConnector {
56
+ id: string;
57
+ name: string;
58
+ slug: string;
59
+ status: "active" | "disabled";
60
+ }
61
+ export interface AdminAgentListQuery {
62
+ orgId?: string | undefined;
63
+ /** Case-insensitive substring of the name; LIKE wildcards match literally. */
64
+ q?: string | undefined;
65
+ /** The last id of the previous page. */
66
+ after?: string | undefined;
67
+ limit: number;
68
+ }
69
+ /**
70
+ * Cross-org agent reads for the super admin (admin agents spec §3.2, §3.3): every org's live
71
+ * agents, private ones included. Agents of a deleted org and soft-deleted agents are never listed
72
+ * and read as `agent.not_found`. The caller checks the super admin (`@SuperAdmin()`).
73
+ */
74
+ export declare class AdminAgents {
75
+ private readonly db;
76
+ private readonly lookup;
77
+ constructor(db: KernelDb, lookup: AgentLookup);
78
+ /** Live agents of live orgs by id, `limit` per page; `next` is the last id when more follow. */
79
+ list(q: AdminAgentListQuery): Promise<{
80
+ agents: AdminAgentRow[];
81
+ next: string | null;
82
+ }>;
83
+ /** One live agent of a live org; otherwise `agent.not_found`. */
84
+ get(agentId: string): Promise<AdminAgentDetail>;
85
+ /**
86
+ * The org's live connectors by name, minus those the agent turns off as a whole: `tools: "all"`,
87
+ * or a list naming every tool the connector has (a connector without tools stays listed).
88
+ */
89
+ private connectorsOf;
90
+ /** The list columns of live agents in live orgs. */
91
+ private rows;
92
+ }
@@ -0,0 +1,170 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { sql } from "kysely";
12
+ import { escapeLike } from "../conversations/conversations.service.js";
13
+ import { ApiError } from "../errors.js";
14
+ import { KernelDb } from "../infra/database/kernel-db.js";
15
+ import { selectedSkills } from "../skills/usable.js";
16
+ import { AgentLookup } from "./agent-lookup.js";
17
+ const notFound = () => new ApiError("agent.not_found", "Agent not found");
18
+ /**
19
+ * Cross-org agent reads for the super admin (admin agents spec §3.2, §3.3): every org's live
20
+ * agents, private ones included. Agents of a deleted org and soft-deleted agents are never listed
21
+ * and read as `agent.not_found`. The caller checks the super admin (`@SuperAdmin()`).
22
+ */
23
+ let AdminAgents = class AdminAgents {
24
+ db;
25
+ lookup;
26
+ constructor(db, lookup) {
27
+ this.db = db;
28
+ this.lookup = lookup;
29
+ }
30
+ /** Live agents of live orgs by id, `limit` per page; `next` is the last id when more follow. */
31
+ async list(q) {
32
+ let query = this.rows();
33
+ if (q.orgId !== undefined)
34
+ query = query.where("agents.org_id", "=", q.orgId);
35
+ if (q.q !== undefined && q.q !== "") {
36
+ query = query.where(sql `agent_versions.definition->>'name' ILIKE ${`%${escapeLike(q.q)}%`} ESCAPE '\\'`);
37
+ }
38
+ if (q.after !== undefined)
39
+ query = query.where("agents.id", ">", q.after);
40
+ const rows = await query
41
+ .orderBy("agents.id")
42
+ .limit(q.limit + 1)
43
+ .execute();
44
+ const page = rows.slice(0, q.limit).map(toRow);
45
+ return { agents: page, next: rows.length > q.limit ? (page.at(-1)?.id ?? null) : null };
46
+ }
47
+ /** One live agent of a live org; otherwise `agent.not_found`. */
48
+ async get(agentId) {
49
+ const row = await this.rows().where("agents.id", "=", agentId).executeTakeFirst();
50
+ if (!row)
51
+ throw notFound();
52
+ const [view, versions] = await Promise.all([
53
+ this.lookup.view(agentId),
54
+ this.db
55
+ .selectFrom("agent_versions")
56
+ .select((eb) => eb.fn.countAll().as("n"))
57
+ .where("agent_id", "=", agentId)
58
+ .executeTakeFirstOrThrow(),
59
+ ]);
60
+ const [skills, connectors] = await Promise.all([
61
+ selectedSkills(this.db.db, {
62
+ orgId: row.org_id,
63
+ agentId,
64
+ orgSkills: view.definition.skills?.orgSkills ?? [],
65
+ }),
66
+ this.connectorsOf(row.org_id, view.definition.disabledTools),
67
+ ]);
68
+ return {
69
+ ...toRow(row),
70
+ skills: skills
71
+ .map((s) => ({ id: s.id, name: s.name, scope: s.scope }))
72
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
73
+ connectors,
74
+ profile: {
75
+ description: view.profile.description === "" ? null : view.profile.description,
76
+ quickActions: view.profile.quickActions.map((a) => ({ label: a.title })),
77
+ },
78
+ memberIds: view.memberIds,
79
+ definition: view.definition,
80
+ versions: Number(versions.n),
81
+ createdAt: view.createdAt,
82
+ };
83
+ }
84
+ /**
85
+ * The org's live connectors by name, minus those the agent turns off as a whole: `tools: "all"`,
86
+ * or a list naming every tool the connector has (a connector without tools stays listed).
87
+ */
88
+ async connectorsOf(orgId, disabled) {
89
+ const rows = await this.db
90
+ .selectFrom("connectors as c")
91
+ .select(["c.id", "c.name", "c.slug", "c.status"])
92
+ .select(sql `coalesce((SELECT array_agg(t.name) FROM capybara.connector_tools t
93
+ WHERE t.connector_id = c.id), '{}')`.as("tools"))
94
+ .where("c.org_id", "=", orgId)
95
+ .where("c.deleted_at", "is", null)
96
+ .orderBy("c.name")
97
+ .orderBy("c.id")
98
+ .execute();
99
+ const offByConnector = new Map();
100
+ for (const r of disabled ?? []) {
101
+ if (r.kind !== "connector")
102
+ continue;
103
+ const prev = offByConnector.get(r.connectorId);
104
+ if (prev === "all" || r.tools === "all")
105
+ offByConnector.set(r.connectorId, "all");
106
+ else
107
+ offByConnector.set(r.connectorId, new Set([...(prev ?? []), ...r.tools]));
108
+ }
109
+ const allOff = (id, tools) => {
110
+ const off = offByConnector.get(id);
111
+ if (off === undefined)
112
+ return false;
113
+ if (off === "all")
114
+ return true;
115
+ return tools.length > 0 && tools.every((t) => off.has(t));
116
+ };
117
+ return rows
118
+ .filter((r) => !allOff(r.id, r.tools))
119
+ .map((r) => ({ id: r.id, name: r.name, slug: r.slug, status: r.status }));
120
+ }
121
+ /** The list columns of live agents in live orgs. */
122
+ rows() {
123
+ return this.db
124
+ .selectFrom("agents")
125
+ .innerJoin("orgs", "orgs.id", "agents.org_id")
126
+ .innerJoin("accounts", "accounts.id", "agents.owner_account_id")
127
+ .innerJoin("agent_versions", (j) => j
128
+ .onRef("agent_versions.agent_id", "=", "agents.id")
129
+ .onRef("agent_versions.version", "=", "agents.current_version"))
130
+ .select([
131
+ "agents.id",
132
+ "agents.org_id",
133
+ "orgs.name as org_name",
134
+ sql `agent_versions.definition->>'name'`.as("name"),
135
+ "agents.profile_avatar",
136
+ "agents.owner_account_id",
137
+ "accounts.display_name as owner_name",
138
+ "agents.needs_reassignment",
139
+ "agents.visibility",
140
+ "agents.created_from_template_id",
141
+ "agents.created_from_template_name",
142
+ "agents.updated_at",
143
+ ])
144
+ .where("agents.deleted_at", "is", null)
145
+ .where("orgs.deleted_at", "is", null);
146
+ }
147
+ };
148
+ AdminAgents = __decorate([
149
+ Injectable(),
150
+ __param(0, Inject(KernelDb)),
151
+ __param(1, Inject(AgentLookup))
152
+ ], AdminAgents);
153
+ export { AdminAgents };
154
+ function toRow(r) {
155
+ return {
156
+ id: r.id,
157
+ orgId: r.org_id,
158
+ orgName: r.org_name,
159
+ name: r.name,
160
+ avatar: (r.profile_avatar ?? null),
161
+ ownerAccountId: r.owner_account_id,
162
+ ownerName: r.owner_name,
163
+ needsReassignment: r.needs_reassignment,
164
+ visibility: r.visibility,
165
+ createdFrom: r.created_from_template_id === null
166
+ ? null
167
+ : { templateId: r.created_from_template_id, name: r.created_from_template_name ?? null },
168
+ updatedAt: r.updated_at,
169
+ };
170
+ }
@@ -2,9 +2,10 @@
2
2
  * Agents: the one {@link AgentsService} (definitions, versions, visibility; writes under the org
3
3
  * row lock), the agent templates (the plugin ones, and the org's own: `OrgAgentTemplates`),
4
4
  * creating agents from templates (`AgentsFromTemplate`) with its pending connector request seam
5
- * (`PendingConnectorRequests`, ruling P4-7), and binding pending connectors
6
- * (`PendingConnectorsService`, P4-6). Reads for a viewer are {@link AgentLookupModule}'s
7
- * (re-exported), which the conversations and the skill invoker use without this module.
5
+ * (`PendingConnectorRequests`, ruling P4-7), binding pending connectors
6
+ * (`PendingConnectorsService`, P4-6) and the super admin's cross-org reads (`AdminAgents`). Reads
7
+ * for a viewer are {@link AgentLookupModule}'s (re-exported), which the conversations and the
8
+ * skill invoker use without this module.
8
9
  */
9
10
  export declare class AgentsModule {
10
11
  }
@@ -13,6 +13,7 @@ import { OrgLockModule } from "../orgs/org-lock.js";
13
13
  import { ProvidersModule } from "../runtime/providers.js";
14
14
  import { SkillsModule } from "../skills/skills.module.js";
15
15
  import { ToolsModule } from "../tools/tools.module.js";
16
+ import { AdminAgents } from "./admin-agents.js";
16
17
  import { AgentLookupModule } from "./agent-lookup.js";
17
18
  import { AgentProfileService } from "./agent-profile.service.js";
18
19
  import { AgentsService } from "./agents.service.js";
@@ -25,9 +26,10 @@ import { AgentTemplatesService } from "./templates.js";
25
26
  * Agents: the one {@link AgentsService} (definitions, versions, visibility; writes under the org
26
27
  * row lock), the agent templates (the plugin ones, and the org's own: `OrgAgentTemplates`),
27
28
  * creating agents from templates (`AgentsFromTemplate`) with its pending connector request seam
28
- * (`PendingConnectorRequests`, ruling P4-7), and binding pending connectors
29
- * (`PendingConnectorsService`, P4-6). Reads for a viewer are {@link AgentLookupModule}'s
30
- * (re-exported), which the conversations and the skill invoker use without this module.
29
+ * (`PendingConnectorRequests`, ruling P4-7), binding pending connectors
30
+ * (`PendingConnectorsService`, P4-6) and the super admin's cross-org reads (`AdminAgents`). Reads
31
+ * for a viewer are {@link AgentLookupModule}'s (re-exported), which the conversations and the
32
+ * skill invoker use without this module.
31
33
  */
32
34
  let AgentsModule = class AgentsModule {
33
35
  };
@@ -49,6 +51,7 @@ AgentsModule = __decorate([
49
51
  ],
50
52
  providers: [
51
53
  AgentsService,
54
+ AdminAgents,
52
55
  AgentProfileService,
53
56
  AgentTemplatesService,
54
57
  OrgAgentTemplates,
@@ -59,6 +62,7 @@ AgentsModule = __decorate([
59
62
  exports: [
60
63
  AgentLookupModule,
61
64
  AgentsService,
65
+ AdminAgents,
62
66
  AgentProfileService,
63
67
  AgentTemplatesService,
64
68
  OrgAgentTemplates,