@capybara-hq/kernel 0.1.0-rc.10 → 0.1.0-rc.12
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/admin/admin.schemas.d.ts +3 -0
- package/dist/agents/agents.schemas.d.ts +3 -0
- package/dist/agents/agents.service.js +11 -1
- package/dist/agents/definition.d.ts +1 -0
- package/dist/agents/definition.js +7 -3
- package/dist/agents/org-templates.js +4 -0
- package/dist/agents/pending-connectors.service.js +1 -1
- package/dist/catalog/catalog-content.d.ts +33 -1
- package/dist/catalog/catalog-content.js +65 -2
- package/dist/catalog/catalog-items.js +1 -0
- package/dist/catalog/catalog-scope.service.d.ts +29 -3
- package/dist/catalog/catalog-scope.service.js +82 -10
- package/dist/catalog/catalog.service.d.ts +7 -5
- package/dist/catalog/catalog.service.js +26 -5
- package/dist/catalog/claude/translate.js +3 -0
- package/dist/catalog/component-scope.controller.d.ts +18 -6
- package/dist/catalog/component-scope.controller.js +36 -13
- package/dist/catalog/component-scope.schemas.d.ts +4 -0
- package/dist/catalog/component-scope.service.d.ts +59 -9
- package/dist/catalog/component-scope.service.js +156 -27
- package/dist/catalog/component-state.d.ts +20 -3
- package/dist/catalog/component-state.js +40 -14
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +3 -0
- package/dist/catalog/owned-items.js +7 -0
- package/dist/catalog/platform-components.d.ts +23 -3
- package/dist/catalog/platform-components.js +118 -9
- package/dist/catalog/platform-migration.js +2 -1
- package/dist/catalog/plugin-import.d.ts +24 -4
- package/dist/catalog/plugin-import.js +41 -7
- package/dist/catalog/plugin-install.d.ts +20 -1
- package/dist/catalog/plugin-install.js +71 -1
- package/dist/catalog/plugin-package.d.ts +41 -4
- package/dist/catalog/plugin-package.js +127 -7
- package/dist/catalog/plugin-upgrade.d.ts +13 -2
- package/dist/catalog/plugin-upgrade.js +124 -6
- package/dist/catalog/unshare.js +1 -0
- package/dist/conversations/conversations.service.d.ts +9 -0
- package/dist/conversations/conversations.service.js +9 -2
- package/dist/conversations/events.d.ts +1 -0
- package/dist/db/kernel-schema.d.ts +54 -4
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +27 -0
- package/dist/http/api.module.js +7 -0
- package/dist/identity/accounts.d.ts +11 -4
- package/dist/identity/accounts.js +23 -10
- package/dist/identity/me.controller.d.ts +10 -10
- package/dist/identity/me.controller.js +8 -6
- package/dist/identity/me.schemas.d.ts +13 -3
- package/dist/identity/me.schemas.js +10 -2
- package/dist/identity/settings.d.ts +27 -0
- package/dist/identity/settings.js +64 -0
- package/dist/orgs/settings.service.d.ts +31 -0
- package/dist/orgs/settings.service.js +31 -1
- package/dist/platform/exclusions.js +26 -0
- package/dist/platform/operation.js +6 -2
- package/dist/platform/ops/admin.ops.d.ts +3 -1
- package/dist/platform/ops/admin.ops.js +25 -7
- package/dist/platform/ops/agents.ops.js +19 -6
- package/dist/platform/ops/components.ops.js +1 -0
- package/dist/platform/ops/org-settings.ops.d.ts +3 -1
- package/dist/platform/ops/org-settings.ops.js +33 -8
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/platform.ops.d.ts +3 -1
- package/dist/platform/ops/platform.ops.js +43 -16
- package/dist/platform/ops/themes.ops.d.ts +14 -0
- package/dist/platform/ops/themes.ops.js +47 -0
- 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 +3 -0
- package/dist/runtime/subagents/definition.d.ts +3 -2
- package/dist/runtime/subagents/definition.js +2 -1
- package/dist/themes/render-css.d.ts +12 -0
- package/dist/themes/render-css.js +132 -0
- package/dist/themes/theme-delivery.service.d.ts +34 -0
- package/dist/themes/theme-delivery.service.js +111 -0
- package/dist/themes/theme-state.d.ts +95 -0
- package/dist/themes/theme-state.js +203 -0
- package/dist/themes/theme-usage.d.ts +61 -0
- package/dist/themes/theme-usage.js +98 -0
- package/dist/themes/themes-public.controller.d.ts +28 -0
- package/dist/themes/themes-public.controller.js +92 -0
- package/dist/themes/themes.controller.d.ts +13 -0
- package/dist/themes/themes.controller.js +38 -0
- package/dist/themes/themes.module.d.ts +3 -0
- package/dist/themes/themes.module.js +21 -0
- package/dist/themes/themes.schemas.d.ts +3 -0
- package/dist/themes/themes.schemas.js +2 -0
- package/dist/themes/themes.service.d.ts +50 -0
- package/dist/themes/themes.service.js +99 -0
- package/dist/tools/connector-account-policies.d.ts +51 -0
- package/dist/tools/connector-account-policies.js +165 -0
- package/dist/tools/connectors.controller.d.ts +35 -4
- package/dist/tools/connectors.controller.js +66 -7
- package/dist/tools/connectors.schemas.d.ts +11 -0
- package/dist/tools/connectors.schemas.js +11 -0
- package/dist/tools/connectors.service.d.ts +2 -0
- package/dist/tools/connectors.service.js +1 -0
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/policy.d.ts +23 -1
- package/dist/tools/policy.js +99 -24
- package/dist/tools/tools.module.js +3 -0
- package/package.json +3 -3
- package/src/migrations/0102_themes.sql +64 -0
- package/src/migrations/0103_ui_theme_setting.sql +7 -0
- package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type AgentTemplate, type ConnectorTemplateFile, type PluginManifest } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type AgentTemplate, type ConnectorTemplateFile, type ParsedTheme, type PluginManifest } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { ApiError } from "../errors.js";
|
|
2
3
|
import { LEGACY_FORMAT_MESSAGE, type PackageTgzLimits, packagePath } from "../packages/tgz.js";
|
|
3
4
|
export { LEGACY_FORMAT_MESSAGE, packagePath };
|
|
4
5
|
/**
|
|
@@ -13,13 +14,24 @@ export { LEGACY_FORMAT_MESSAGE, packagePath };
|
|
|
13
14
|
* plugin.json) is rejected first, with {@link LEGACY_FORMAT_MESSAGE}: there is no reader for the
|
|
14
15
|
* old formats.
|
|
15
16
|
*/
|
|
16
|
-
/**
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Codes of {@link PluginPackageError}: `theme.invalid` / `theme.unreadable` when the package's only
|
|
19
|
+
* problems are in its themes (themes spec §5: a failing theme refuses the whole plugin).
|
|
20
|
+
*/
|
|
21
|
+
export type PluginPackageErrorCode = "catalog.invalid_manifest" | "catalog.legacy_format" | "theme.invalid" | "theme.unreadable";
|
|
22
|
+
/** The problems of one theme of a refused plugin (English; `unreadable:` lines for readability). */
|
|
23
|
+
export interface PluginThemeProblem {
|
|
24
|
+
/** The theme id (the file's base name). */
|
|
25
|
+
theme: string;
|
|
26
|
+
problems: string[];
|
|
27
|
+
}
|
|
18
28
|
/** The plugin package is invalid; `issues` lists the problems (English, shown to the admin). */
|
|
19
29
|
export declare class PluginPackageError extends Error {
|
|
20
30
|
readonly code: PluginPackageErrorCode;
|
|
21
31
|
readonly issues: string[];
|
|
22
|
-
|
|
32
|
+
/** The failing themes of a `theme.*` code. */
|
|
33
|
+
readonly themes?: PluginThemeProblem[];
|
|
34
|
+
constructor(code: PluginPackageErrorCode, issues: string[], themes?: PluginThemeProblem[]);
|
|
23
35
|
}
|
|
24
36
|
/** A skill of the plugin. `dir` is relative to the package root; `files` are relative to `dir`. */
|
|
25
37
|
export interface PluginPackageSkill {
|
|
@@ -42,6 +54,18 @@ export interface PluginPackageAgentTemplate {
|
|
|
42
54
|
path: string;
|
|
43
55
|
template: AgentTemplate;
|
|
44
56
|
}
|
|
57
|
+
/** A theme of the plugin (themes spec §4); `id` is the file's base name. */
|
|
58
|
+
export interface PluginPackageTheme {
|
|
59
|
+
id: string;
|
|
60
|
+
/** Relative to the package root (no leading `./`). */
|
|
61
|
+
path: string;
|
|
62
|
+
theme: ParsedTheme;
|
|
63
|
+
/**
|
|
64
|
+
* The theme's fonts and logo files after `validateThemeAssets` (the SVGs sanitized, the rest
|
|
65
|
+
* unchanged), keyed by their path relative to the package root (no leading `./`).
|
|
66
|
+
*/
|
|
67
|
+
assets: Map<string, Uint8Array>;
|
|
68
|
+
}
|
|
45
69
|
/** A validated plugin file tree. Paths are relative to the plugin root (no `package/`). */
|
|
46
70
|
export interface PluginContents {
|
|
47
71
|
/** `{}` when a git plugin has no package.json. */
|
|
@@ -52,6 +76,8 @@ export interface PluginContents {
|
|
|
52
76
|
connectorTemplates: PluginPackageConnectorTemplate[];
|
|
53
77
|
/** Agent templates (listed in plugin.json `agents`, else scanned from `agents/*.md`). */
|
|
54
78
|
agentTemplates: PluginPackageAgentTemplate[];
|
|
79
|
+
/** Themes (listed in plugin.json `themes`, else scanned from `themes/*.json`). */
|
|
80
|
+
themes: PluginPackageTheme[];
|
|
55
81
|
/** {@link contentDigest} of `files` (the same for a tgz and a git tree of the same files). */
|
|
56
82
|
contentDigest: string;
|
|
57
83
|
}
|
|
@@ -141,3 +167,14 @@ export interface ReadPluginFilesOptions {
|
|
|
141
167
|
* {@link PluginPackageError} like {@link readPluginPackage}.
|
|
142
168
|
*/
|
|
143
169
|
export declare function readPluginFiles(files: Map<string, Uint8Array>, opts: ReadPluginFilesOptions): PluginContents;
|
|
170
|
+
/**
|
|
171
|
+
* The refusal of a plugin whose only problems are in its themes: `theme.invalid` when any theme
|
|
172
|
+
* has a problem other than readability (those themes listed), else `theme.unreadable` (every
|
|
173
|
+
* failing theme listed with its failing pairs).
|
|
174
|
+
*/
|
|
175
|
+
export declare function pluginThemeError(problems: PluginThemeProblem[]): PluginPackageError;
|
|
176
|
+
/**
|
|
177
|
+
* The API error of a `theme.*` {@link PluginPackageError}: 400 `theme.invalid` with details
|
|
178
|
+
* `[{ theme, errors }]`, or 400 `theme.unreadable` with details `[{ theme, issues }]`.
|
|
179
|
+
*/
|
|
180
|
+
export declare function themeApiError(e: PluginPackageError): ApiError;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { AGENT_TEMPLATE_LIMITS, CATALOG_ITEM_NAME_RE, CONNECTOR_TEMPLATE_LIMITS, hostSdkProblem, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_LIMITS, parseAgentTemplate, parseConnectorTemplateFile, parsePluginManifest, parseSkillMd, SKILL_ENTRY_FILE, setupGuideFileProblem, validatePluginPackageJsonFor, } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { AGENT_TEMPLATE_LIMITS, CATALOG_ITEM_NAME_RE, CONNECTOR_TEMPLATE_LIMITS, hostSdkProblem, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_LIMITS, parseAgentTemplate, parseConnectorTemplateFile, parsePluginManifest, parseSkillMd, parseThemeFile, SKILL_ENTRY_FILE, setupGuideFileProblem, THEME_LIMITS, themeIdOf, validatePluginPackageJsonFor, validateThemeAssets, } from "@capybara-hq/plugin-sdk";
|
|
3
|
+
import { ApiError } from "../errors.js";
|
|
3
4
|
import { HOST_ZOD_VERSION, LEGACY_FORMAT_MESSAGE, PackageTgzError, packagePath, parsePackageJsonFile, unpackPackageTgz, } from "../packages/tgz.js";
|
|
4
5
|
export { LEGACY_FORMAT_MESSAGE, packagePath };
|
|
5
6
|
/** The plugin package is invalid; `issues` lists the problems (English, shown to the admin). */
|
|
6
7
|
export class PluginPackageError extends Error {
|
|
7
8
|
code;
|
|
8
9
|
issues;
|
|
9
|
-
|
|
10
|
+
/** The failing themes of a `theme.*` code. */
|
|
11
|
+
themes;
|
|
12
|
+
constructor(code, issues, themes) {
|
|
10
13
|
super(code === "catalog.legacy_format"
|
|
11
14
|
? (issues[0] ?? LEGACY_FORMAT_MESSAGE)
|
|
12
|
-
:
|
|
15
|
+
: code === "theme.invalid" || code === "theme.unreadable"
|
|
16
|
+
? `Theme ${themes?.[0]?.theme ?? "?"} ${code === "theme.invalid" ? "is invalid" : "is not readable"}: ${issues[0] ?? "unknown problem"}`
|
|
17
|
+
: `Invalid plugin package: ${issues[0] ?? "unknown problem"}`);
|
|
13
18
|
this.name = "PluginPackageError";
|
|
14
19
|
this.code = code;
|
|
15
20
|
this.issues = issues;
|
|
21
|
+
if (themes !== undefined)
|
|
22
|
+
this.themes = themes;
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
25
|
/**
|
|
@@ -345,6 +352,7 @@ export function readPluginFiles(files, opts) {
|
|
|
345
352
|
agentNames.set(r.template.name, path);
|
|
346
353
|
agentTemplates.push({ name: r.template.name, path, template: r.template });
|
|
347
354
|
}
|
|
355
|
+
const { themes, themePaths, themeProblems } = readThemes(files, manifest);
|
|
348
356
|
if (manifest.icon !== undefined) {
|
|
349
357
|
const icon = files.get(packagePath(manifest.icon));
|
|
350
358
|
const problem = icon === undefined
|
|
@@ -378,11 +386,21 @@ export function readPluginFiles(files, opts) {
|
|
|
378
386
|
}
|
|
379
387
|
}
|
|
380
388
|
}
|
|
381
|
-
else if (skillDirs.length === 0 &&
|
|
382
|
-
|
|
389
|
+
else if (skillDirs.length === 0 &&
|
|
390
|
+
templatePaths.length === 0 &&
|
|
391
|
+
agentPaths.length === 0 &&
|
|
392
|
+
themePaths.length === 0) {
|
|
393
|
+
issues.push("a plugin must contain an extension, a skill, a connector template, an agent template or a theme");
|
|
383
394
|
}
|
|
384
|
-
if (issues.length > 0)
|
|
385
|
-
|
|
395
|
+
if (issues.length > 0) {
|
|
396
|
+
// Other problems first: the theme problems are listed with them as plain issues
|
|
397
|
+
invalid([
|
|
398
|
+
...issues,
|
|
399
|
+
...themeProblems.flatMap((t) => t.problems.map((p) => `themes/${t.theme}: ${p}`)),
|
|
400
|
+
]);
|
|
401
|
+
}
|
|
402
|
+
if (themeProblems.length > 0)
|
|
403
|
+
throw pluginThemeError(themeProblems);
|
|
386
404
|
return {
|
|
387
405
|
packageJson: (packageJson ?? {}),
|
|
388
406
|
manifest,
|
|
@@ -390,6 +408,108 @@ export function readPluginFiles(files, opts) {
|
|
|
390
408
|
skills,
|
|
391
409
|
connectorTemplates,
|
|
392
410
|
agentTemplates,
|
|
411
|
+
themes,
|
|
393
412
|
contentDigest: contentDigest(files),
|
|
394
413
|
};
|
|
395
414
|
}
|
|
415
|
+
const UNREADABLE = "unreadable:";
|
|
416
|
+
/**
|
|
417
|
+
* The refusal of a plugin whose only problems are in its themes: `theme.invalid` when any theme
|
|
418
|
+
* has a problem other than readability (those themes listed), else `theme.unreadable` (every
|
|
419
|
+
* failing theme listed with its failing pairs).
|
|
420
|
+
*/
|
|
421
|
+
export function pluginThemeError(problems) {
|
|
422
|
+
const invalidOnes = problems.filter((t) => t.problems.some((p) => !p.startsWith(UNREADABLE)));
|
|
423
|
+
const code = invalidOnes.length > 0 ? "theme.invalid" : "theme.unreadable";
|
|
424
|
+
const listed = invalidOnes.length > 0 ? invalidOnes : problems;
|
|
425
|
+
return new PluginPackageError(code, listed.flatMap((t) => t.problems), listed);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The API error of a `theme.*` {@link PluginPackageError}: 400 `theme.invalid` with details
|
|
429
|
+
* `[{ theme, errors }]`, or 400 `theme.unreadable` with details `[{ theme, issues }]`.
|
|
430
|
+
*/
|
|
431
|
+
export function themeApiError(e) {
|
|
432
|
+
const code = e.code === "theme.unreadable" ? "theme.unreadable" : "theme.invalid";
|
|
433
|
+
const key = code === "theme.invalid" ? "errors" : "issues";
|
|
434
|
+
return new ApiError(code, e.message, (e.themes ?? []).map((t) => ({ theme: t.theme, [key]: t.problems })));
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* The plugin's themes (themes spec §4-§5): listed in `manifest.themes`, else scanned from
|
|
438
|
+
* `themes/*.json` (at most {@link THEME_LIMITS}`.perPlugin`); each parsed by `parseThemeFile`
|
|
439
|
+
* (shape, token grammar, readability) and its assets checked by `validateThemeAssets`. The asset
|
|
440
|
+
* limit is per plugin, so the sizes of the distinct asset paths are also summed over all themes.
|
|
441
|
+
* Problems are returned per theme, never thrown.
|
|
442
|
+
*/
|
|
443
|
+
function readThemes(files, manifest) {
|
|
444
|
+
const themeProblems = [];
|
|
445
|
+
const problem = (theme, problems) => {
|
|
446
|
+
const t = themeProblems.find((x) => x.theme === theme);
|
|
447
|
+
if (t)
|
|
448
|
+
t.problems.push(...problems);
|
|
449
|
+
else
|
|
450
|
+
themeProblems.push({ theme, problems: [...problems] });
|
|
451
|
+
};
|
|
452
|
+
const themePaths = [];
|
|
453
|
+
if (manifest.themes !== undefined) {
|
|
454
|
+
for (const t of manifest.themes) {
|
|
455
|
+
const path = packagePath(t.path);
|
|
456
|
+
if (!files.has(path))
|
|
457
|
+
problem(themeIdOf(path), [`${t.path} not found in the package`]);
|
|
458
|
+
else if (!themePaths.includes(path))
|
|
459
|
+
themePaths.push(path);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
for (const p of [...files.keys()].sort()) {
|
|
464
|
+
if (/^themes\/[^/]+\.json$/.test(p))
|
|
465
|
+
themePaths.push(p);
|
|
466
|
+
}
|
|
467
|
+
if (themePaths.length > THEME_LIMITS.perPlugin) {
|
|
468
|
+
problem(themeIdOf(themePaths[THEME_LIMITS.perPlugin]), [
|
|
469
|
+
`themes/ holds ${themePaths.length} themes; a plugin may have at most ${THEME_LIMITS.perPlugin}`,
|
|
470
|
+
]);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const themes = [];
|
|
474
|
+
const sizes = new Map();
|
|
475
|
+
for (const path of themePaths) {
|
|
476
|
+
const id = themeIdOf(path);
|
|
477
|
+
if (themes.some((t) => t.id === id)) {
|
|
478
|
+
problem(id, [`${path}: another theme of the plugin has the id "${id}"`]);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const bytes = files.get(path);
|
|
482
|
+
if (bytes.byteLength > THEME_LIMITS.fileBytes) {
|
|
483
|
+
problem(id, [`${path}: the theme file is larger than ${THEME_LIMITS.fileBytes} bytes`]);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const json = decode(bytes);
|
|
487
|
+
if (json === undefined) {
|
|
488
|
+
problem(id, [`${path}: not valid UTF-8 text`]);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const parsed = parseThemeFile(id, json);
|
|
492
|
+
if ("errors" in parsed) {
|
|
493
|
+
problem(id, parsed.errors);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
const { assets, errors } = validateThemeAssets(parsed, files);
|
|
497
|
+
if (errors.length > 0) {
|
|
498
|
+
problem(id, errors);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
let total = 0;
|
|
502
|
+
for (const p of assets.keys())
|
|
503
|
+
sizes.set(p, files.get(p).byteLength);
|
|
504
|
+
for (const n of sizes.values())
|
|
505
|
+
total += n;
|
|
506
|
+
if (total > THEME_LIMITS.assetsBytes) {
|
|
507
|
+
problem(id, [
|
|
508
|
+
`the plugin's theme assets are larger than ${THEME_LIMITS.assetsBytes} bytes in total (${total}); the limit covers all of the plugin's themes`,
|
|
509
|
+
]);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
themes.push({ id, path, theme: parsed, assets });
|
|
513
|
+
}
|
|
514
|
+
return { themes, themePaths, themeProblems };
|
|
515
|
+
}
|
|
@@ -11,6 +11,7 @@ import { LockService } from "../infra/lock/lock.service.js";
|
|
|
11
11
|
import type { KernelLogger } from "../log.js";
|
|
12
12
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
13
13
|
import { StorageService } from "../storage/storage.service.js";
|
|
14
|
+
import { type ThemePreview } from "../themes/theme-usage.js";
|
|
14
15
|
import { ConnectorsService } from "../tools/connectors.service.js";
|
|
15
16
|
import { type CatalogVersionFiles } from "./catalog-content.js";
|
|
16
17
|
import { type ComponentKind } from "./component-state.js";
|
|
@@ -25,7 +26,7 @@ export interface PlatformUpdateComponent {
|
|
|
25
26
|
kind: ComponentKind;
|
|
26
27
|
/** The platform component (null: added by the target). */
|
|
27
28
|
id: string | null;
|
|
28
|
-
/** Skill or agent template name; a connector template's id. */
|
|
29
|
+
/** Skill or agent template name; a connector template's or a theme's id. */
|
|
29
30
|
name: string;
|
|
30
31
|
change: UpdateChange;
|
|
31
32
|
/** The catalog version of the component's current version (null: added, or no catalog version). */
|
|
@@ -109,6 +110,11 @@ export interface PlatformUpdatePreview {
|
|
|
109
110
|
needsApproval: boolean;
|
|
110
111
|
} | null;
|
|
111
112
|
connectorOrgs: PlatformUpdateConnectorOrg[];
|
|
113
|
+
/**
|
|
114
|
+
* The themes the update removes that are on in some org (themes spec §6): the orgs whose default
|
|
115
|
+
* each is and how many people picked it. Absent when no such theme is removed.
|
|
116
|
+
*/
|
|
117
|
+
themes?: ThemePreview[];
|
|
112
118
|
/**
|
|
113
119
|
* An owner's update preview only ({@link PlatformPluginUpdater.previewForOwner}): the name of
|
|
114
120
|
* every org the preview mentions, by id. An org admin cannot read other orgs, and these names
|
|
@@ -306,7 +312,10 @@ export declare class PlatformPluginUpdater {
|
|
|
306
312
|
* extension removes it.
|
|
307
313
|
*/
|
|
308
314
|
private diff;
|
|
309
|
-
/**
|
|
315
|
+
/**
|
|
316
|
+
* The target's skill names, agent templates, connector templates and themes, from object storage
|
|
317
|
+
* (a theme failing its checks -> 400 `theme.invalid`, themes spec §6).
|
|
318
|
+
*/
|
|
310
319
|
private readContents;
|
|
311
320
|
/**
|
|
312
321
|
* Whether the target's extension package asks for permissions the approved snapshot does not
|
|
@@ -328,6 +337,8 @@ export declare class PlatformPluginUpdater {
|
|
|
328
337
|
private checkDependents;
|
|
329
338
|
/** The plugin's live platform components with their current versions. */
|
|
330
339
|
private liveComponents;
|
|
340
|
+
/** The `themes` block of the preview: the orgs each of the removed themes `ids` is on in. */
|
|
341
|
+
private themesLost;
|
|
331
342
|
/** The orgs where the plugin is on: a live install (builtin: every live org). */
|
|
332
343
|
private pluginOrgs;
|
|
333
344
|
private liveOrgs;
|
|
@@ -34,11 +34,12 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
|
34
34
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
35
35
|
import { packagePath } from "../packages/tgz.js";
|
|
36
36
|
import { StorageService } from "../storage/storage.service.js";
|
|
37
|
+
import { assertNotPlatformDefault, pickedByIn, themesPreview, } from "../themes/theme-usage.js";
|
|
37
38
|
import { canonicalJson } from "../tools/canonical-json.js";
|
|
38
39
|
import { ConnectorsService } from "../tools/connectors.service.js";
|
|
39
|
-
import { dirDigest, getPath, readCatalogAgentTemplates, readCatalogTemplates, setPath, skillFilesDigest, templatePluginProblem, } from "./catalog-content.js";
|
|
40
|
+
import { dirDigest, getPath, readCatalogAgentTemplates, readCatalogTemplates, readCatalogThemes, setPath, skillFilesDigest, templatePluginProblem, themeAssetsDigest, } from "./catalog-content.js";
|
|
40
41
|
import { markVersionApplied, versionUsable } from "./catalog-items.js";
|
|
41
|
-
import { agentsUsing, clashingOrgSkills, clashingPlatformSkills, componentOn, connectorsMadeFrom, connectorTemplatesOn, listedIn, loadScopeState, pluginFactsOf, switchOf, } from "./component-state.js";
|
|
42
|
+
import { agentsUsing, clashingOrgSkills, clashingPlatformSkills, componentOn, connectorsMadeFrom, connectorTemplatesOn, listedIn, loadComponents, loadScopeState, pluginFactsOf, switchOf, themeIdClashes, } from "./component-state.js";
|
|
42
43
|
import { CatalogItemLock } from "./locks.js";
|
|
43
44
|
import { itemPendingError, itemRemovedError } from "./ownership.js";
|
|
44
45
|
import { PlanStale, PlatformComponents } from "./platform-components.js";
|
|
@@ -58,6 +59,7 @@ const KIND_ORDER = {
|
|
|
58
59
|
skill: 0,
|
|
59
60
|
agent_template: 1,
|
|
60
61
|
connector_template: 2,
|
|
62
|
+
theme: 3,
|
|
61
63
|
};
|
|
62
64
|
/** How long one update holds its item (renewed while it runs). */
|
|
63
65
|
const UPDATE_LOCK_TTL_MS = 60_000;
|
|
@@ -188,7 +190,17 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
188
190
|
owner: { orgId },
|
|
189
191
|
...(staged !== undefined && { target: staged }),
|
|
190
192
|
}));
|
|
191
|
-
|
|
193
|
+
// The owner sees the names of the orgs whose default a removed theme is (named like the other
|
|
194
|
+
// orgs the preview mentions, P3-14 / P3-19), but people only of its own org: other orgs'
|
|
195
|
+
// member counts were never disclosed to it. Display only: `diffHash` covers the full preview.
|
|
196
|
+
const themes = preview.themes === undefined
|
|
197
|
+
? undefined
|
|
198
|
+
: await Promise.all(preview.themes.map(async (t) => ({
|
|
199
|
+
...t,
|
|
200
|
+
pickedBy: await pickedByIn(this.db, t.id, new Set([orgId])),
|
|
201
|
+
})));
|
|
202
|
+
const owned = { ...preview, ...(themes !== undefined && { themes }) };
|
|
203
|
+
return { ...owned, orgNames: await this.orgNamesOf(owned) };
|
|
192
204
|
}
|
|
193
205
|
/**
|
|
194
206
|
* Locks, in sorted order and before the item (ruling P3-50), every live org the update may
|
|
@@ -234,6 +246,7 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
234
246
|
...(p.extension?.orgs ?? []),
|
|
235
247
|
...p.connectorOrgs.map((c) => c.orgId),
|
|
236
248
|
...p.components.flatMap((c) => [...c.pinnedOrgs, ...(c.kept ?? []).map((k) => k.orgId)]),
|
|
249
|
+
...(p.themes ?? []).flatMap((t) => t.orgDefaults.map((o) => o.orgId)),
|
|
237
250
|
]);
|
|
238
251
|
if (ids.size === 0)
|
|
239
252
|
return {};
|
|
@@ -517,7 +530,9 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
517
530
|
? "platform_skills"
|
|
518
531
|
: c.kind === "agent_template"
|
|
519
532
|
? "platform_agent_templates"
|
|
520
|
-
: "
|
|
533
|
+
: c.kind === "theme"
|
|
534
|
+
? "platform_themes"
|
|
535
|
+
: "platform_connector_templates";
|
|
521
536
|
await t
|
|
522
537
|
.updateTable(table)
|
|
523
538
|
.set({ deleted_at: now, updated_at: now })
|
|
@@ -984,6 +999,43 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
984
999
|
pinnedOrgs: [],
|
|
985
1000
|
});
|
|
986
1001
|
}
|
|
1002
|
+
// Themes (by id, canonical definition and their assets' digest)
|
|
1003
|
+
const toThemes = contents.themes;
|
|
1004
|
+
for (const th of toThemes) {
|
|
1005
|
+
const l = live.themes.find((x) => x.name === th.id);
|
|
1006
|
+
if (!l) {
|
|
1007
|
+
components.push(added("theme", th.id));
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
const same = canonicalJson(l.definition) === canonicalJson(th.theme) &&
|
|
1011
|
+
l.assetsDigest === th.assetsDigest;
|
|
1012
|
+
components.push({
|
|
1013
|
+
kind: "theme",
|
|
1014
|
+
id: l.id,
|
|
1015
|
+
name: th.id,
|
|
1016
|
+
change: same ? "same" : direction(l.catalogVersion),
|
|
1017
|
+
from: l.catalogVersion,
|
|
1018
|
+
to: to.version,
|
|
1019
|
+
updatedOnOwn: false,
|
|
1020
|
+
ownScope: l.ownScope,
|
|
1021
|
+
pinnedOrgs: [],
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
for (const l of live.themes) {
|
|
1025
|
+
if (toThemes.some((x) => x.id === l.name))
|
|
1026
|
+
continue;
|
|
1027
|
+
components.push({
|
|
1028
|
+
kind: "theme",
|
|
1029
|
+
id: l.id,
|
|
1030
|
+
name: l.name,
|
|
1031
|
+
change: "removed",
|
|
1032
|
+
from: l.catalogVersion,
|
|
1033
|
+
to: null,
|
|
1034
|
+
updatedOnOwn: false,
|
|
1035
|
+
ownScope: l.ownScope,
|
|
1036
|
+
pinnedOrgs: [],
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
987
1039
|
// The orgs where the plugin is on (a live install; builtin: every org)
|
|
988
1040
|
const onOrgs = await this.pluginOrgs(itemId, item.builtin);
|
|
989
1041
|
if (extension)
|
|
@@ -1005,6 +1057,32 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
1005
1057
|
if (conflicts.length > 0) {
|
|
1006
1058
|
throw new ApiError("plugin.skill_conflict", `The update adds skills whose names organizations already use where the plugin is on (${[...new Set(conflicts.map((c) => c.skill))].join(", ")}); remove or rename those skills first`, conflicts);
|
|
1007
1059
|
}
|
|
1060
|
+
// Themes spec §7: an update never removes the platform default theme; rule 10 (§6): an added
|
|
1061
|
+
// theme must not take an id already on where the plugin is on
|
|
1062
|
+
const removedThemes = components
|
|
1063
|
+
.filter((c) => c.kind === "theme" && c.change === "removed" && c.id !== null)
|
|
1064
|
+
.map((c) => c.id);
|
|
1065
|
+
await assertNotPlatformDefault(db, removedThemes, () => "gone");
|
|
1066
|
+
const themeConflicts = [];
|
|
1067
|
+
for (const c of components) {
|
|
1068
|
+
if (c.kind !== "theme" || c.change !== "added")
|
|
1069
|
+
continue;
|
|
1070
|
+
for (const orgId of onOrgs) {
|
|
1071
|
+
const clash = await themeIdClashes(db, orgId, null, c.name);
|
|
1072
|
+
if (clash.length > 0) {
|
|
1073
|
+
themeConflicts.push({
|
|
1074
|
+
theme: c.name,
|
|
1075
|
+
orgId,
|
|
1076
|
+
themes: clash.map((x) => x.id),
|
|
1077
|
+
plugins: clash.map((x) => x.plugin ?? "platform"),
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (themeConflicts.length > 0) {
|
|
1083
|
+
throw new ApiError("theme.id_taken", `The update adds themes whose ids are already on in organizations where the plugin is on (${[...new Set(themeConflicts.map((c) => c.theme))].join(", ")}); switch those themes off there first`, themeConflicts);
|
|
1084
|
+
}
|
|
1085
|
+
const themes = await this.themesLost(removedThemes);
|
|
1008
1086
|
// Pinned copies (in orgs where the component is on) and removed components agents use
|
|
1009
1087
|
for (const kind of ["skill", "agent_template"]) {
|
|
1010
1088
|
const mine = components.filter((c) => c.kind === kind && c.id !== null);
|
|
@@ -1145,6 +1223,8 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
1145
1223
|
reset: c.credentialsReset,
|
|
1146
1224
|
})),
|
|
1147
1225
|
connectorOrgs,
|
|
1226
|
+
// only when present: a diff without themes keeps the hash it had before themes
|
|
1227
|
+
...(themes !== undefined && { themes }),
|
|
1148
1228
|
}));
|
|
1149
1229
|
return {
|
|
1150
1230
|
itemId,
|
|
@@ -1155,18 +1235,23 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
1155
1235
|
extension,
|
|
1156
1236
|
connectors,
|
|
1157
1237
|
connectorOrgs,
|
|
1238
|
+
...(themes !== undefined && { themes }),
|
|
1158
1239
|
suggest,
|
|
1159
1240
|
contents,
|
|
1160
1241
|
dependencies,
|
|
1161
1242
|
diffHash,
|
|
1162
1243
|
};
|
|
1163
1244
|
}
|
|
1164
|
-
/**
|
|
1245
|
+
/**
|
|
1246
|
+
* The target's skill names, agent templates, connector templates and themes, from object storage
|
|
1247
|
+
* (a theme failing its checks -> 400 `theme.invalid`, themes spec §6).
|
|
1248
|
+
*/
|
|
1165
1249
|
async readContents(to) {
|
|
1166
1250
|
return {
|
|
1167
1251
|
skillNames: await catalogSkillNames(this.storage, to, to.manifest),
|
|
1168
1252
|
agents: await readCatalogAgentTemplates(this.storage, to),
|
|
1169
1253
|
templates: await readCatalogTemplates(this.storage, to),
|
|
1254
|
+
themes: await readCatalogThemes(this.storage, to),
|
|
1170
1255
|
};
|
|
1171
1256
|
}
|
|
1172
1257
|
/**
|
|
@@ -1331,7 +1416,39 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
1331
1416
|
file: r.file,
|
|
1332
1417
|
ownScope: r.scope_mode === "own",
|
|
1333
1418
|
}));
|
|
1334
|
-
|
|
1419
|
+
const themes = (await db
|
|
1420
|
+
.selectFrom("platform_themes as p")
|
|
1421
|
+
.innerJoin("platform_theme_versions as v", (j) => j.onRef("v.theme_id", "=", "p.id").onRef("v.version", "=", "p.current_version"))
|
|
1422
|
+
.leftJoin("catalog_item_versions as cv", (j) => j
|
|
1423
|
+
.onRef("cv.item_id", "=", "p.catalog_item_id")
|
|
1424
|
+
.onRef("cv.version", "=", "v.catalog_version"))
|
|
1425
|
+
.select(["p.id", "p.name", "p.scope_mode", "v.definition", "v.catalog_version", "cv.files"])
|
|
1426
|
+
.where("p.catalog_item_id", "=", itemId)
|
|
1427
|
+
.where("p.deleted_at", "is", null)
|
|
1428
|
+
.execute()).map((r) => ({
|
|
1429
|
+
id: r.id,
|
|
1430
|
+
name: r.name,
|
|
1431
|
+
definition: r.definition,
|
|
1432
|
+
catalogVersion: r.catalog_version,
|
|
1433
|
+
// no recorded files (should not happen): never equal, the update writes a new version
|
|
1434
|
+
assetsDigest: r.files === null ? null : themeAssetsDigest(r.files, r.definition),
|
|
1435
|
+
ownScope: r.scope_mode === "own",
|
|
1436
|
+
}));
|
|
1437
|
+
return { skills, agentTemplates, templates, themes };
|
|
1438
|
+
}
|
|
1439
|
+
/** The `themes` block of the preview: the orgs each of the removed themes `ids` is on in. */
|
|
1440
|
+
async themesLost(ids) {
|
|
1441
|
+
if (ids.length === 0)
|
|
1442
|
+
return undefined;
|
|
1443
|
+
const comps = await loadComponents(this.db, "theme", { ids });
|
|
1444
|
+
const st = await loadScopeState(this.db, "theme", comps);
|
|
1445
|
+
const orgs = await this.liveOrgs();
|
|
1446
|
+
const lost = new Map();
|
|
1447
|
+
for (const c of comps) {
|
|
1448
|
+
const on = orgs.filter((orgId) => componentOn(c, pluginFactsOf(st, c.itemId, orgId), listedIn(st, c.id, orgId), switchOf(st, c.id, orgId)));
|
|
1449
|
+
lost.set(c.id, new Set(on));
|
|
1450
|
+
}
|
|
1451
|
+
return themesPreview(this.db, lost);
|
|
1335
1452
|
}
|
|
1336
1453
|
/** The orgs where the plugin is on: a live install (builtin: every live org). */
|
|
1337
1454
|
async pluginOrgs(itemId, builtin) {
|
|
@@ -1398,6 +1515,7 @@ function previewOf(d) {
|
|
|
1398
1515
|
components: d.components,
|
|
1399
1516
|
extension: extensionView(d.extension),
|
|
1400
1517
|
connectorOrgs: d.connectorOrgs,
|
|
1518
|
+
...(d.themes !== undefined && { themes: d.themes }),
|
|
1401
1519
|
diffHash: d.diffHash,
|
|
1402
1520
|
};
|
|
1403
1521
|
}
|
package/dist/catalog/unshare.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentLookup } from "../agents/agent-lookup.js";
|
|
2
2
|
import type { ModelRef } from "../agents/definition.js";
|
|
3
|
+
import type { AgentAvatar } from "../agents/profile.js";
|
|
3
4
|
import { BackgroundTaskStopper } from "../background/stop.js";
|
|
4
5
|
import type { TitleSource } from "../db/kernel-schema.js";
|
|
5
6
|
import { ConversationEnvironments, type NewConversationEnvironment } from "../environments/conversation-environments.js";
|
|
@@ -53,9 +54,17 @@ export interface ConversationView {
|
|
|
53
54
|
export declare function escapeLike(s: string): string;
|
|
54
55
|
/** One agent's group in the sidebar list: the caller's newest conversations with that agent. */
|
|
55
56
|
export interface ConversationGroup {
|
|
57
|
+
/**
|
|
58
|
+
* The agent's name and the avatar half of its profile, in the agents API's shape (the sidebar
|
|
59
|
+
* draws the group title from it without reading the agents list).
|
|
60
|
+
*/
|
|
56
61
|
agent: {
|
|
57
62
|
id: string;
|
|
58
63
|
name: string;
|
|
64
|
+
profile: {
|
|
65
|
+
avatar: AgentAvatar;
|
|
66
|
+
updatedAt: string;
|
|
67
|
+
};
|
|
59
68
|
};
|
|
60
69
|
/** max(updated_at) of the group's conversations. */
|
|
61
70
|
latestAt: Date;
|
|
@@ -482,7 +482,7 @@ let ConversationsService = class ConversationsService {
|
|
|
482
482
|
LIMIT ${opts.limit + 1}
|
|
483
483
|
)
|
|
484
484
|
SELECT page.agent_id, page.latest_at, v.definition ->> 'name' AS agent_name,
|
|
485
|
-
cs.id, cs.org_id, cs.account_id, cs.title, cs.title_source, cs.selection, cs.last_seq,
|
|
485
|
+
a.profile_avatar, a.profile_updated_at, cs.id, cs.org_id, cs.account_id, cs.title, cs.title_source, cs.selection, cs.last_seq,
|
|
486
486
|
cs.created_at, cs.updated_at, cs.parent_conversation_id, cs.subagent_type,
|
|
487
487
|
cs.archived_at, cs.environment_id, cs.activity
|
|
488
488
|
FROM page
|
|
@@ -506,7 +506,14 @@ let ConversationsService = class ConversationsService {
|
|
|
506
506
|
for (const r of rows) {
|
|
507
507
|
if (current?.agent.id !== r.agent_id) {
|
|
508
508
|
current = {
|
|
509
|
-
agent: {
|
|
509
|
+
agent: {
|
|
510
|
+
id: r.agent_id,
|
|
511
|
+
name: r.agent_name ?? "",
|
|
512
|
+
profile: {
|
|
513
|
+
avatar: r.profile_avatar,
|
|
514
|
+
updatedAt: r.profile_updated_at.toISOString(),
|
|
515
|
+
},
|
|
516
|
+
},
|
|
510
517
|
latestAt: r.latest_at,
|
|
511
518
|
conversations: [],
|
|
512
519
|
more: false,
|
|
@@ -809,6 +809,7 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
809
809
|
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
810
810
|
}, z.core.$strict>>;
|
|
811
811
|
toolPolicies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
812
|
+
allow: "allow";
|
|
812
813
|
ask: "ask";
|
|
813
814
|
deny: "deny";
|
|
814
815
|
}>>>;
|