@capybara-hq/testing 0.0.0-stage → 0.1.0-rc.1

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 (89) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +40 -2
  4. package/dist/containers.d.ts +102 -0
  5. package/dist/containers.js +314 -0
  6. package/dist/dual-replica.d.ts +142 -0
  7. package/dist/dual-replica.js +270 -0
  8. package/dist/extension-config.d.ts +9 -0
  9. package/dist/extension-config.js +16 -0
  10. package/dist/fake-channel.d.ts +162 -0
  11. package/dist/fake-channel.js +325 -0
  12. package/dist/fake-e2b.d.ts +104 -0
  13. package/dist/fake-e2b.js +500 -0
  14. package/dist/fake-git-server.d.ts +57 -0
  15. package/dist/fake-git-server.js +323 -0
  16. package/dist/fake-lark.d.ts +234 -0
  17. package/dist/fake-lark.js +837 -0
  18. package/dist/fake-marketplace.d.ts +25 -0
  19. package/dist/fake-marketplace.js +58 -0
  20. package/dist/fake-mcp-server.d.ts +134 -0
  21. package/dist/fake-mcp-server.js +308 -0
  22. package/dist/fake-model-server.d.ts +28 -0
  23. package/dist/fake-model-server.js +67 -0
  24. package/dist/fake-npm-registry.d.ts +59 -0
  25. package/dist/fake-npm-registry.js +174 -0
  26. package/dist/fake-oauth-server.d.ts +63 -0
  27. package/dist/fake-oauth-server.js +284 -0
  28. package/dist/fake-openapi-server.d.ts +50 -0
  29. package/dist/fake-openapi-server.js +278 -0
  30. package/dist/fake-slack.d.ts +258 -0
  31. package/dist/fake-slack.js +654 -0
  32. package/dist/fixtures.d.ts +49 -0
  33. package/dist/fixtures.js +92 -0
  34. package/dist/global-setup.d.ts +15 -0
  35. package/dist/global-setup.js +58 -0
  36. package/dist/index.d.ts +25 -0
  37. package/dist/index.js +25 -0
  38. package/dist/kernel-helpers.d.ts +125 -0
  39. package/dist/kernel-helpers.js +336 -0
  40. package/dist/media-samples.d.ts +10 -0
  41. package/dist/media-samples.js +76 -0
  42. package/dist/model-conformance.d.ts +61 -0
  43. package/dist/model-conformance.js +287 -0
  44. package/dist/replica-runner.d.ts +1 -0
  45. package/dist/replica-runner.js +59 -0
  46. package/dist/sse.d.ts +20 -0
  47. package/dist/sse.js +87 -0
  48. package/fixtures/extensions/fake-bootstrap-a/.capybara-plugin/plugin.json +7 -0
  49. package/fixtures/extensions/fake-bootstrap-a/package.json +12 -0
  50. package/fixtures/extensions/fake-bootstrap-a/src/index.ts +62 -0
  51. package/fixtures/extensions/fake-bootstrap-b/.capybara-plugin/plugin.json +7 -0
  52. package/fixtures/extensions/fake-bootstrap-b/migrations/0001_init.sql +10 -0
  53. package/fixtures/extensions/fake-bootstrap-b/package.json +12 -0
  54. package/fixtures/extensions/fake-bootstrap-b/src/index.ts +123 -0
  55. package/fixtures/extensions/fake-connector/.capybara-plugin/plugin.json +7 -0
  56. package/fixtures/extensions/fake-connector/connectors/fake-demo.json +12 -0
  57. package/fixtures/extensions/fake-connector/package.json +13 -0
  58. package/fixtures/extensions/fake-connector/src/index.ts +477 -0
  59. package/fixtures/extensions/fake-dep-provider/.capybara-plugin/plugin.json +7 -0
  60. package/fixtures/extensions/fake-dep-provider/package.json +12 -0
  61. package/fixtures/extensions/fake-dep-provider/src/index.ts +19 -0
  62. package/fixtures/extensions/fake-dep-requirer/.capybara-plugin/plugin.json +8 -0
  63. package/fixtures/extensions/fake-dep-requirer/package.json +12 -0
  64. package/fixtures/extensions/fake-dep-requirer/src/index.ts +21 -0
  65. package/fixtures/extensions/fake-embed/.capybara-plugin/plugin.json +7 -0
  66. package/fixtures/extensions/fake-embed/package.json +12 -0
  67. package/fixtures/extensions/fake-embed/src/index.ts +207 -0
  68. package/fixtures/extensions/fake-im/.capybara-plugin/plugin.json +7 -0
  69. package/fixtures/extensions/fake-im/package.json +12 -0
  70. package/fixtures/extensions/fake-im/src/channel.ts +224 -0
  71. package/fixtures/extensions/fake-im/src/directory.ts +25 -0
  72. package/fixtures/extensions/fake-im/src/identity.ts +66 -0
  73. package/fixtures/extensions/fake-im/src/index.ts +91 -0
  74. package/fixtures/extensions/fake-object-store/.capybara-plugin/plugin.json +7 -0
  75. package/fixtures/extensions/fake-object-store/package.json +15 -0
  76. package/fixtures/extensions/fake-object-store/src/index.ts +210 -0
  77. package/fixtures/extensions/fake-openapi/.capybara-plugin/plugin.json +7 -0
  78. package/fixtures/extensions/fake-openapi/package.json +12 -0
  79. package/fixtures/extensions/fake-openapi/src/index.ts +258 -0
  80. package/fixtures/extensions/fake-storage-user/.capybara-plugin/plugin.json +7 -0
  81. package/fixtures/extensions/fake-storage-user/package.json +12 -0
  82. package/fixtures/extensions/fake-storage-user/src/index.ts +58 -0
  83. package/fixtures/extensions/fake-tools-ui/.capybara-plugin/plugin.json +7 -0
  84. package/fixtures/extensions/fake-tools-ui/connectors/fake-notes.json +11 -0
  85. package/fixtures/extensions/fake-tools-ui/migrations/0001_init.sql +7 -0
  86. package/fixtures/extensions/fake-tools-ui/package.json +12 -0
  87. package/fixtures/extensions/fake-tools-ui/skills/fake-notes/SKILL.md +7 -0
  88. package/fixtures/extensions/fake-tools-ui/src/index.ts +140 -0
  89. package/package.json +67 -3
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Fake extensions, one or more per extension point, that core tests load instead of the official
3
+ * plugins (they live in `fixtures/extensions/<name>` of this package, in the pack layout:
4
+ * `package.json`, `.capybara-plugin/plugin.json`, `src/index.ts`, plus `migrations/`, `skills/`,
5
+ * `connectors/` where they have them). Each imports only `@capybara-hq/plugin-sdk`, zod and
6
+ * third-party packages this package depends on.
7
+ *
8
+ * | name | provides / covers |
9
+ * |---|---|
10
+ * | `fake-connector` | `connector@1` kind `fake`: an MCP client for `startFakeMcpServer` (auth, one 401 retry, connectorData routes from `_meta["fake/route"]`, onToolsChanged, onElicit incl. `-32042`, resources, prompts, inspect); endpoint `{ url, headers?, notesText? }`; template `fake-demo`; deployment config `allowInsecureLoopback` (default true), `inspectAuth` (`bearer` / `api_key`), `kind` (default `fake`; `mcp` lets clients that know only the official kinds drive it) |
11
+ * | `fake-openapi` | `connector@1` kind `openapi` (the web wizard's OpenAPI flow): reads a small JSON document (`securitySchemes` as auth hints, `operations` as tools) from the endpoint's `specUrl`, calls operations over HTTP with the injected auth; endpoint `{ specUrl, baseUrl?, operations? }`; deployment config `allowInsecureLoopback` (default true) |
12
+ * | `fake-object-store` | `object-store@1` on the `startTestS3()` container (deployment config as `TestS3` gives it; POST-policy presign) |
13
+ * | `fake-tools-ui` | tool `write`, view `tasks`, a migration, skill `fake-notes`, template `fake-notes` (for fake-connector); no storage |
14
+ * | `fake-storage-user` | tools `note` / `read_note` through `ctx.storage` (`permissions.storage`) |
15
+ * | `fake-embed` | embeds `panel` (page) and `panel-conv` (conversation), static `/ui/panel.js`, token-checked routes, SSE over `ctx.bus` |
16
+ * | `fake-bootstrap-a` | `model-provider@2` with a required secret `apiKey` (pending configuration until set) |
17
+ * | `fake-bootstrap-b` | `object-store@1` kept in its own PG table; `bucket` + secret `secretKey` |
18
+ * | `fake-dep-provider` / `fake-dep-requirer` | a dependency pair on `counter@1` (the requirer's plugin.json depends on the provider) |
19
+ * | `fake-im` | a whole IM platform for `startFakeChannelServer`: `channel@1` (cards, edits, threads, webhook inbound), `identity-source@1`, `directory@1` (org config `url` of a directory book) and tool `post_announcement` (default policy ask, posts to the platform); deployment config `platformUrl`, `tenant`, `clientId`, secret `clientSecret`. Its `src/index.ts` also exports `makeFakeImChannel`, `fakeImIdentitySource`, `fakeImDirectory` for test extensions that need one part |
20
+ */
21
+ export declare const FAKE_EXTENSIONS: readonly ["fake-connector", "fake-openapi", "fake-object-store", "fake-tools-ui", "fake-storage-user", "fake-embed", "fake-bootstrap-a", "fake-bootstrap-b", "fake-dep-provider", "fake-dep-requirer", "fake-im"];
22
+ /** The name of a fake extension (also its plugin and extension name). */
23
+ export type FakeExtensionName = (typeof FAKE_EXTENSIONS)[number];
24
+ /** The plugin directory of a fake (pack layout), for `packFixture` or a packer of your own. */
25
+ export declare function fakeExtensionDir(name: FakeExtensionName): string;
26
+ /**
27
+ * The extension entry `src/index.ts` of a fake, for kernel configs that load extensions by path
28
+ * (`extensions: [fakeExtensionEntry("fake-connector")]`). `createDualReplicaHarness` loads it in
29
+ * its replicas wherever this package is installed (they run under tsx for TypeScript entries). An
30
+ * in-process kernel under vitest loads it when vitest transforms the kernel (in this repository, or
31
+ * with the core packages inlined: `server.deps.inline`); elsewhere, pack the fake with
32
+ * {@link packFixture} (Node does not strip types under node_modules).
33
+ */
34
+ export declare function fakeExtensionEntry(name: FakeExtensionName): string;
35
+ /** The result of {@link packFixture}. */
36
+ export interface PackedFixture {
37
+ /** Path of the tgz in `outDir`. */
38
+ file: string;
39
+ bytes: Uint8Array;
40
+ /** Hex sha256 of the tgz. */
41
+ sha256: string;
42
+ }
43
+ /**
44
+ * Packs a fake extension into a plugin tgz with the plugin packer (the same bytes `pnpm
45
+ * pack-plugin` would produce: single-file ESM bundle, static manifest in plugin.json, migrations,
46
+ * skills and templates), writes it to `outDir` and returns its path, bytes and sha256. Packing
47
+ * leaves no files in the fixture directory.
48
+ */
49
+ export declare function packFixture(name: FakeExtensionName, outDir: string): Promise<PackedFixture>;
@@ -0,0 +1,92 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Fake extensions, one or more per extension point, that core tests load instead of the official
6
+ * plugins (they live in `fixtures/extensions/<name>` of this package, in the pack layout:
7
+ * `package.json`, `.capybara-plugin/plugin.json`, `src/index.ts`, plus `migrations/`, `skills/`,
8
+ * `connectors/` where they have them). Each imports only `@capybara-hq/plugin-sdk`, zod and
9
+ * third-party packages this package depends on.
10
+ *
11
+ * | name | provides / covers |
12
+ * |---|---|
13
+ * | `fake-connector` | `connector@1` kind `fake`: an MCP client for `startFakeMcpServer` (auth, one 401 retry, connectorData routes from `_meta["fake/route"]`, onToolsChanged, onElicit incl. `-32042`, resources, prompts, inspect); endpoint `{ url, headers?, notesText? }`; template `fake-demo`; deployment config `allowInsecureLoopback` (default true), `inspectAuth` (`bearer` / `api_key`), `kind` (default `fake`; `mcp` lets clients that know only the official kinds drive it) |
14
+ * | `fake-openapi` | `connector@1` kind `openapi` (the web wizard's OpenAPI flow): reads a small JSON document (`securitySchemes` as auth hints, `operations` as tools) from the endpoint's `specUrl`, calls operations over HTTP with the injected auth; endpoint `{ specUrl, baseUrl?, operations? }`; deployment config `allowInsecureLoopback` (default true) |
15
+ * | `fake-object-store` | `object-store@1` on the `startTestS3()` container (deployment config as `TestS3` gives it; POST-policy presign) |
16
+ * | `fake-tools-ui` | tool `write`, view `tasks`, a migration, skill `fake-notes`, template `fake-notes` (for fake-connector); no storage |
17
+ * | `fake-storage-user` | tools `note` / `read_note` through `ctx.storage` (`permissions.storage`) |
18
+ * | `fake-embed` | embeds `panel` (page) and `panel-conv` (conversation), static `/ui/panel.js`, token-checked routes, SSE over `ctx.bus` |
19
+ * | `fake-bootstrap-a` | `model-provider@2` with a required secret `apiKey` (pending configuration until set) |
20
+ * | `fake-bootstrap-b` | `object-store@1` kept in its own PG table; `bucket` + secret `secretKey` |
21
+ * | `fake-dep-provider` / `fake-dep-requirer` | a dependency pair on `counter@1` (the requirer's plugin.json depends on the provider) |
22
+ * | `fake-im` | a whole IM platform for `startFakeChannelServer`: `channel@1` (cards, edits, threads, webhook inbound), `identity-source@1`, `directory@1` (org config `url` of a directory book) and tool `post_announcement` (default policy ask, posts to the platform); deployment config `platformUrl`, `tenant`, `clientId`, secret `clientSecret`. Its `src/index.ts` also exports `makeFakeImChannel`, `fakeImIdentitySource`, `fakeImDirectory` for test extensions that need one part |
23
+ */
24
+ export const FAKE_EXTENSIONS = [
25
+ "fake-connector",
26
+ "fake-openapi",
27
+ "fake-object-store",
28
+ "fake-tools-ui",
29
+ "fake-storage-user",
30
+ "fake-embed",
31
+ "fake-bootstrap-a",
32
+ "fake-bootstrap-b",
33
+ "fake-dep-provider",
34
+ "fake-dep-requirer",
35
+ "fake-im",
36
+ ];
37
+ /** `fixtures/extensions/` of this package (same path from `src/` and from `dist/`). */
38
+ const FIXTURES_DIR = fileURLToPath(new URL("../fixtures/extensions/", import.meta.url));
39
+ /** The plugin directory of a fake (pack layout), for `packFixture` or a packer of your own. */
40
+ export function fakeExtensionDir(name) {
41
+ return join(FIXTURES_DIR, name);
42
+ }
43
+ /**
44
+ * The extension entry `src/index.ts` of a fake, for kernel configs that load extensions by path
45
+ * (`extensions: [fakeExtensionEntry("fake-connector")]`). `createDualReplicaHarness` loads it in
46
+ * its replicas wherever this package is installed (they run under tsx for TypeScript entries). An
47
+ * in-process kernel under vitest loads it when vitest transforms the kernel (in this repository, or
48
+ * with the core packages inlined: `server.deps.inline`); elsewhere, pack the fake with
49
+ * {@link packFixture} (Node does not strip types under node_modules).
50
+ */
51
+ export function fakeExtensionEntry(name) {
52
+ return join(FIXTURES_DIR, name, "src/index.ts");
53
+ }
54
+ /**
55
+ * The plugin packer of `@capybara-hq/plugin-tools` (an optional peer dependency of this package:
56
+ * install it to use {@link packFixture}), imported lazily so only callers of {@link packFixture}
57
+ * need it. It is a peer, not a dependency, because plugin-tools depends on the kernel, which tests
58
+ * with this package; the specifier is a variable so TypeScript does not pull the packer into this
59
+ * package's build.
60
+ */
61
+ const PACKER_MODULE = "@capybara-hq/plugin-tools/pack";
62
+ async function packer() {
63
+ let mod;
64
+ try {
65
+ mod = (await import(PACKER_MODULE));
66
+ }
67
+ catch (e) {
68
+ const err = e;
69
+ const missing = err.code === "ERR_MODULE_NOT_FOUND" &&
70
+ String(err.message).includes("@capybara-hq/plugin-tools");
71
+ if (!missing)
72
+ throw e;
73
+ throw new Error("packFixture needs the plugin packer: install @capybara-hq/plugin-tools (a devDependency)");
74
+ }
75
+ if (typeof mod.packPlugin !== "function") {
76
+ throw new Error(`${PACKER_MODULE} has no packPlugin export`);
77
+ }
78
+ return mod.packPlugin;
79
+ }
80
+ /**
81
+ * Packs a fake extension into a plugin tgz with the plugin packer (the same bytes `pnpm
82
+ * pack-plugin` would produce: single-file ESM bundle, static manifest in plugin.json, migrations,
83
+ * skills and templates), writes it to `outDir` and returns its path, bytes and sha256. Packing
84
+ * leaves no files in the fixture directory.
85
+ */
86
+ export async function packFixture(name, outDir) {
87
+ if (!FAKE_EXTENSIONS.includes(name))
88
+ throw new Error(`unknown fake extension ${String(name)}`);
89
+ const pack = await packer();
90
+ const r = await pack(fakeExtensionDir(name), outDir);
91
+ return { file: r.file, bytes: new Uint8Array(await readFile(r.file)), sha256: r.sha256 };
92
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Vitest `globalSetup`: start one PostgreSQL 17 and one Valkey for the whole run and publish their
3
+ * addresses through `CAPYBARA_TEST_PG_ADMIN_URL` / `CAPYBARA_TEST_REDIS_URL`. Workers (forks pool)
4
+ * are spawned after this runs and inherit `process.env`, so `startTestPostgres()` /
5
+ * `startTestRedis({ shared: true })` in any test file see them.
6
+ *
7
+ * - `CAPYBARA_TEST_NO_SHARED=1` disables sharing: nothing starts, every file starts its own containers.
8
+ * - An address already present in the environment (e.g. a CI service container) is used as is.
9
+ * For an external Redis the allocator wraps on its `CONFIG GET databases` value (at least 2 needed;
10
+ * start it with `--databases 1024` for headroom). Where `CONFIG` is disabled the server is assumed
11
+ * to have 1024 databases, so it must be started with `--databases 1024`.
12
+ * An external PostgreSQL is not cleaned up: `cap_*` databases of crashed or killed runs (stop()
13
+ * never ran) accumulate on a long-lived server and need dropping by hand now and then.
14
+ */
15
+ export default function setup(): Promise<() => Promise<void>>;
@@ -0,0 +1,58 @@
1
+ import { PG_ADMIN_URL_ENV, REDIS_DATABASES_ENV, REDIS_URL_ENV, SHARED_REDIS_DATABASES, sharingDisabled, startPostgresContainer, startRedisContainer, withRedis, } from "./containers.js";
2
+ /**
3
+ * Vitest `globalSetup`: start one PostgreSQL 17 and one Valkey for the whole run and publish their
4
+ * addresses through `CAPYBARA_TEST_PG_ADMIN_URL` / `CAPYBARA_TEST_REDIS_URL`. Workers (forks pool)
5
+ * are spawned after this runs and inherit `process.env`, so `startTestPostgres()` /
6
+ * `startTestRedis({ shared: true })` in any test file see them.
7
+ *
8
+ * - `CAPYBARA_TEST_NO_SHARED=1` disables sharing: nothing starts, every file starts its own containers.
9
+ * - An address already present in the environment (e.g. a CI service container) is used as is.
10
+ * For an external Redis the allocator wraps on its `CONFIG GET databases` value (at least 2 needed;
11
+ * start it with `--databases 1024` for headroom). Where `CONFIG` is disabled the server is assumed
12
+ * to have 1024 databases, so it must be started with `--databases 1024`.
13
+ * An external PostgreSQL is not cleaned up: `cap_*` databases of crashed or killed runs (stop()
14
+ * never ran) accumulate on a long-lived server and need dropping by hand now and then.
15
+ */
16
+ export default async function setup() {
17
+ if (sharingDisabled())
18
+ return async () => { };
19
+ const wanted = [];
20
+ if (!process.env[PG_ADMIN_URL_ENV]) {
21
+ wanted.push([PG_ADMIN_URL_ENV, () => startPostgresContainer({ tuned: true })]);
22
+ }
23
+ const externalRedis = process.env[REDIS_URL_ENV];
24
+ const setDatabases = externalRedis !== undefined && externalRedis !== "";
25
+ if (setDatabases) {
26
+ process.env[REDIS_DATABASES_ENV] = String(await redisDatabases(externalRedis));
27
+ }
28
+ else {
29
+ wanted.push([REDIS_URL_ENV, () => startRedisContainer({ databases: SHARED_REDIS_DATABASES })]);
30
+ }
31
+ const started = await Promise.allSettled(wanted.map(([, start]) => start()));
32
+ const owned = started.flatMap((s) => (s.status === "fulfilled" ? [s.value] : []));
33
+ const failed = started.find((s) => s.status === "rejected");
34
+ if (failed) {
35
+ await Promise.allSettled(owned.map((c) => c.stop()));
36
+ throw failed.reason;
37
+ }
38
+ wanted.forEach(([env], i) => {
39
+ process.env[env] = owned[i].url;
40
+ });
41
+ return async () => {
42
+ for (const [env] of wanted)
43
+ delete process.env[env];
44
+ if (setDatabases)
45
+ delete process.env[REDIS_DATABASES_ENV];
46
+ await Promise.allSettled(owned.map((c) => c.stop()));
47
+ };
48
+ }
49
+ /** `CONFIG GET databases` of an external Redis; {@link SHARED_REDIS_DATABASES} when CONFIG is disabled. */
50
+ async function redisDatabases(url) {
51
+ const reply = await withRedis(url, (r) => r.config("GET", "databases").catch(() => undefined));
52
+ const n = reply === undefined ? SHARED_REDIS_DATABASES : Number(reply[1]);
53
+ if (!Number.isInteger(n) || n < 2) {
54
+ throw new Error(`${REDIS_URL_ENV} must point at a Redis with at least 2 databases (got ${reply?.[1]}); ` +
55
+ "start it with --databases 1024");
56
+ }
57
+ return n;
58
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Kernel names an extension's kernel-level tests need, re-exported so they import only this
3
+ * package and the SDK: the database helpers and types, and model-visible texts, queue names and
4
+ * keys the kernel owns (assert on them instead of copying their values).
5
+ */
6
+ export { type ConversationEvent, createDb, gatewayReplicasKey, type Kernel, type KernelConfig, type KernelDb, kernelDb, MAINTENANCE_QUEUE_NAME, runJobId, SANDBOX_LOCK_TTL_MS, SANDBOX_RECREATED_NOTICE, SANDBOX_STARTED_NOTICE, SANDBOX_SUSPEND_JOB, UNKNOWN_RESULT_TEXT, } from "@capybara-hq/kernel";
7
+ export { createFreshDatabase, startTestPostgres, startTestRedis, startTestS3, TEST_S3_IMAGE, type TestContainer, type TestS3, } from "./containers.js";
8
+ export { createDualReplicaHarness, type DualReplicaOptions, type ExtraReplicaHandle, type Harness, REPLICA_HEADER, type ReplicaHandle, type ReplicaResponse, replicaOf, } from "./dual-replica.js";
9
+ export { assertExtensionSecretsReachable, extensionDeploymentSecretValues, } from "./extension-config.js";
10
+ export { FAKE_CHANNEL_BAD_SECRET, FAKE_WEBHOOK_SIGNATURE_HEADER, type FakeChannelPost, type FakeChannelSent, type FakeChannelServer, type FakeChannelServerOptions, type FakeChannelStream, type FakeChannelUser, type FakeDirectoryBook, type FakeSenderProfile, fakeCardAction, fakeDirectoryMember, fakeDm, fakeGroupMessage, fakeWebhookChallenge, fakeWebhookEvents, signFakeWebhook, startFakeChannelServer, } from "./fake-channel.js";
11
+ export { type FakeE2BCall, type FakeE2BMatch, type FakeE2BOptions, type FakeE2BSandbox, type FakeE2BServer, startFakeE2B, } from "./fake-e2b.js";
12
+ export { type FakeGitCommitOptions, type FakeGitRequest, type FakeGitServer, startFakeGitServer, } from "./fake-git-server.js";
13
+ export { encryptLarkWebhook, type FakeLarkAck, type FakeLarkApi, type FakeLarkButton, type FakeLarkCall, type FakeLarkDepartment, type FakeLarkFailure, type FakeLarkMessage, type FakeLarkOptions, type FakeLarkServer, type FakeLarkUser, findLarkButton, larkCardAction, larkMessageEvent, signLarkWebhook, startFakeLark, } from "./fake-lark.js";
14
+ export { type FakeMarketplace, type FakeMarketplaceRequest, startFakeMarketplace, } from "./fake-marketplace.js";
15
+ export { type FakeMcpCallInfo, type FakeMcpHandlerContext, type FakeMcpPrompt, type FakeMcpRequest, type FakeMcpResource, FakeMcpRpcError, type FakeMcpServer, type FakeMcpServerOptions, type FakeMcpTool, startFakeMcpServer, } from "./fake-mcp-server.js";
16
+ export { type FakeModelServer, type FakeRequest, type FakeResponse, startFakeModelServer, } from "./fake-model-server.js";
17
+ export { type FakeNpmPackage, type FakeNpmRegistry, npmProvenanceAttestations, startFakeNpmRegistry, } from "./fake-npm-registry.js";
18
+ export { type FakeOAuthClient, type FakeOAuthServer, type FakeOAuthServerOptions, type FakeTokenRequest, startFakeOAuthServer, } from "./fake-oauth-server.js";
19
+ export { defaultFakeOpenApiSpec, type FakeOpenApiRequest, type FakeOpenApiServer, type FakeOpenApiServerOptions, type FakePet, startFakeOpenApiServer, } from "./fake-openapi-server.js";
20
+ export { type FakeSlackAck, type FakeSlackCall, type FakeSlackFailure, type FakeSlackMessage, type FakeSlackOptions, type FakeSlackServer, type FakeSlackUser, findSlackButton, signSlackWebhook, slackBlockAction, slackDmEvent, slackEventCallback, slackInteractiveForm, slackMentionEvent, startFakeSlack, } from "./fake-slack.js";
21
+ export { FAKE_EXTENSIONS, type FakeExtensionName, fakeExtensionDir, fakeExtensionEntry, type PackedFixture, packFixture, } from "./fixtures.js";
22
+ export { cardRecorded, cookieRequest, createAgent, createConversation, createLocalAccount, createOrg, eventsOf, joinOrg, type KernelRequest, type KernelRequestOptions, type KernelResponse, login, pidOwningLocalPort, postMessage, sleep, startTestKernel, TEST_PASSWORD, testKernelConfig, truncateTables, waitFor, waitForEvent, waitForTerminal, } from "./kernel-helpers.js";
23
+ export { isPngBase64, pdfBase64, pdfSampleWord, redPngBase64, solidPngBase64, } from "./media-samples.js";
24
+ export { type ConformanceTarget, checkDeclaredParameters, describeModelProviderConformance, MEDIA_SAMPLES, thinkingReplayParams, } from "./model-conformance.js";
25
+ export { openSse, type SseClient, type SseMessage } from "./sse.js";
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Kernel names an extension's kernel-level tests need, re-exported so they import only this
3
+ * package and the SDK: the database helpers and types, and model-visible texts, queue names and
4
+ * keys the kernel owns (assert on them instead of copying their values).
5
+ */
6
+ export { createDb, gatewayReplicasKey, kernelDb, MAINTENANCE_QUEUE_NAME, runJobId, SANDBOX_LOCK_TTL_MS, SANDBOX_RECREATED_NOTICE, SANDBOX_STARTED_NOTICE, SANDBOX_SUSPEND_JOB, UNKNOWN_RESULT_TEXT, } from "@capybara-hq/kernel";
7
+ export { createFreshDatabase, startTestPostgres, startTestRedis, startTestS3, TEST_S3_IMAGE, } from "./containers.js";
8
+ export { createDualReplicaHarness, REPLICA_HEADER, replicaOf, } from "./dual-replica.js";
9
+ export { assertExtensionSecretsReachable, extensionDeploymentSecretValues, } from "./extension-config.js";
10
+ export { FAKE_CHANNEL_BAD_SECRET, FAKE_WEBHOOK_SIGNATURE_HEADER, fakeCardAction, fakeDirectoryMember, fakeDm, fakeGroupMessage, fakeWebhookChallenge, fakeWebhookEvents, signFakeWebhook, startFakeChannelServer, } from "./fake-channel.js";
11
+ export { startFakeE2B, } from "./fake-e2b.js";
12
+ export { startFakeGitServer, } from "./fake-git-server.js";
13
+ export { encryptLarkWebhook, findLarkButton, larkCardAction, larkMessageEvent, signLarkWebhook, startFakeLark, } from "./fake-lark.js";
14
+ export { startFakeMarketplace, } from "./fake-marketplace.js";
15
+ export { FakeMcpRpcError, startFakeMcpServer, } from "./fake-mcp-server.js";
16
+ export { startFakeModelServer, } from "./fake-model-server.js";
17
+ export { npmProvenanceAttestations, startFakeNpmRegistry, } from "./fake-npm-registry.js";
18
+ export { startFakeOAuthServer, } from "./fake-oauth-server.js";
19
+ export { defaultFakeOpenApiSpec, startFakeOpenApiServer, } from "./fake-openapi-server.js";
20
+ export { findSlackButton, signSlackWebhook, slackBlockAction, slackDmEvent, slackEventCallback, slackInteractiveForm, slackMentionEvent, startFakeSlack, } from "./fake-slack.js";
21
+ export { FAKE_EXTENSIONS, fakeExtensionDir, fakeExtensionEntry, packFixture, } from "./fixtures.js";
22
+ export { cardRecorded, cookieRequest, createAgent, createConversation, createLocalAccount, createOrg, eventsOf, joinOrg, login, pidOwningLocalPort, postMessage, sleep, startTestKernel, TEST_PASSWORD, testKernelConfig, truncateTables, waitFor, waitForEvent, waitForTerminal, } from "./kernel-helpers.js";
23
+ export { isPngBase64, pdfBase64, pdfSampleWord, redPngBase64, solidPngBase64, } from "./media-samples.js";
24
+ export { checkDeclaredParameters, describeModelProviderConformance, MEDIA_SAMPLES, thinkingReplayParams, } from "./model-conformance.js";
25
+ export { openSse } from "./sse.js";
@@ -0,0 +1,125 @@
1
+ import { type ConversationEvent, type Kernel, type KernelConfig, type KernelDb, type KernelHttp, type LogLevel } from "@capybara-hq/kernel";
2
+ import { type Kysely } from "kysely";
3
+ /**
4
+ * Helpers for extension tests that run a real kernel: in process (`startTestKernel` +
5
+ * `http.inject`) or as two replicas (`createDualReplicaHarness`). They only use the kernel's public
6
+ * API and HTTP routes, so a plugin's tests can use them without reaching into kernel sources.
7
+ */
8
+ /** The password `createLocalAccount` gives accounts (and `login` / `joinOrg` use by default). */
9
+ export declare const TEST_PASSWORD = "correct horse battery";
10
+ export declare const sleep: (ms: number) => Promise<void>;
11
+ /** Polls `fn` until it returns a value other than null / undefined / false; throws on timeout. */
12
+ export declare function waitFor<T>(fn: () => Promise<T | null | undefined | false>, timeoutMs?: number, label?: string, stepMs?: number): Promise<T>;
13
+ /**
14
+ * A kernel config for tests: the given PG / Redis, a fixed master key, an ephemeral HTTP port,
15
+ * auto-approved extension permissions, short run-lock timings and the official marketplace off
16
+ * (it is on by default and its address is on the internet). `overrides` are top-level config keys
17
+ * and win; `runtime` is merged into the defaults, and `marketplaces` too, so other marketplace
18
+ * settings keep the official one off unless `marketplaces.official` turns it on.
19
+ */
20
+ export declare function testKernelConfig(urls: {
21
+ pg: string;
22
+ redis: string;
23
+ }, overrides: Record<string, unknown> & {
24
+ extensions: string[];
25
+ runtime?: Record<string, unknown>;
26
+ marketplaces?: Record<string, unknown> & {
27
+ official?: Record<string, unknown>;
28
+ };
29
+ }): KernelConfig;
30
+ /**
31
+ * Throws when `config` would make a test kernel fetch the real official marketplace (enabled, at its
32
+ * default address on the internet): tests turn it off or point it at a fake. A kernel helper calls
33
+ * it before creating anything.
34
+ */
35
+ export declare function assertOfflineOfficialMarketplace(config: KernelConfig, allow?: boolean): void;
36
+ /**
37
+ * Creates and starts an in-process kernel. Logs are discarded unless `log` is given (JSON lines at
38
+ * `log.level` and above go to `log.write`, e.g. to assert a secret never reaches the log).
39
+ * Extensions named by package resolve from `resolveFrom` (default `process.cwd()`). Stop it with
40
+ * `stop()`.
41
+ */
42
+ export declare function startTestKernel(config: KernelConfig, opts?: {
43
+ resolveFrom?: string;
44
+ log?: {
45
+ level: LogLevel;
46
+ write: (line: string) => void;
47
+ };
48
+ /** Only for a test about the real default: allow the official marketplace at its real URL. */
49
+ allowOfficialMarketplace?: boolean;
50
+ }): Promise<Kernel>;
51
+ /** Options of a {@link KernelRequest} call (`http.inject` options; `csrf: false` drops the header). */
52
+ export type KernelRequestOptions = Parameters<KernelHttp["inject"]>[0] & {
53
+ csrf?: boolean;
54
+ };
55
+ export type KernelResponse = Awaited<ReturnType<KernelHttp["inject"]>>;
56
+ /**
57
+ * A signed-in user's request function: `http.inject` with the session cookies (kept from each
58
+ * response) and the `X-Capybara-Csrf` header.
59
+ */
60
+ export type KernelRequest = (opts: KernelRequestOptions) => Promise<KernelResponse>;
61
+ /** A request function with its own cookie jar (signed out until a response sets cookies). */
62
+ export declare function cookieRequest(http: KernelHttp): KernelRequest;
63
+ /** Creates a local account with {@link TEST_PASSWORD}; returns its id. */
64
+ export declare function createLocalAccount(db: KernelDb, login: string, displayName?: string): Promise<string>;
65
+ /** Signs in over HTTP and returns the user's request function. */
66
+ export declare function login(k: Pick<Kernel, "http">, name: string, password?: string): Promise<KernelRequest>;
67
+ /** Creates an org as `req`'s user (who becomes its owner); returns its id. */
68
+ export declare function createOrg(req: KernelRequest, name?: string): Promise<string>;
69
+ /**
70
+ * A new account joins the org through an invitation (created by `owner`, accepted with `login`
71
+ * and {@link TEST_PASSWORD}); returns the account id and its signed-in request function.
72
+ */
73
+ export declare function joinOrg(k: Pick<Kernel, "http">, owner: KernelRequest, orgId: string, name: string, role?: "member" | "admin"): Promise<{
74
+ accountId: string;
75
+ req: KernelRequest;
76
+ }>;
77
+ /**
78
+ * Creates an org-visible agent; returns its id. Without `tools` the agent has none (model-fake then
79
+ * answers ordinary messages instead of calling the first tool).
80
+ */
81
+ export declare function createAgent(req: KernelRequest, orgId: string, opts?: {
82
+ model?: {
83
+ provider: string;
84
+ model: string;
85
+ };
86
+ tools?: unknown[];
87
+ }): Promise<string>;
88
+ /** Creates a conversation with the agent; returns its id. */
89
+ export declare function createConversation(req: KernelRequest, orgId: string, agentId: string): Promise<string>;
90
+ /** Posts a user message; returns its event seq. */
91
+ export declare function postMessage(req: KernelRequest, orgId: string, conversationId: string, text: string): Promise<number>;
92
+ /** Every event of a conversation, in seq order (read from the database). */
93
+ export declare function eventsOf(db: KernelDb, conversationId: string): Promise<ConversationEvent[]>;
94
+ /** Waits for the first event matching `pred` and returns it. */
95
+ export declare function waitForEvent(db: KernelDb, conversationId: string, pred: (e: ConversationEvent) => boolean, timeoutMs?: number, label?: string): Promise<ConversationEvent>;
96
+ /** Waits for the conversation's `n`-th terminal event (run_finished / run_failed / run_aborted). */
97
+ export declare function waitForTerminal(db: KernelDb, conversationId: string, n?: number, timeoutMs?: number): Promise<ConversationEvent[]>;
98
+ /**
99
+ * Waits until an IM card's message id is recorded (`channel_card_messages`). The kernel writes it in
100
+ * the transaction after the card is sent; a click before that is treated as "not a card of this
101
+ * conversation", so a test waits for this before it clicks, not only for the platform to get it.
102
+ */
103
+ export declare function cardRecorded(db: KernelDb, card: {
104
+ messageId: string;
105
+ } | {
106
+ kind: "approval" | "question";
107
+ }, timeoutMs?: number): Promise<void>;
108
+ /**
109
+ * `TRUNCATE <tables> CASCADE` between tests, retried on deadlock / lock timeout: a kernel with the
110
+ * worker role keeps scanning in the background (channel supervisor, deliveries, maintenance), and a
111
+ * background join holding one table while waiting for another deadlocks with TRUNCATE's ordered
112
+ * ACCESS EXCLUSIVE locks. TRUNCATE is atomic, so a retry is safe. `tables` is inlined into the SQL:
113
+ * pass only table names written in the test.
114
+ */
115
+ export declare function truncateTables<DB>(db: Kysely<DB>, tables: string): Promise<void>;
116
+ /**
117
+ * Finds which of `pids` owns the local TCP port `port` (the client side of a connection). Dual
118
+ * replica tests use it to map a fake platform's long-connection peer port (`socketPeerPorts()`) to
119
+ * a replica process: a real extension connecting to a fake platform carries no pid marker.
120
+ *
121
+ * Linux reads `/proc/net/tcp{,6}` for the socket inode and looks for it under `/proc/<pid>/fd`,
122
+ * falling back to `ss -tnp`, then `lsof`; other platforms (macOS) use `lsof`. Undefined when not
123
+ * found.
124
+ */
125
+ export declare function pidOwningLocalPort(port: number, pids: readonly number[]): Promise<number | undefined>;