@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.
- package/bootstrap.json +25 -25
- package/dist/admin/admin-agents.controller.d.ts +28 -0
- package/dist/admin/admin-agents.controller.js +79 -0
- package/dist/admin/admin-connectors.controller.d.ts +29 -0
- package/dist/admin/admin-connectors.controller.js +79 -0
- package/dist/admin/admin-items.schemas.d.ts +42 -0
- package/dist/admin/admin-items.schemas.js +48 -0
- package/dist/admin/admin.controller.d.ts +11 -20
- package/dist/admin/admin.controller.js +28 -34
- package/dist/admin/admin.module.d.ts +4 -1
- package/dist/admin/admin.module.js +50 -4
- package/dist/admin/admin.schemas.d.ts +4 -0
- package/dist/admin/admin.schemas.js +4 -1
- package/dist/admin/audit.controller.d.ts +14 -0
- package/dist/admin/audit.controller.js +42 -0
- package/dist/admin/audit.schemas.d.ts +8 -0
- package/dist/admin/audit.schemas.js +17 -0
- package/dist/admin/health.service.d.ts +104 -0
- package/dist/admin/health.service.js +246 -0
- package/dist/admin/overview.controller.d.ts +31 -0
- package/dist/admin/overview.controller.js +78 -0
- package/dist/admin/overview.schemas.d.ts +8 -0
- package/dist/admin/overview.schemas.js +13 -0
- package/dist/admin/overview.service.d.ts +112 -0
- package/dist/admin/overview.service.js +223 -0
- package/dist/admin/platform-audit.service.d.ts +41 -0
- package/dist/admin/platform-audit.service.js +70 -0
- package/dist/admin/usage.service.d.ts +149 -0
- package/dist/admin/usage.service.js +321 -0
- package/dist/agents/admin-agents.d.ts +92 -0
- package/dist/agents/admin-agents.js +170 -0
- package/dist/agents/agents.module.d.ts +4 -3
- package/dist/agents/agents.module.js +7 -3
- package/dist/agents/agents.service.d.ts +41 -3
- package/dist/agents/agents.service.js +113 -27
- package/dist/agents/definition.d.ts +16 -0
- package/dist/agents/definition.js +32 -2
- package/dist/agents/org-templates.js +4 -0
- package/dist/background/launch.d.ts +1 -1
- package/dist/background/launch.js +3 -3
- package/dist/background/tasks.service.d.ts +35 -3
- package/dist/background/tasks.service.js +91 -36
- package/dist/catalog/catalog.module.js +8 -2
- package/dist/channels/channels.module.js +11 -2
- package/dist/channels/supervisor.d.ts +2 -0
- package/dist/channels/supervisor.js +4 -0
- package/dist/conversations/events.js +1 -1
- package/dist/conversations/titles.js +4 -8
- package/dist/db/kernel-schema.d.ts +8 -1
- package/dist/devices/devices.module.js +5 -2
- package/dist/errors.d.ts +22 -2
- package/dist/errors.js +22 -2
- package/dist/evals/evals.module.js +11 -2
- package/dist/extensions/permission-view.js +3 -3
- package/dist/http/api.module.js +10 -1
- package/dist/identity/settings.js +3 -2
- package/dist/identity/write-actor.d.ts +11 -0
- package/dist/identity/write-actor.js +7 -0
- package/dist/inbox/inbox.service.d.ts +2 -1
- package/dist/inbox/platform-inbox.service.d.ts +7 -1
- package/dist/inbox/platform-inbox.service.js +20 -2
- package/dist/infra/presence/presence.module.d.ts +6 -0
- package/dist/infra/presence/presence.module.js +22 -0
- package/dist/infra/presence/replica-presence.d.ts +87 -0
- package/dist/infra/presence/replica-presence.js +189 -0
- package/dist/kernel.module.js +3 -0
- package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
- package/dist/lifecycle/kernel-lifecycle.js +17 -3
- package/dist/local-devices/local-device-gateway.d.ts +6 -0
- package/dist/local-devices/local-device-gateway.js +12 -0
- package/dist/overrides.d.ts +7 -1
- package/dist/overrides.js +4 -0
- package/dist/platform/exclusions.js +15 -0
- package/dist/platform/operation.d.ts +1 -1
- package/dist/platform/operation.js +1 -1
- package/dist/platform/ops/admin-items.ops.d.ts +37 -0
- package/dist/platform/ops/admin-items.ops.js +278 -0
- package/dist/platform/ops/admin.ops.d.ts +10 -10
- package/dist/platform/ops/admin.ops.js +111 -42
- package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
- package/dist/platform/ops/agent-skills.ops.js +290 -7
- package/dist/platform/ops/inbox.ops.js +7 -4
- package/dist/platform/ops/platform.ops.js +1 -1
- package/dist/platform/ops/skills.ops.js +1 -57
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +8 -0
- package/dist/roles/gateway-role.d.ts +4 -0
- package/dist/roles/gateway-role.js +8 -0
- package/dist/runtime/run-jobs.js +4 -2
- package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
- package/dist/sandbox/sandbox-file-reader.js +98 -0
- package/dist/skills/agent-skills.controller.d.ts +48 -0
- package/dist/skills/agent-skills.controller.js +131 -0
- package/dist/skills/agent-skills.service.d.ts +262 -0
- package/dist/skills/agent-skills.service.js +996 -0
- package/dist/skills/catalog.d.ts +8 -6
- package/dist/skills/catalog.js +8 -4
- package/dist/skills/changes.controller.d.ts +5 -22
- package/dist/skills/changes.controller.js +7 -55
- package/dist/skills/changes.service.d.ts +69 -115
- package/dist/skills/changes.service.js +249 -599
- package/dist/skills/conversation-catalog.d.ts +53 -0
- package/dist/skills/conversation-catalog.js +149 -0
- package/dist/skills/invocation-keys.d.ts +1 -1
- package/dist/skills/invocation-keys.js +1 -1
- package/dist/skills/package.d.ts +0 -5
- package/dist/skills/package.js +0 -14
- package/dist/skills/skills.controller.js +1 -10
- package/dist/skills/skills.module.js +15 -2
- package/dist/skills/skills.schemas.d.ts +11 -15
- package/dist/skills/skills.schemas.js +27 -20
- package/dist/skills/skills.service.d.ts +37 -30
- package/dist/skills/skills.service.js +107 -59
- package/dist/skills/usable.d.ts +4 -3
- package/dist/skills/usable.js +13 -4
- package/dist/tools/admin-connectors.d.ts +69 -0
- package/dist/tools/admin-connectors.js +135 -0
- package/dist/tools/builtin-names.d.ts +9 -14
- package/dist/tools/builtin-names.js +13 -22
- package/dist/tools/builtins/builtin-tools.d.ts +2 -3
- package/dist/tools/builtins/builtin-tools.js +21 -26
- package/dist/tools/builtins/sandbox/bash.js +2 -2
- package/dist/tools/builtins/skill-load.js +1 -0
- package/dist/tools/connectors.controller.d.ts +5 -1
- package/dist/tools/connectors.controller.js +17 -3
- package/dist/tools/connectors.service.d.ts +20 -1
- package/dist/tools/connectors.service.js +61 -0
- package/dist/tools/legacy-keys.js +0 -1
- package/dist/tools/org-tool-switches.d.ts +3 -2
- package/dist/tools/org-tool-switches.js +4 -4
- package/dist/tools/policy.d.ts +7 -9
- package/dist/tools/policy.js +11 -22
- package/dist/tools/tools.module.js +3 -0
- package/package.json +1 -1
- package/src/migrations/0109_audit_platform_idx.sql +5 -0
- package/src/migrations/0110_usage_time_idx.sql +8 -0
- package/src/migrations/0111_agent_skill_sharing.sql +22 -0
- package/src/migrations/0112_skill_share_requests.sql +4 -0
- package/dist/tools/builtins/skill-write.d.ts +0 -59
- package/dist/tools/builtins/skill-write.js +0 -244
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { IanaTimeZone } from "../time-zone.js";
|
|
3
|
+
/** `GET /api/v1/admin/overview/usage`: the reader's zone; days are calendar days in it (spec §5.2). */
|
|
4
|
+
export const UsageQuery = z.object({
|
|
5
|
+
tz: IanaTimeZone.describe("The IANA time zone the days are counted in (the reader's zone), such as Asia/Shanghai."),
|
|
6
|
+
});
|
|
7
|
+
/** The admin overview's routes (admin overview spec §5.2): reads, only usage takes input. */
|
|
8
|
+
export const OVERVIEW_ROUTES = [
|
|
9
|
+
{ route: "GET /api/v1/admin/overview" },
|
|
10
|
+
{ route: "GET /api/v1/admin/overview/attention" },
|
|
11
|
+
{ route: "GET /api/v1/admin/overview/health" },
|
|
12
|
+
{ route: "GET /api/v1/admin/overview/usage", query: UsageQuery },
|
|
13
|
+
];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { LocalizedName, Logger } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { CatalogService } from "../catalog/catalog.service.js";
|
|
3
|
+
import { MarketplacesService } from "../catalog/marketplaces.service.js";
|
|
4
|
+
import { ExtensionAdmin } from "../extensions/admin-view.js";
|
|
5
|
+
import { ExtensionDisplayNames } from "../extensions/display-name.js";
|
|
6
|
+
import type { AdminExtensionView, DeploymentExtensionStatus } from "../extensions/permission-view.js";
|
|
7
|
+
import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
8
|
+
import { AccountsService } from "../identity/accounts.js";
|
|
9
|
+
import { PlatformInboxService } from "../inbox/platform-inbox.service.js";
|
|
10
|
+
import { OrgsService } from "../orgs/orgs.service.js";
|
|
11
|
+
import { ProviderRegistry, type ProviderStatus } from "../runtime/providers.js";
|
|
12
|
+
/** The most rows each attention list returns; the rest are counted in `more`. */
|
|
13
|
+
export declare const ATTENTION_LIST_MAX = 20;
|
|
14
|
+
/** The admin overview's numbers (`GET /api/v1/admin/overview`): counts only. */
|
|
15
|
+
export interface AdminOverviewStats {
|
|
16
|
+
orgs: number;
|
|
17
|
+
accounts: number;
|
|
18
|
+
/**
|
|
19
|
+
* What waits for a super admin: the platform inbox total plus the extensions, plugin updates and
|
|
20
|
+
* model providers of {@link AdminAttention} (each counted in full, not capped).
|
|
21
|
+
*/
|
|
22
|
+
attentionTotal: number;
|
|
23
|
+
/** An attention source could not be read: `attentionTotal` leaves its items out. */
|
|
24
|
+
partial: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Why an extension needs a look (spec §5.2), from its status and `configValid`: `config` (missing
|
|
28
|
+
* or invalid deployment config), `load_failed`, `approval` (permissions wait for confirmation),
|
|
29
|
+
* `dependency`, `conflict`, `converging` (replicas disagree, or a migration waits) and
|
|
30
|
+
* `legacy_format`. Never the raw replica detail.
|
|
31
|
+
*/
|
|
32
|
+
export type AttentionExtensionReason = "config" | "load_failed" | "approval" | "dependency" | "conflict" | "converging" | "legacy_format";
|
|
33
|
+
/** What is wrong with a model provider: its model list failed, or it lists no usable chat model. */
|
|
34
|
+
export type AttentionProviderProblem = "list_failed" | "no_chat_model";
|
|
35
|
+
/** An attention list a source failed to read (it is then empty, the others intact). */
|
|
36
|
+
export type AttentionSource = "extensions" | "updates" | "providers";
|
|
37
|
+
export interface AttentionExtension {
|
|
38
|
+
name: string;
|
|
39
|
+
displayName: LocalizedName | null;
|
|
40
|
+
icon: string | null;
|
|
41
|
+
status: DeploymentExtensionStatus;
|
|
42
|
+
reason: AttentionExtensionReason;
|
|
43
|
+
}
|
|
44
|
+
/** A platform plugin whose marketplace has a newer version than the newest imported one. */
|
|
45
|
+
export interface AttentionUpdate {
|
|
46
|
+
itemId: string;
|
|
47
|
+
name: string;
|
|
48
|
+
displayName: LocalizedName | null;
|
|
49
|
+
icon: string | null;
|
|
50
|
+
/** The newest imported version. */
|
|
51
|
+
version: string;
|
|
52
|
+
marketplaceVersion: string;
|
|
53
|
+
marketplaceId: string | null;
|
|
54
|
+
/** The marketplace's name; null without a marketplace (or when it was deleted). */
|
|
55
|
+
marketplaceName: string | null;
|
|
56
|
+
}
|
|
57
|
+
export interface AttentionProvider {
|
|
58
|
+
/** The model provider (its extension's plugin name). */
|
|
59
|
+
plugin: string;
|
|
60
|
+
displayName: LocalizedName | null;
|
|
61
|
+
icon: string | null;
|
|
62
|
+
problem: AttentionProviderProblem;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `GET /api/v1/admin/overview/attention` (spec §5.2, §9): what needs a super admin besides the
|
|
66
|
+
* inbox, each list at most {@link ATTENTION_LIST_MAX} rows with the rest counted in `more`, and
|
|
67
|
+
* `unavailable` naming each source that could not be read. Reason categories only: never an error
|
|
68
|
+
* text, a replica detail or a config value.
|
|
69
|
+
*/
|
|
70
|
+
export interface AdminAttention {
|
|
71
|
+
extensions: AttentionExtension[];
|
|
72
|
+
updates: AttentionUpdate[];
|
|
73
|
+
providers: AttentionProvider[];
|
|
74
|
+
more: Record<AttentionSource, number>;
|
|
75
|
+
unavailable: AttentionSource[];
|
|
76
|
+
}
|
|
77
|
+
/** The reason an extension is listed, or null when it needs nothing. */
|
|
78
|
+
export declare function extensionReason(e: Pick<AdminExtensionView, "status" | "configValid">): AttentionExtensionReason | null;
|
|
79
|
+
/** The problem of a provider in the platform view, or null (`org_configured` is not one). */
|
|
80
|
+
export declare function providerProblem(p: ProviderStatus): AttentionProviderProblem | null;
|
|
81
|
+
/**
|
|
82
|
+
* The admin overview (spec §4.1, §5.2): its numbers and the 待处理 lists, read live from the
|
|
83
|
+
* sources the admin pages read (`ExtensionAdmin`, the catalog, the provider registry's cached
|
|
84
|
+
* lists). Each source is read on its own: a failing one is reported in `unavailable`. Reads are not
|
|
85
|
+
* audited (decision D5) and not cached further (every source is PG or already Redis-cached).
|
|
86
|
+
*/
|
|
87
|
+
export declare class AdminOverviewService {
|
|
88
|
+
private readonly extensions;
|
|
89
|
+
private readonly catalog;
|
|
90
|
+
private readonly registry;
|
|
91
|
+
private readonly displayNames;
|
|
92
|
+
private readonly icons;
|
|
93
|
+
private readonly inbox;
|
|
94
|
+
private readonly orgs;
|
|
95
|
+
private readonly accounts;
|
|
96
|
+
private readonly marketplaces;
|
|
97
|
+
private readonly log;
|
|
98
|
+
constructor(extensions: ExtensionAdmin, catalog: CatalogService, registry: ProviderRegistry, displayNames: ExtensionDisplayNames, icons: PluginIcons, inbox: PlatformInboxService, orgs: OrgsService, accounts: AccountsService, marketplaces: MarketplacesService, log: Logger);
|
|
99
|
+
stats(): Promise<AdminOverviewStats>;
|
|
100
|
+
attention(): Promise<AdminAttention>;
|
|
101
|
+
/**
|
|
102
|
+
* The extensions needing a look, but not one pending approval whose plugin waits in the platform
|
|
103
|
+
* inbox (a permission confirmation or an org upload: the inbox already lists and counts it). A
|
|
104
|
+
* config-declared extension has no catalog item and stays. Without the catalog nothing is left out.
|
|
105
|
+
*/
|
|
106
|
+
private extensionRows;
|
|
107
|
+
/** Every live catalog item, page by page (the catalog lists by name). */
|
|
108
|
+
private catalogItems;
|
|
109
|
+
/** Platform plugins with a newer marketplace version (org items are their org admins'). */
|
|
110
|
+
private updateRows;
|
|
111
|
+
private providerRows;
|
|
112
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
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 { CATALOG_LIST_MAX_LIMIT, CatalogService, } from "../catalog/catalog.service.js";
|
|
12
|
+
import { MarketplacesService } from "../catalog/marketplaces.service.js";
|
|
13
|
+
import { ExtensionAdmin } from "../extensions/admin-view.js";
|
|
14
|
+
import { ExtensionDisplayNames } from "../extensions/display-name.js";
|
|
15
|
+
import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
16
|
+
import { AccountsService } from "../identity/accounts.js";
|
|
17
|
+
import { PlatformInboxService } from "../inbox/platform-inbox.service.js";
|
|
18
|
+
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
19
|
+
import { OrgsService } from "../orgs/orgs.service.js";
|
|
20
|
+
import { ProviderRegistry } from "../runtime/providers.js";
|
|
21
|
+
/** The most rows each attention list returns; the rest are counted in `more`. */
|
|
22
|
+
export const ATTENTION_LIST_MAX = 20;
|
|
23
|
+
/** The reason an extension is listed, or null when it needs nothing. */
|
|
24
|
+
export function extensionReason(e) {
|
|
25
|
+
switch (e.status) {
|
|
26
|
+
case "pending_config":
|
|
27
|
+
return "config";
|
|
28
|
+
case "failed":
|
|
29
|
+
return "load_failed";
|
|
30
|
+
case "pending_approval":
|
|
31
|
+
return "approval";
|
|
32
|
+
case "dependency":
|
|
33
|
+
case "conflict":
|
|
34
|
+
case "legacy_format":
|
|
35
|
+
return e.status;
|
|
36
|
+
case "converging":
|
|
37
|
+
case "pending_migration":
|
|
38
|
+
return "converging";
|
|
39
|
+
case "enabled":
|
|
40
|
+
return e.configValid ? null : "config";
|
|
41
|
+
case "disabled":
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** The problem of a provider in the platform view, or null (`org_configured` is not one). */
|
|
46
|
+
export function providerProblem(p) {
|
|
47
|
+
if (p.status === "error")
|
|
48
|
+
return "list_failed";
|
|
49
|
+
if (p.status !== "ok")
|
|
50
|
+
return null;
|
|
51
|
+
return p.models.some((m) => m.kind === "chat" && m.disabled !== true) ? null : "no_chat_model";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The admin overview (spec §4.1, §5.2): its numbers and the 待处理 lists, read live from the
|
|
55
|
+
* sources the admin pages read (`ExtensionAdmin`, the catalog, the provider registry's cached
|
|
56
|
+
* lists). Each source is read on its own: a failing one is reported in `unavailable`. Reads are not
|
|
57
|
+
* audited (decision D5) and not cached further (every source is PG or already Redis-cached).
|
|
58
|
+
*/
|
|
59
|
+
let AdminOverviewService = class AdminOverviewService {
|
|
60
|
+
extensions;
|
|
61
|
+
catalog;
|
|
62
|
+
registry;
|
|
63
|
+
displayNames;
|
|
64
|
+
icons;
|
|
65
|
+
inbox;
|
|
66
|
+
orgs;
|
|
67
|
+
accounts;
|
|
68
|
+
marketplaces;
|
|
69
|
+
log;
|
|
70
|
+
constructor(extensions, catalog, registry, displayNames, icons, inbox, orgs, accounts, marketplaces, log) {
|
|
71
|
+
this.extensions = extensions;
|
|
72
|
+
this.catalog = catalog;
|
|
73
|
+
this.registry = registry;
|
|
74
|
+
this.displayNames = displayNames;
|
|
75
|
+
this.icons = icons;
|
|
76
|
+
this.inbox = inbox;
|
|
77
|
+
this.orgs = orgs;
|
|
78
|
+
this.accounts = accounts;
|
|
79
|
+
this.marketplaces = marketplaces;
|
|
80
|
+
this.log = log;
|
|
81
|
+
}
|
|
82
|
+
async stats() {
|
|
83
|
+
const [orgs, accounts, inbox, attention] = await Promise.all([
|
|
84
|
+
this.orgs.listAll(),
|
|
85
|
+
this.accounts.count(),
|
|
86
|
+
this.inbox.count(),
|
|
87
|
+
this.attention(),
|
|
88
|
+
]);
|
|
89
|
+
const listed = (s) => attention[s].length + attention.more[s];
|
|
90
|
+
return {
|
|
91
|
+
orgs: orgs.length,
|
|
92
|
+
accounts,
|
|
93
|
+
attentionTotal: inbox + listed("extensions") + listed("updates") + listed("providers"),
|
|
94
|
+
partial: attention.unavailable.length > 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async attention() {
|
|
98
|
+
// the catalog is read once: the update rows, and the plugins the inbox already lists
|
|
99
|
+
const items = this.catalogItems();
|
|
100
|
+
items.catch(() => { }); // awaited (and reported) by updateRows
|
|
101
|
+
const [extensions, updates, providers] = await Promise.allSettled([
|
|
102
|
+
this.extensionRows(items),
|
|
103
|
+
this.updateRows(items),
|
|
104
|
+
this.providerRows(),
|
|
105
|
+
]);
|
|
106
|
+
const out = {
|
|
107
|
+
extensions: [],
|
|
108
|
+
updates: [],
|
|
109
|
+
providers: [],
|
|
110
|
+
more: { extensions: 0, updates: 0, providers: 0 },
|
|
111
|
+
unavailable: [],
|
|
112
|
+
};
|
|
113
|
+
/** The rows of a source that answered (capped), or none and its name in `unavailable`. */
|
|
114
|
+
const take = (source, r) => {
|
|
115
|
+
if (r.status === "rejected") {
|
|
116
|
+
// logged for the operator; the answer carries only the source's name (spec §9)
|
|
117
|
+
this.log.warn("admin overview: an attention source could not be read", {
|
|
118
|
+
source,
|
|
119
|
+
err: r.reason,
|
|
120
|
+
});
|
|
121
|
+
out.unavailable.push(source);
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
out.more[source] = Math.max(0, r.value.length - ATTENTION_LIST_MAX);
|
|
125
|
+
return r.value.slice(0, ATTENTION_LIST_MAX);
|
|
126
|
+
};
|
|
127
|
+
out.extensions = take("extensions", extensions);
|
|
128
|
+
out.updates = take("updates", updates);
|
|
129
|
+
out.providers = take("providers", providers);
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The extensions needing a look, but not one pending approval whose plugin waits in the platform
|
|
134
|
+
* inbox (a permission confirmation or an org upload: the inbox already lists and counts it). A
|
|
135
|
+
* config-declared extension has no catalog item and stays. Without the catalog nothing is left out.
|
|
136
|
+
*/
|
|
137
|
+
async extensionRows(items) {
|
|
138
|
+
const [views, catalog] = await Promise.all([this.extensions.list(), items.catch(() => [])]);
|
|
139
|
+
const inInbox = new Set(catalog.filter((i) => i.pending).map((i) => i.name));
|
|
140
|
+
return views.flatMap((v) => {
|
|
141
|
+
// every status of extensionNeedsAction has a reason, and so has enabled with an invalid config
|
|
142
|
+
const reason = extensionReason(v);
|
|
143
|
+
if (reason === null)
|
|
144
|
+
return [];
|
|
145
|
+
if (v.status === "pending_approval" && inInbox.has(v.name))
|
|
146
|
+
return [];
|
|
147
|
+
return [
|
|
148
|
+
{
|
|
149
|
+
name: v.name,
|
|
150
|
+
displayName: v.displayName,
|
|
151
|
+
icon: v.icon ?? null,
|
|
152
|
+
status: v.status,
|
|
153
|
+
reason,
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** Every live catalog item, page by page (the catalog lists by name). */
|
|
159
|
+
async catalogItems() {
|
|
160
|
+
const items = [];
|
|
161
|
+
let offset = 0;
|
|
162
|
+
while (offset !== null) {
|
|
163
|
+
const page = await this.catalog.page({ limit: CATALOG_LIST_MAX_LIMIT, offset });
|
|
164
|
+
items.push(...page.items);
|
|
165
|
+
offset = page.nextOffset;
|
|
166
|
+
}
|
|
167
|
+
return items;
|
|
168
|
+
}
|
|
169
|
+
/** Platform plugins with a newer marketplace version (org items are their org admins'). */
|
|
170
|
+
async updateRows(items) {
|
|
171
|
+
const newer = (await items).filter((i) => i.owner.kind === "platform" && i.newerInMarketplace && i.latestMarketplaceVersion !== null);
|
|
172
|
+
if (newer.length === 0)
|
|
173
|
+
return [];
|
|
174
|
+
const [icons, marketplaces] = await Promise.all([
|
|
175
|
+
this.icons.ofItems(newer.map((i) => i.id), { kind: "admin" }),
|
|
176
|
+
this.marketplaces.list(),
|
|
177
|
+
]);
|
|
178
|
+
const names = new Map(marketplaces.map((m) => [m.id, m.name]));
|
|
179
|
+
return newer.map((i) => ({
|
|
180
|
+
itemId: i.id,
|
|
181
|
+
name: i.name,
|
|
182
|
+
displayName: i.displayName,
|
|
183
|
+
icon: icons.get(i.id) ?? null,
|
|
184
|
+
version: i.latestVersion,
|
|
185
|
+
marketplaceVersion: i.latestMarketplaceVersion,
|
|
186
|
+
marketplaceId: i.marketplaceId,
|
|
187
|
+
marketplaceName: i.marketplaceId === null ? null : (names.get(i.marketplaceId) ?? null),
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
async providerRows() {
|
|
191
|
+
const problems = (await this.registry.listWithStatus()).flatMap((p) => {
|
|
192
|
+
const problem = providerProblem(p);
|
|
193
|
+
return problem === null ? [] : [{ plugin: p.plugin, problem }];
|
|
194
|
+
});
|
|
195
|
+
if (problems.length === 0)
|
|
196
|
+
return [];
|
|
197
|
+
const plugins = problems.map((p) => p.plugin);
|
|
198
|
+
const [names, icons] = await Promise.all([
|
|
199
|
+
this.displayNames.many(plugins),
|
|
200
|
+
this.icons.many(plugins, { kind: "admin" }),
|
|
201
|
+
]);
|
|
202
|
+
return problems.map((p) => ({
|
|
203
|
+
plugin: p.plugin,
|
|
204
|
+
displayName: names.get(p.plugin) ?? null,
|
|
205
|
+
icon: icons.get(p.plugin) ?? null,
|
|
206
|
+
problem: p.problem,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
AdminOverviewService = __decorate([
|
|
211
|
+
Injectable(),
|
|
212
|
+
__param(0, Inject(ExtensionAdmin)),
|
|
213
|
+
__param(1, Inject(CatalogService)),
|
|
214
|
+
__param(2, Inject(ProviderRegistry)),
|
|
215
|
+
__param(3, Inject(ExtensionDisplayNames)),
|
|
216
|
+
__param(4, Inject(PluginIcons)),
|
|
217
|
+
__param(5, Inject(PlatformInboxService)),
|
|
218
|
+
__param(6, Inject(OrgsService)),
|
|
219
|
+
__param(7, Inject(AccountsService)),
|
|
220
|
+
__param(8, Inject(MarketplacesService)),
|
|
221
|
+
__param(9, Inject(KERNEL_LOGGER))
|
|
222
|
+
], AdminOverviewService);
|
|
223
|
+
export { AdminOverviewService };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
2
|
+
/**
|
|
3
|
+
* One platform-level audit event (admin overview spec §5.2, §9): who, what, on what and when, and
|
|
4
|
+
* two flags. Never the event's `detail` (it may carry config field names or ids); a deleted actor
|
|
5
|
+
* account reads as its id with a null name and email; `actor` is null for the kernel itself.
|
|
6
|
+
*/
|
|
7
|
+
export interface PlatformAuditEvent {
|
|
8
|
+
/** The event id (a bigserial, as a decimal string); also the keyset cursor. */
|
|
9
|
+
id: string;
|
|
10
|
+
at: string;
|
|
11
|
+
actor: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string | null;
|
|
14
|
+
email: string | null;
|
|
15
|
+
} | null;
|
|
16
|
+
/** Done with a personal access token. */
|
|
17
|
+
viaToken: boolean;
|
|
18
|
+
/** Done through the Platform tool by the actor's own agent (`detail.via === "agent"`). */
|
|
19
|
+
viaAgent: boolean;
|
|
20
|
+
/** The action; a legacy name is mapped through {@link LEGACY_AUDIT_ACTIONS}. */
|
|
21
|
+
action: string;
|
|
22
|
+
target: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** A page of {@link PlatformAuditEvent}s; `next` is the `after` of the next page, null at the end. */
|
|
25
|
+
export interface PlatformAuditPage {
|
|
26
|
+
events: PlatformAuditEvent[];
|
|
27
|
+
next: string | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reads the platform-level audit log: events with no org (`org_id IS NULL`, decision D3), newest
|
|
31
|
+
* first by id, keyset paged (`audit_events_platform_idx`). Read-only and not audited (D5).
|
|
32
|
+
*/
|
|
33
|
+
export declare class PlatformAuditService {
|
|
34
|
+
private readonly kdb;
|
|
35
|
+
constructor(kdb: KernelDb);
|
|
36
|
+
/** `limit` events with an id below `after` (from the newest without it); one extra row tells `next`. */
|
|
37
|
+
list(q: {
|
|
38
|
+
after?: string | undefined;
|
|
39
|
+
limit: number;
|
|
40
|
+
}): Promise<PlatformAuditPage>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { LEGACY_AUDIT_ACTIONS } from "../identity/audit.js";
|
|
13
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
14
|
+
/**
|
|
15
|
+
* Reads the platform-level audit log: events with no org (`org_id IS NULL`, decision D3), newest
|
|
16
|
+
* first by id, keyset paged (`audit_events_platform_idx`). Read-only and not audited (D5).
|
|
17
|
+
*/
|
|
18
|
+
let PlatformAuditService = class PlatformAuditService {
|
|
19
|
+
kdb;
|
|
20
|
+
constructor(kdb) {
|
|
21
|
+
this.kdb = kdb;
|
|
22
|
+
}
|
|
23
|
+
/** `limit` events with an id below `after` (from the newest without it); one extra row tells `next`. */
|
|
24
|
+
async list(q) {
|
|
25
|
+
let query = this.kdb.db
|
|
26
|
+
.selectFrom("audit_events as a")
|
|
27
|
+
.leftJoin("accounts as ac", "ac.id", "a.actor_account_id")
|
|
28
|
+
.select([
|
|
29
|
+
"a.id",
|
|
30
|
+
"a.at",
|
|
31
|
+
"a.actor_account_id",
|
|
32
|
+
"a.actor_token_id",
|
|
33
|
+
"a.action",
|
|
34
|
+
"a.target",
|
|
35
|
+
"ac.display_name",
|
|
36
|
+
"ac.email",
|
|
37
|
+
// only the one flag the overview shows, never the whole detail (spec §9)
|
|
38
|
+
sql `a.detail->>'via'`.as("via"),
|
|
39
|
+
])
|
|
40
|
+
.where("a.org_id", "is", null);
|
|
41
|
+
if (q.after !== undefined)
|
|
42
|
+
query = query.where("a.id", "<", q.after);
|
|
43
|
+
const rows = await query
|
|
44
|
+
.orderBy("a.id", "desc")
|
|
45
|
+
.limit(q.limit + 1)
|
|
46
|
+
.execute();
|
|
47
|
+
const events = rows.slice(0, q.limit).map((r) => ({
|
|
48
|
+
id: String(r.id),
|
|
49
|
+
at: r.at.toISOString(),
|
|
50
|
+
actor: r.actor_account_id === null
|
|
51
|
+
? null
|
|
52
|
+
: {
|
|
53
|
+
id: r.actor_account_id,
|
|
54
|
+
name: r.display_name ?? null,
|
|
55
|
+
email: r.email ?? null,
|
|
56
|
+
},
|
|
57
|
+
viaToken: r.actor_token_id !== null,
|
|
58
|
+
viaAgent: r.via === "agent",
|
|
59
|
+
action: LEGACY_AUDIT_ACTIONS[r.action] ?? r.action,
|
|
60
|
+
target: r.target,
|
|
61
|
+
}));
|
|
62
|
+
const next = rows.length > q.limit ? (events.at(-1)?.id ?? null) : null;
|
|
63
|
+
return { events, next };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
PlatformAuditService = __decorate([
|
|
67
|
+
Injectable(),
|
|
68
|
+
__param(0, Inject(KernelDb))
|
|
69
|
+
], PlatformAuditService);
|
|
70
|
+
export { PlatformAuditService };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { SettingsService } from "../identity/settings.js";
|
|
2
|
+
import { CacheService } from "../infra/cache/cache.service.js";
|
|
3
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
4
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
5
|
+
import { PricesService } from "../models/prices.js";
|
|
6
|
+
/** Seam token of the kernel's own tests: a shorter statement timeout and a fixed clock. */
|
|
7
|
+
export declare const ADMIN_USAGE_TIMINGS: unique symbol;
|
|
8
|
+
export interface AdminUsageTimings {
|
|
9
|
+
/** The aggregate's `statement_timeout` in milliseconds (default 10 000). */
|
|
10
|
+
statementTimeoutMs: number;
|
|
11
|
+
/** The clock "today" is read from (default `Date.now`). */
|
|
12
|
+
now: () => number;
|
|
13
|
+
}
|
|
14
|
+
/** Days in the window: today and the 6 days before. */
|
|
15
|
+
export declare const USAGE_WINDOW_DAYS = 7;
|
|
16
|
+
/** Orgs listed by name; the rest are summed in `otherOrgs`. */
|
|
17
|
+
export declare const USAGE_TOP_ORGS = 10;
|
|
18
|
+
/** Seconds one computed week stays cached (shared by every replica, Redis). */
|
|
19
|
+
export declare const USAGE_CACHE_TTL = 300;
|
|
20
|
+
/** Tokens of one day; the four chat kinds do not overlap (plugin-sdk `ModelUsage`). */
|
|
21
|
+
export interface UsageTokens {
|
|
22
|
+
input: number;
|
|
23
|
+
output: number;
|
|
24
|
+
cacheRead: number;
|
|
25
|
+
cacheWrite: number;
|
|
26
|
+
/** Input tokens of `ctx.models.embed` calls (`model_usage`). */
|
|
27
|
+
embedding: number;
|
|
28
|
+
}
|
|
29
|
+
/** One calendar day in the requested time zone. */
|
|
30
|
+
export interface UsageDay {
|
|
31
|
+
/** `YYYY-MM-DD` in the answer's `tz`. */
|
|
32
|
+
date: string;
|
|
33
|
+
/** Runs created that day (every run: sub-agent and eval replay runs included, D6). */
|
|
34
|
+
runs: number;
|
|
35
|
+
tokens: UsageTokens;
|
|
36
|
+
/**
|
|
37
|
+
* Estimated cost in `currency` of the priced tokens; null when the day used tokens and none of
|
|
38
|
+
* them had a price (0 for a day with no tokens).
|
|
39
|
+
*/
|
|
40
|
+
cost: number | null;
|
|
41
|
+
}
|
|
42
|
+
/** One org's week: totals only (never per account, agent or conversation; spec §9). */
|
|
43
|
+
export interface UsageOrg {
|
|
44
|
+
orgId: string;
|
|
45
|
+
/** The org's name (its id when the org row is gone). */
|
|
46
|
+
name: string;
|
|
47
|
+
runs: number;
|
|
48
|
+
/** Every token kind summed, embeddings included. */
|
|
49
|
+
tokens: number;
|
|
50
|
+
/** As {@link UsageDay.cost}, over the org's week. */
|
|
51
|
+
cost: number | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `AdminUsageService.week(tz)` (admin overview spec §4.3, §5.2): the platform's runs, tokens and
|
|
55
|
+
* estimated cost per calendar day in `tz` over today and the 6 days before, and the 10 orgs that
|
|
56
|
+
* used the most tokens (the rest summed in `otherOrgs`).
|
|
57
|
+
*/
|
|
58
|
+
export interface AdminUsageWeek {
|
|
59
|
+
/** Start of the first day in `tz` (ISO 8601, UTC). */
|
|
60
|
+
from: string;
|
|
61
|
+
/** End of today in `tz`, exclusive (ISO 8601, UTC). */
|
|
62
|
+
to: string;
|
|
63
|
+
tz: string;
|
|
64
|
+
/** The deployment setting `priceCurrency`. */
|
|
65
|
+
currency: string;
|
|
66
|
+
/** Exactly 7 days, oldest first, today last; days without usage are zeros. */
|
|
67
|
+
days: UsageDay[];
|
|
68
|
+
/** Some tokens of the week had no price: the costs count only the priced ones. */
|
|
69
|
+
costPartial: boolean;
|
|
70
|
+
/** At most 10, by total tokens descending (then runs, then id). */
|
|
71
|
+
orgs: UsageOrg[];
|
|
72
|
+
/** The orgs with usage outside the top 10, summed; `count` 0 when there are none. */
|
|
73
|
+
otherOrgs: {
|
|
74
|
+
count: number;
|
|
75
|
+
runs: number;
|
|
76
|
+
tokens: number;
|
|
77
|
+
cost: number | null;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** `YYYY-MM-DD` of `at` in `tz`. */
|
|
81
|
+
export declare function dateIn(tz: string, at: number): string;
|
|
82
|
+
/** The calendar date `days` after `date` (`YYYY-MM-DD`; date arithmetic, no time zone involved). */
|
|
83
|
+
export declare function addDays(date: string, days: number): string;
|
|
84
|
+
interface TokenRow {
|
|
85
|
+
day: string;
|
|
86
|
+
org_id: string;
|
|
87
|
+
provider: string;
|
|
88
|
+
model: string;
|
|
89
|
+
input: string;
|
|
90
|
+
output: string;
|
|
91
|
+
cache_read: string;
|
|
92
|
+
cache_write: string;
|
|
93
|
+
}
|
|
94
|
+
interface RunRow {
|
|
95
|
+
day: string;
|
|
96
|
+
org_id: string;
|
|
97
|
+
runs: string;
|
|
98
|
+
}
|
|
99
|
+
interface EmbeddingRow {
|
|
100
|
+
day: string;
|
|
101
|
+
org_id: string;
|
|
102
|
+
provider: string;
|
|
103
|
+
model: string;
|
|
104
|
+
input: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The admin overview's usage block (spec §4.3, §5.2, §5.4). Three grouped queries over a bounded
|
|
108
|
+
* window (`conversation_events_usage_idx`, `runs_created_idx`, `model_usage_time_idx`) in one
|
|
109
|
+
* read-only transaction with a statement timeout; priced here with the model price table. The
|
|
110
|
+
* answer is cached in Redis for 300 s per time zone and first day, so every replica serves the
|
|
111
|
+
* same answer and a zone is recomputed at most once per 5 minutes (prices or currency changed in
|
|
112
|
+
* between show after the TTL). Aggregates only: no account, agent or conversation, no content.
|
|
113
|
+
*/
|
|
114
|
+
export declare class AdminUsageService {
|
|
115
|
+
private readonly db;
|
|
116
|
+
readonly txHost: TransactionHost;
|
|
117
|
+
readonly cacheService: CacheService;
|
|
118
|
+
private readonly prices;
|
|
119
|
+
private readonly settings;
|
|
120
|
+
private readonly statementTimeoutMs;
|
|
121
|
+
private readonly now;
|
|
122
|
+
constructor(db: KernelDb, txHost: TransactionHost, cacheService: CacheService, prices: Pick<PricesService, "table">, settings: Pick<SettingsService, "get">, timings?: Partial<AdminUsageTimings>);
|
|
123
|
+
/**
|
|
124
|
+
* The week ending today in `tz` (an IANA zone the caller validated, `IanaTimeZone`; normalized to
|
|
125
|
+
* its canonical spelling, which the answer echoes and the cache key holds). Throws
|
|
126
|
+
* `admin.usage_unavailable` (503) when the aggregate ran past its statement timeout, and
|
|
127
|
+
* `validation_failed` (400) for a zone PG does not know.
|
|
128
|
+
*
|
|
129
|
+
* Never call it inside a transaction: it leaves the caller's context on purpose (inside one,
|
|
130
|
+
* `@Cacheable` would be bypassed and the read-only transaction would hold a second connection).
|
|
131
|
+
*/
|
|
132
|
+
week(tz: string): Promise<AdminUsageWeek>;
|
|
133
|
+
/** The cache key holds the zone and the first day: a new day starts a new entry. */
|
|
134
|
+
protected cachedWeek(tz: string, firstDay: string): Promise<AdminUsageWeek>;
|
|
135
|
+
/** The window's queries, in their own read-only transaction (the timeout must not leak into a caller's). */
|
|
136
|
+
protected aggregate(tz: string, firstDay: string): Promise<AdminUsageWeek>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The three grouped queries over `[from, to)` (spec §5.4), each on its time index:
|
|
140
|
+
* `conversation_events_usage_idx`, `runs_created_idx`, `model_usage_time_idx`. Rows are bounded by
|
|
141
|
+
* days × orgs (× models), not by events. Days are calendar days in `tz` (`YYYY-MM-DD` text, so no
|
|
142
|
+
* driver turns them into local-time dates).
|
|
143
|
+
*/
|
|
144
|
+
export declare function usageQueries(tz: string, from: Date, to: Date): {
|
|
145
|
+
tokens: import("kysely").RawBuilder<TokenRow>;
|
|
146
|
+
runs: import("kysely").RawBuilder<RunRow>;
|
|
147
|
+
embeddings: import("kysely").RawBuilder<EmbeddingRow>;
|
|
148
|
+
};
|
|
149
|
+
export {};
|