From 870179723e23c06f77bba0bd2df4d3cf6f6ad640 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 8 Aug 2026 15:06:42 -0700 Subject: [PATCH 1/3] Add tests for creating agents and browsing their instances Cover the create-agent-definition route's validation and error mapping, the agent-workflow builder's single-step contract, and the Agents page's search, tab, and unlinked-instance logic. --- apps/web/test/agents-directory.test.ts | 116 +++++++++++++ apps/web/test/pages.test.tsx | 95 +++++++++-- .../test/agent-workflow.test.ts | 85 ++++++++++ packages/agent-directory/test/routes.test.ts | 157 ++++++++++++++++++ .../agent-directory/test/validation.test.ts | 59 +++++++ 5 files changed, 496 insertions(+), 16 deletions(-) create mode 100644 apps/web/test/agents-directory.test.ts create mode 100644 packages/agent-directory/test/agent-workflow.test.ts create mode 100644 packages/agent-directory/test/routes.test.ts create mode 100644 packages/agent-directory/test/validation.test.ts diff --git a/apps/web/test/agents-directory.test.ts b/apps/web/test/agents-directory.test.ts new file mode 100644 index 000000000..32d864109 --- /dev/null +++ b/apps/web/test/agents-directory.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, test } from "bun:test"; + +import type { AgentDefinition, AgentInstance } from "../src/agents-api"; +import { + definitionsById, + filterDefinitions, + filterInstances, + isOrphanedInstance, + purposeAgentDefinitions, + purposeAgentInstances, +} from "../src/pages/agents-directory"; + +const researcher: AgentDefinition = { + id: "wfd_1", + tenantId: "tenant_1", + name: "Researcher", + description: "Answers research questions", + currentVersion: "1", + status: "deployed", + createdAt: "2026-08-05T11:00:00.000Z", + updatedAt: "2026-08-05T11:00:00.000Z", +}; + +const channelHostDefinition: AgentDefinition = { + ...researcher, + id: "wfd_2", + name: "ins-cd03d8e3", + description: null, +}; + +const instance: AgentInstance = { + id: "ins_1", + definitionId: "wfd_1", + definitionName: "Researcher", + tenantId: "tenant_1", + address: "ins_1@acme.localhost", + status: "running", + createdAt: "2026-08-05T11:00:00.000Z", + updatedAt: "2026-08-05T11:00:00.000Z", +}; + +const channelHostInstance: AgentInstance = { + ...instance, + id: "ins_2", + definitionId: "wfd_2", + definitionName: "ins-cd03d8e3", +}; + +describe("purposeAgentDefinitions", () => { + test("drops the chat anchor machinery's channel-host definitions", () => { + const result = purposeAgentDefinitions([researcher, channelHostDefinition]); + expect(result).toEqual([researcher]); + }); +}); + +describe("purposeAgentInstances", () => { + test("drops channel-host instances", () => { + const result = purposeAgentInstances([instance, channelHostInstance]); + expect(result).toEqual([instance]); + }); +}); + +describe("filterDefinitions", () => { + test("matches by name", () => { + expect(filterDefinitions([researcher], "research")).toEqual([researcher]); + }); + + test("matches by description", () => { + expect(filterDefinitions([researcher], "questions")).toEqual([researcher]); + }); + + test("is case-insensitive", () => { + expect(filterDefinitions([researcher], "RESEARCHER")).toEqual([researcher]); + }); + + test("excludes a definition matching neither field", () => { + expect(filterDefinitions([researcher], "nonexistent")).toEqual([]); + }); + + test("an empty query returns everything unfiltered", () => { + expect(filterDefinitions([researcher], " ")).toEqual([researcher]); + }); + + test("never matches on the raw id", () => { + expect(filterDefinitions([researcher], "wfd_1")).toEqual([]); + }); +}); + +describe("filterInstances", () => { + test("matches by the instance's definition name", () => { + expect(filterInstances([instance], "research")).toEqual([instance]); + }); + + test("never matches on the raw id or address", () => { + expect(filterInstances([instance], "ins_1")).toEqual([]); + expect(filterInstances([instance], "acme.localhost")).toEqual([]); + }); + + test("preserves extra fields callers have attached", () => { + const augmented = { ...instance, orphaned: false } as const; + expect(filterInstances([augmented], "research")).toEqual([augmented]); + }); +}); + +describe("orphan detection", () => { + test("an instance whose definition is present is not orphaned", () => { + const byId = definitionsById([researcher]); + expect(isOrphanedInstance(instance, byId)).toBe(false); + }); + + test("an instance whose definition is absent is orphaned", () => { + const byId = definitionsById([researcher]); + const orphan: AgentInstance = { ...instance, definitionId: "wfd_gone" }; + expect(isOrphanedInstance(orphan, byId)).toBe(true); + }); +}); diff --git a/apps/web/test/pages.test.tsx b/apps/web/test/pages.test.tsx index 7a2a7bc0b..98a8452a5 100644 --- a/apps/web/test/pages.test.tsx +++ b/apps/web/test/pages.test.tsx @@ -6,9 +6,13 @@ import { describe, expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; import type { ArtifactSummary } from "@corbits/artifact-ui"; -import type { Channel } from "@corbits/chat-ui"; import type { APIQuery, Approval, WorkflowRun } from "../src/api"; +import type { + AgentDefinition, + AgentDirectoryData, + AgentInstance, +} from "../src/agents-api"; import { AgentsPage } from "../src/pages/agents-page"; import { ApprovalsPage } from "../src/pages/approvals-page"; import { HomePage } from "../src/pages/home-page"; @@ -53,7 +57,10 @@ describe("empty states", () => { test("agents reports a missing session instead of empty panels", () => { const markup = renderToStaticMarkup( - , + undefined} + />, ); expect(markup).toContain("Sign in required"); }); @@ -162,30 +169,86 @@ describe("live data", () => { expect(markup).toContain("Signups export"); }); - const channel: Channel = { - id: "chan_1", - title: "general", - kind: "channel", - pinned: false, - participants: [{ address: "echo@acme.localhost", handle: "echo" }], + const definition: AgentDefinition = { + id: "wfd_1", + tenantId: "tenant_1", + name: "Researcher", + description: "Answers research questions", + currentVersion: "1", + status: "deployed", + createdAt: "2026-08-05T11:00:00.000Z", + updatedAt: "2026-08-05T11:00:00.000Z", }; + const instance: AgentInstance = { + id: "ins_1", + definitionId: "wfd_1", + definitionName: "Researcher", + tenantId: "tenant_1", + address: "ins_1@acme.localhost", + status: "running", + createdAt: "2026-08-05T11:00:00.000Z", + updatedAt: "2026-08-05T11:00:00.000Z", + }; + const directoryData: AgentDirectoryData = { + tenantId: "tenant_1", + definitions: [definition], + instances: [instance], + models: [], + }; + + test("agents lists definitions by name and description, never a raw id", () => { + const markup = renderToStaticMarkup( + undefined} + />, + ); + expect(markup).toContain("Researcher"); + expect(markup).toContain("Answers research questions"); + expect(markup).not.toContain("wfd_1"); + }); - test("agents lists channels and their participants by handle, not raw address", () => { + test("agents never renders an instance's mailbox address as visible text", () => { const markup = renderToStaticMarkup( undefined} + initialTab="instances" />, ); - expect(markup).toContain("general"); - expect(markup).toContain("@echo"); - expect(markup).not.toContain("echo@acme.localhost"); + expect(markup).toContain("Researcher"); + expect(markup).not.toContain("ins_1@acme.localhost"); }); - test("agents says there's no channel to invite into", () => { + test("agents flags an instance whose definition is not in the listing", () => { + const orphan: AgentInstance = { + ...instance, + id: "ins_2", + definitionId: "wfd_missing", + }; const markup = renderToStaticMarkup( - , + undefined} + initialTab="instances" + />, + ); + expect(markup).toContain("Unlinked definition"); + }); + + test("agents says there are no agents yet", () => { + const markup = renderToStaticMarkup( + undefined} + />, ); - expect(markup).toContain("No channel to invite an agent into"); + expect(markup).toContain("No agents yet"); }); test("home counts what the hub reports", () => { diff --git a/packages/agent-directory/test/agent-workflow.test.ts b/packages/agent-directory/test/agent-workflow.test.ts new file mode 100644 index 000000000..67b205010 --- /dev/null +++ b/packages/agent-directory/test/agent-workflow.test.ts @@ -0,0 +1,85 @@ +// Tests for this package's own contract: the definition shape a +// hand-authored agent commits to, its serialization guarantees, and +// its boundary errors. The platform's own hydration/deploy validation +// is its business, not re-proven here. + +import { expect, test } from "bun:test"; +import type { StepPrimitive, WorkflowDefinition } from "@intx/workflow"; + +import { + AGENT_DEFINITION_STEP_ID, + buildAgentDefinitionWorkflow, + serializeAgentDefinitionWorkflow, +} from "../src/agent-workflow"; + +const INPUT = { + handle: "research-buddy", + tenantDomain: "example.test", + description: "Answers research questions", + systemPrompt: "You are a careful research assistant.", +} as const; + +function agentStep(definition: WorkflowDefinition): StepPrimitive { + const primitive = definition.steps[AGENT_DEFINITION_STEP_ID]; + if (primitive === undefined || primitive.kind !== "step") { + throw new Error( + `definition has no step primitive named ${AGENT_DEFINITION_STEP_ID}`, + ); + } + return primitive; +} + +test("the definition has exactly one step, so a launch stays conversational", () => { + const definition = buildAgentDefinitionWorkflow(INPUT); + expect(definition.stepOrder).toEqual([AGENT_DEFINITION_STEP_ID]); + expect(Object.keys(definition.steps)).toEqual([AGENT_DEFINITION_STEP_ID]); +}); + +test("the system prompt and description land on the step's agent", () => { + const definition = buildAgentDefinitionWorkflow(INPUT); + const step = agentStep(definition); + expect(step.agent.systemPrompt).toBe(INPUT.systemPrompt); + expect(step.agent.description).toBe(INPUT.description); +}); + +test("an omitted model leaves the agent with no inference sources", () => { + const definition = buildAgentDefinitionWorkflow(INPUT); + const step = agentStep(definition); + expect(step.agent.inference.sources).toEqual([]); +}); + +test("a supplied model becomes the agent's one inference source", () => { + const definition = buildAgentDefinitionWorkflow({ + ...INPUT, + model: "claude-sonnet-test", + }); + const step = agentStep(definition); + expect(step.agent.inference.sources).toEqual([ + { provider: "catalog", model: "claude-sonnet-test" }, + ]); +}); + +test("the trigger address is derived from the handle and tenant domain", () => { + const definition = buildAgentDefinitionWorkflow(INPUT); + expect(definition.triggers).toEqual([ + { type: "mail", to: "research-buddy@example.test" }, + ]); +}); + +test("an empty handle is rejected", () => { + expect(() => buildAgentDefinitionWorkflow({ ...INPUT, handle: "" })).toThrow( + /non-empty handle/, + ); +}); + +test("an empty system prompt is rejected", () => { + expect(() => + buildAgentDefinitionWorkflow({ ...INPUT, systemPrompt: "" }), + ).toThrow(/non-empty systemPrompt/); +}); + +test("serialization round-trips through JSON byte-faithfully", () => { + const definition = buildAgentDefinitionWorkflow(INPUT); + const json = serializeAgentDefinitionWorkflow(definition); + expect(JSON.parse(json)).toEqual(JSON.parse(JSON.stringify(definition))); +}); diff --git a/packages/agent-directory/test/routes.test.ts b/packages/agent-directory/test/routes.test.ts new file mode 100644 index 000000000..cab7ac5bd --- /dev/null +++ b/packages/agent-directory/test/routes.test.ts @@ -0,0 +1,157 @@ +// Route-level tests cover this package's own wiring: request parsing, +// grant gating, and error-envelope mapping. The definition-projection +// path (`ensureWorkflowDefinitionForAsset` + the read-back query) is +// `@intx/hub-sessions`/`@intx/db` machinery already covered upstream — +// re-proving it here against a hand-rolled fake drizzle db would be +// coverage theater, not a meaningful test of this package's code. + +import { expect, test } from "bun:test"; +import { Hono } from "hono"; +import type { MiddlewareHandler } from "hono"; + +import type { TenantEnv } from "@intx/hub-api"; +import { AssetServiceError } from "@intx/hub-sessions"; +import type { AssetService } from "@intx/hub-sessions"; +import type { DB } from "@intx/db"; + +import { createAgentDefinitionRoutes } from "../src/routes"; + +const TENANT = { + id: "tnt_1", + name: "Acme", + slug: "acme", + domain: "acme.example", + parentId: null, + config: null, + createdAt: new Date(), + updatedAt: new Date(), +}; + +const PRINCIPAL = { + id: "prn_1", + tenantId: TENANT.id, + kind: "user" as const, + refId: "prn_1", + status: "active" as const, + createdAt: new Date(), + updatedAt: new Date(), +}; + +function fakeAssetService(overrides: Partial = {}): AssetService { + return { + createAsset: () => { + throw new Error("createAsset not stubbed for this test"); + }, + populateAsset: () => Promise.resolve({ commitSha: "deadbeef" }), + readAssetBlob: () => { + throw new Error("not used in these tests"); + }, + listAssetBlobs: () => { + throw new Error("not used in these tests"); + }, + ...overrides, + }; +} + +// Never reached on either path these tests exercise: the 400 fails +// before any dependency call, and the 409 fails inside `createAsset` +// before `db` is ever touched. +const UNUSED_DB = {} as DB["db"]; + +function buildApp(assetService: AssetService): Hono { + const routes = createAgentDefinitionRoutes({ + db: UNUSED_DB, + assetService, + requireGrant: () => async (_c, next) => { + await next(); + }, + }); + const asPrincipal: MiddlewareHandler = async (c, next) => { + c.set("tenant", TENANT); + c.set("principal", PRINCIPAL); + await next(); + }; + const app = new Hono(); + app.use("*", asPrincipal); + app.route("/", routes); + return app; +} + +async function post(app: Hono, body: unknown): Promise { + return app.request("/", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + }); +} + +test("a malformed body is rejected with a field-scoped 400", async () => { + const app = buildApp(fakeAssetService()); + const response = await post(app, { + name: "", + handle: "Not Kebab", + systemPrompt: "hello", + }); + expect(response.status).toBe(400); + const body = (await response.json()) as { error: { message: string } }; + expect(body.error.message).toContain("invalid agent definition"); +}); + +test("a missing system prompt is rejected before any asset is created", async () => { + let createCalled = false; + const app = buildApp( + fakeAssetService({ + createAsset: () => { + createCalled = true; + throw new Error("should never be called"); + }, + }), + ); + const response = await post(app, { + name: "Research Buddy", + handle: "research-buddy", + }); + expect(response.status).toBe(400); + expect(createCalled).toBe(false); +}); + +test("a duplicate handle surfaces as a 409, not a 500", async () => { + const app = buildApp( + fakeAssetService({ + createAsset: () => { + throw new AssetServiceError( + "duplicate_asset", + 'an asset named "research-buddy" already exists', + ); + }, + }), + ); + const response = await post(app, { + name: "Research Buddy", + handle: "research-buddy", + systemPrompt: "You are a careful research assistant.", + }); + expect(response.status).toBe(409); + const body = (await response.json()) as { error: { code: string } }; + expect(body.error.code).toBe("conflict"); +}); + +test("an unrelated asset-service failure is not swallowed as a conflict", async () => { + const app = buildApp( + fakeAssetService({ + createAsset: () => { + throw new Error("the git backend is unreachable"); + }, + }), + ); + const response = await post(app, { + name: "Research Buddy", + handle: "research-buddy", + systemPrompt: "You are a careful research assistant.", + }); + // Hono's default error handler turns an uncaught throw into a 500 + // rather than the 409 the duplicate-asset path returns — proving this + // route re-throws instead of misclassifying every asset-service + // failure as a handle conflict. + expect(response.status).toBe(500); +}); diff --git a/packages/agent-directory/test/validation.test.ts b/packages/agent-directory/test/validation.test.ts new file mode 100644 index 000000000..edb5245a7 --- /dev/null +++ b/packages/agent-directory/test/validation.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from "bun:test"; +import { type } from "arktype"; + +import { CreateAgentDefinitionInput } from "../src/validation"; + +const VALID = { + name: "Research Buddy", + handle: "research-buddy", + systemPrompt: "You are a careful research assistant.", +} as const; + +test("a well-formed submission parses", () => { + const result = CreateAgentDefinitionInput(VALID); + expect(result instanceof type.errors).toBe(false); +}); + +test("a blank name is rejected, not silently trimmed to empty", () => { + const result = CreateAgentDefinitionInput({ ...VALID, name: " " }); + expect(result instanceof type.errors).toBe(true); +}); + +test("a handle with uppercase letters is rejected", () => { + const result = CreateAgentDefinitionInput({ + ...VALID, + handle: "Research-Buddy", + }); + expect(result instanceof type.errors).toBe(true); +}); + +test("a handle with a leading hyphen is rejected", () => { + const result = CreateAgentDefinitionInput({ + ...VALID, + handle: "-research-buddy", + }); + expect(result instanceof type.errors).toBe(true); +}); + +test("an overlong system prompt is rejected", () => { + const result = CreateAgentDefinitionInput({ + ...VALID, + systemPrompt: "x".repeat(8001), + }); + expect(result instanceof type.errors).toBe(true); +}); + +test("a blank system prompt is rejected", () => { + const result = CreateAgentDefinitionInput({ ...VALID, systemPrompt: " " }); + expect(result instanceof type.errors).toBe(true); +}); + +test("description and model are optional", () => { + const result = CreateAgentDefinitionInput(VALID); + expect(result instanceof type.errors).toBe(false); +}); + +test("a whitespace-only model is rejected rather than accepted as unset", () => { + const result = CreateAgentDefinitionInput({ ...VALID, model: " " }); + expect(result instanceof type.errors).toBe(true); +}); From 55f1be4a6be0813b745c4a6884b580a93484ef94 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 8 Aug 2026 18:16:39 -0700 Subject: [PATCH 2/3] Add agent-directory package and create-agent flow on the Agents page Domain package owns create/list routes and validation; hub mounts it. The web Agents page lists definitions by name and opens a create dialog. --- apps/hub/package.json | 1 + apps/hub/src/index.ts | 18 + apps/web/src/agents-api.ts | 214 +++++++ apps/web/src/pages/agents-directory.ts | 72 +++ apps/web/src/pages/agents-page.tsx | 578 +++++++++++++----- apps/web/src/pages/create-agent-dialog.tsx | 255 ++++++++ bun.lock | 161 ++--- packages/agent-directory/package.json | 29 + .../agent-directory/src/agent-workflow.ts | 134 ++++ packages/agent-directory/src/index.ts | 12 + packages/agent-directory/src/routes.ts | 148 +++++ packages/agent-directory/src/validation.ts | 36 ++ packages/agent-directory/tsconfig.json | 7 + 13 files changed, 1453 insertions(+), 212 deletions(-) create mode 100644 apps/web/src/agents-api.ts create mode 100644 apps/web/src/pages/agents-directory.ts create mode 100644 apps/web/src/pages/create-agent-dialog.tsx create mode 100644 packages/agent-directory/package.json create mode 100644 packages/agent-directory/src/agent-workflow.ts create mode 100644 packages/agent-directory/src/index.ts create mode 100644 packages/agent-directory/src/routes.ts create mode 100644 packages/agent-directory/src/validation.ts create mode 100644 packages/agent-directory/tsconfig.json diff --git a/apps/hub/package.json b/apps/hub/package.json index 39f6796ed..98c6f2647 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -12,6 +12,7 @@ "test": "bun test" }, "dependencies": { + "@corbits/agent-directory": "workspace:*", "@corbits/chat": "workspace:*", "@corbits/commands": "workspace:*", "@corbits/folded-runs": "workspace:*", diff --git a/apps/hub/src/index.ts b/apps/hub/src/index.ts index edae0de94..8ac3b49ca 100644 --- a/apps/hub/src/index.ts +++ b/apps/hub/src/index.ts @@ -22,6 +22,7 @@ import { startWorkflowCommand, } from "@corbits/chat"; import { createCryptoProviderCache } from "@corbits/folded-runs"; +import { createAgentDefinitionRoutes } from "@corbits/agent-directory"; import { createDrizzleWebhookTriggerStore, createWebhookIngressRoutes, @@ -314,6 +315,23 @@ export async function createHub(config: HubConfig) { commands: commandRegistry, }; app.route(`${TENANT_PREFIX}/chat`, createChatRoutes(chatDeps)); + // Agent definitions a person authors by hand from the Agents page's + // create form, materialized the same way the platform's own starter + // agents are (see `@corbits/agent-directory`'s doc comment). Shares + // `chatGrantStore`/`chatConditionRegistry` with every other extension + // mounted here — there is nothing chat-specific about that pair, it + // is just this composition root's one db-backed grant store. + app.route( + `${TENANT_PREFIX}/agent-definitions`, + createAgentDefinitionRoutes({ + db, + assetService, + requireGrant: createRequireGrant({ + grantStore: chatGrantStore, + conditionRegistry: chatConditionRegistry, + }), + }), + ); app.route( `${TENANT_PREFIX}/chat`, createCommandRoutes({ diff --git a/apps/web/src/agents-api.ts b/apps/web/src/agents-api.ts new file mode 100644 index 000000000..a8f5e7016 --- /dev/null +++ b/apps/web/src/agents-api.ts @@ -0,0 +1,214 @@ +// The Agents page's one seam to the hub: agent definitions (templates +// an agent can be launched from), their deployed instances, and the +// tenant's model catalog — each fetched with the platform's own wire +// schemas, validated at the boundary exactly like every other query in +// `./api.ts`. Kept separate from that file because these three +// endpoints are tenant-scoped (the path needs a resolved `tenantId` +// before it can even be built), unlike the fixed `/api/me/...` paths +// `useAPIQuery` there is built around. + +import { + ModelResponse, + WorkflowDefinitionResponse, + WorkflowRunResponse, + paginatedSchema, +} from "@intx/types"; +import { type } from "arktype"; +import type { ArkErrors } from "arktype"; +import { useEffect, useState } from "react"; + +import type { APIQuery } from "./api"; + +export type AgentDefinition = typeof WorkflowDefinitionResponse.infer; +export type AgentInstance = typeof WorkflowRunResponse.infer; +export type CatalogModel = typeof ModelResponse.infer; + +const DefinitionsPage = paginatedSchema(WorkflowDefinitionResponse); +const InstancesPage = paginatedSchema(WorkflowRunResponse); +const ModelsPage = paginatedSchema(ModelResponse); + +// The REST pagination ceiling (see `vendor/intx/hub-api/src/pagination.ts`). +// A bench with more agents or instances than this needs real pagination on +// this page, not raised here — tracked as a known limit, not silently +// worked around. +const PAGE_LIMIT = 100; + +export class AgentDirectoryError extends Error { + constructor( + message: string, + readonly status?: number, + ) { + super(message); + } +} + +type Validator = (data: unknown) => T | ArkErrors; + +async function getJSON(path: string, schema: Validator): Promise { + let response: Response; + try { + response = await fetch(path, { headers: { accept: "application/json" } }); + } catch (cause) { + throw new AgentDirectoryError( + cause instanceof Error ? cause.message : String(cause), + ); + } + if (response.status === 401) { + throw new AgentDirectoryError("Not signed in.", 401); + } + if (!response.ok) { + throw new AgentDirectoryError( + `The hub answered ${response.status} for ${path}.`, + response.status, + ); + } + const parsed = schema(await response.json().catch(() => undefined)); + if (parsed instanceof type.errors) { + throw new AgentDirectoryError( + `Unexpected response shape from ${path}: ${parsed.summary}`, + ); + } + return parsed; +} + +const ErrorEnvelope = type({ error: { message: "string" } }); + +async function postJSON( + path: string, + schema: Validator, + body: unknown, +): Promise { + let response: Response; + try { + response = await fetch(path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + }); + } catch (cause) { + throw new AgentDirectoryError( + cause instanceof Error ? cause.message : String(cause), + ); + } + const json: unknown = await response.json().catch(() => undefined); + if (!response.ok) { + const envelope = ErrorEnvelope(json); + const message = + envelope instanceof type.errors + ? `The hub answered ${response.status} for ${path}.` + : envelope.error.message; + throw new AgentDirectoryError(message, response.status); + } + const parsed = schema(json); + if (parsed instanceof type.errors) { + throw new AgentDirectoryError( + `Unexpected response shape from ${path}: ${parsed.summary}`, + ); + } + return parsed; +} + +export function listAgentDefinitions( + tenantId: string, +): Promise { + return getJSON( + `/api/tenants/${tenantId}/workflows/definitions?limit=${PAGE_LIMIT}`, + DefinitionsPage, + ).then((page) => page.data); +} + +export function listAgentInstances( + tenantId: string, +): Promise { + return getJSON( + `/api/tenants/${tenantId}/workflows/runs?limit=${PAGE_LIMIT}`, + InstancesPage, + ).then((page) => page.data); +} + +/** The tenant's visible, enabled catalog models, for the create-agent + * form's model picker. Never invented client-side — only what the + * catalog actually resolves against at launch time. */ +export function listCatalogModels( + tenantId: string, +): Promise { + return getJSON( + `/api/tenants/${tenantId}/models?limit=${PAGE_LIMIT}`, + ModelsPage, + ).then((page) => page.data.filter((model) => !model.disabled)); +} + +export type CreateAgentDefinitionInput = { + readonly name: string; + readonly handle: string; + readonly description?: string; + readonly systemPrompt: string; + readonly model?: string; +}; + +export function createAgentDefinition( + tenantId: string, + input: CreateAgentDefinitionInput, +): Promise { + return postJSON( + `/api/tenants/${tenantId}/agent-definitions`, + WorkflowDefinitionResponse, + input, + ); +} + +export type AgentDirectoryData = { + readonly tenantId: string; + readonly definitions: readonly AgentDefinition[]; + readonly instances: readonly AgentInstance[]; + readonly models: readonly CatalogModel[]; +}; + +/** + * Loads a bench's full agent directory in one shot, re-fetching whenever + * `tenantId` changes or `reloadKey` is bumped — the same "no push, refetch + * on demand" convention `useAPIQuery` uses, so a freshly created + * definition shows up the moment the create dialog closes. + */ +export function useAgentDirectory( + tenantId: string | undefined, + reloadKey: number, +): APIQuery { + const [state, setState] = useState>({ + kind: "loading", + }); + + useEffect(() => { + if (tenantId === undefined) return; + let cancelled = false; + setState({ kind: "loading" }); + Promise.all([ + listAgentDefinitions(tenantId), + listAgentInstances(tenantId), + listCatalogModels(tenantId), + ]) + .then(([definitions, instances, models]) => { + if (cancelled) return; + setState({ + kind: "ready", + data: { tenantId, definitions, instances, models }, + }); + }) + .catch((cause: unknown) => { + if (cancelled) return; + if (cause instanceof AgentDirectoryError && cause.status === 401) { + setState({ kind: "unauthenticated" }); + return; + } + setState({ + kind: "error", + message: cause instanceof Error ? cause.message : String(cause), + }); + }); + return () => { + cancelled = true; + }; + }, [tenantId, reloadKey]); + + return state; +} diff --git a/apps/web/src/pages/agents-directory.ts b/apps/web/src/pages/agents-directory.ts new file mode 100644 index 000000000..5dfa6c457 --- /dev/null +++ b/apps/web/src/pages/agents-directory.ts @@ -0,0 +1,72 @@ +// Pure logic behind the Agents page: filtering out the chat anchor +// machinery's channel hosts (they are plumbing, not an agent a person +// created), full-text search across the fields a person actually reads +// (never an id), and flagging an instance whose definition has since +// gone missing from the tenant's own listing. + +import { isChannelHostDefinitionName } from "@corbits/chat/channel-host-naming"; + +import type { AgentDefinition, AgentInstance } from "../agents-api"; + +/** Every definition and instance a bench holds, minus the chat anchor + * machinery's channel hosts — those are internal plumbing, never a + * user-facing agent. */ +export function purposeAgentDefinitions( + definitions: readonly AgentDefinition[], +): readonly AgentDefinition[] { + return definitions.filter((d) => !isChannelHostDefinitionName(d.name)); +} + +export function purposeAgentInstances( + instances: readonly AgentInstance[], +): readonly AgentInstance[] { + return instances.filter( + (instance) => !isChannelHostDefinitionName(instance.definitionName), + ); +} + +export function filterDefinitions( + definitions: readonly AgentDefinition[], + query: string, +): readonly AgentDefinition[] { + const needle = query.trim().toLowerCase(); + if (needle === "") return definitions; + return definitions.filter( + (d) => + d.name.toLowerCase().includes(needle) || + (d.description ?? "").toLowerCase().includes(needle), + ); +} + +export function filterInstances( + instances: readonly T[], + query: string, +): readonly T[] { + const needle = query.trim().toLowerCase(); + if (needle === "") return instances; + return instances.filter((i) => + i.definitionName.toLowerCase().includes(needle), + ); +} + +/** + * An instance is orphaned when the tenant's own definitions listing no + * longer carries its `definitionId` — the definition was deleted or, + * more commonly, has scrolled past the page's fetch window. A + * definition row's own FK to the run means this can never mean "no + * definition ever existed"; it means "not resolvable from here", which + * is exactly the distinction the UI floor cares about: never hide an + * instance the page cannot fully explain, mark it instead. + */ +export function isOrphanedInstance( + instance: AgentInstance, + definitionsById: ReadonlyMap, +): boolean { + return !definitionsById.has(instance.definitionId); +} + +export function definitionsById( + definitions: readonly AgentDefinition[], +): ReadonlyMap { + return new Map(definitions.map((d) => [d.id, d])); +} diff --git a/apps/web/src/pages/agents-page.tsx b/apps/web/src/pages/agents-page.tsx index f0fe10bf1..eefe7b812 100644 --- a/apps/web/src/pages/agents-page.tsx +++ b/apps/web/src/pages/agents-page.tsx @@ -1,195 +1,489 @@ import { + Badge, Button, + Card, EmptyState, + LibrarySearchInput, PageShell, + RichEmptyState, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, + Tabs, TopBar, + TopBarActions, TopBarTitle, + ViewToggle, + formatRelativeTime, } from "@corbits/react-ui"; -import { InviteAgentDialog, inviteAgent, listChannels } from "@corbits/chat-ui"; -import type { Channel, ParticipantRecord } from "@corbits/chat-ui"; -import { Bot } from "lucide-react"; -import { useEffect, useState } from "react"; +import type { BadgeTone, ViewMode } from "@corbits/react-ui"; +import { Bot, Copy, Plus, Workflow } from "lucide-react"; +import { useState } from "react"; +import type { ReactNode } from "react"; +import type { AgentDefinition, AgentInstance } from "../agents-api"; +import type { AgentDirectoryData } from "../agents-api"; import { PrincipalsSchema, useAPIQuery } from "../api"; -import { countProp } from "../optional-props"; import type { APIQuery } from "../api"; +import { useAgentDirectory } from "../agents-api"; +import { countProp } from "../optional-props"; import { QueryView } from "../query-view"; +import { CreateAgentDialog } from "./create-agent-dialog"; +import { + definitionsById, + filterDefinitions, + filterInstances, + isOrphanedInstance, + purposeAgentDefinitions, + purposeAgentInstances, +} from "./agents-directory"; + +const DEFINITION_STATUS_TONE: Record = { + deployed: "success", + stopped: "neutral", +}; -type TenantChannels = { - readonly tenantId: string; - readonly channels: readonly Channel[]; +const INSTANCE_STATUS_TONE: Record = { + running: "success", + deployed: "info", + updating: "info", + stopped: "neutral", + error: "danger", }; -/** - * A participant's mention handle, never its raw address on screen — the UI - * floor covers every surface, so the address does not appear here at all, - * not even as a tooltip. - */ -function ParticipantHandles({ - participants, +const INSTANCE_CAP = 4; + +/** Copies an instance's mailbox address to the clipboard on demand — + * the only way this page ever exposes it. The address never renders as + * visible text anywhere on this surface. */ +function CopyAddressButton({ address }: { readonly address: string }) { + const [copied, setCopied] = useState(false); + return ( + + ); +} + +function InstanceBadges({ + instances, }: { - readonly participants: readonly ParticipantRecord[]; + readonly instances: readonly (AgentInstance & { + readonly orphaned: boolean; + })[]; }) { - if (participants.length === 0) { - return No participants; + if (instances.length === 0) { + return No instances; } + const shown = instances.slice(0, INSTANCE_CAP); + const overflow = instances.length - shown.length; return ( - - {participants.map((participant) => ( - - @{participant.handle} - + + {shown.map((instance) => ( + + {instance.status} + ))} + {overflow > 0 && ( + +{overflow} more + )} ); } -/** - * Agent definitions you can invite, one channel at a time: the channel - * table doubles as "who's active where" (its participants), and each row's - * "Invite agent" action opens `@corbits/chat-ui`'s existing - * `InviteAgentDialog` — the same list the chat surface's own invite flow - * uses, so this page never re-derives what counts as invitable. - */ +function DefinitionCard({ + definition, + instances, +}: { + readonly definition: AgentDefinition; + readonly instances: readonly (AgentInstance & { + readonly orphaned: boolean; + })[]; +}) { + return ( + +
+ {definition.name} + + {definition.status} + +
+

+ {definition.description ?? "No description"} +

+ +
+ ); +} + +function DefinitionRows({ + definitions, + instancesByDefinition, +}: { + readonly definitions: readonly AgentDefinition[]; + readonly instancesByDefinition: ReadonlyMap< + string, + readonly (AgentInstance & { readonly orphaned: boolean })[] + >; +}) { + return ( + + + + Name + Description + Status + Instances + + + + {definitions.map((definition) => ( + + {definition.name} + + {definition.description ?? "—"} + + + + {definition.status} + + + + + + + ))} + +
+ ); +} + +function InstanceCard({ + instance, + now, +}: { + readonly instance: AgentInstance & { readonly orphaned: boolean }; + readonly now: number; +}) { + return ( + +
+ + {instance.definitionName} + + + {instance.status} + +
+ {instance.orphaned && ( + + Unlinked definition + + )} + + Started {formatRelativeTime(instance.createdAt, now)} + + +
+ ); +} + +function InstanceRows({ + instances, + now, +}: { + readonly instances: readonly (AgentInstance & { + readonly orphaned: boolean; + })[]; + readonly now: number; +}) { + return ( + + + + Agent + Status + Started + Mailbox + + + + {instances.map((instance) => ( + + + + {instance.definitionName} + {instance.orphaned && ( + Unlinked definition + )} + + + + + {instance.status} + + + + {formatRelativeTime(instance.createdAt, now)} + + + + + + ))} + +
+ ); +} + +type AgentsTab = "definitions" | "instances"; + export function AgentsPage({ - tenant, + directory, + onAgentCreated, + now = Date.now(), + initialTab = "definitions", }: { - readonly tenant: APIQuery; + readonly directory: APIQuery; + readonly onAgentCreated: (definition: AgentDefinition) => void; + /** Reference time for relative timestamps; injectable for tests. */ + readonly now?: number; + /** Which tab is active on first render; injectable for tests that need + * to inspect the instances panel without a click. */ + readonly initialTab?: AgentsTab; }) { - const [inviteChannel, setInviteChannel] = useState(null); + const [query, setQuery] = useState(""); + const [viewMode, setViewMode] = useState("grid"); + const [tab, setTab] = useState(initialTab); + const [createOpen, setCreateOpen] = useState(false); + + const isReady = directory.kind === "ready"; return ( <> Agents + + + + + - - - {({ tenantId, channels }) => - channels.length === 0 ? ( - } - title="No channel to invite an agent into" - description="Create a channel in Chat first — agents join channels, not the workspace at large." - /> - ) : ( - <> - - - - Channel - Participants - - Actions - - - - - {channels.map((channel) => ( - - - {channel.title} - - - - - - - - - ))} - -
- {inviteChannel === null ? null : ( - { - if (!open) setInviteChannel(null); - }} - tenantId={tenantId} - channelId={inviteChannel.id} - onInvite={(definitionId) => - inviteAgent( - tenantId, - inviteChannel.id, - definitionId, - ).then(() => undefined) + + + {(data) => { + const definitions = purposeAgentDefinitions(data.definitions); + const instances = purposeAgentInstances(data.instances).map( + (instance) => ({ + ...instance, + orphaned: isOrphanedInstance( + instance, + definitionsById(definitions), + ), + }), + ); + const visibleDefinitions = filterDefinitions(definitions, query); + const visibleInstances = filterInstances(instances, query); + const instancesByDefinition = new Map< + string, + (AgentInstance & { readonly orphaned: boolean })[] + >(); + for (const instance of instances) { + const list = instancesByDefinition.get(instance.definitionId); + if (list === undefined) { + instancesByDefinition.set(instance.definitionId, [instance]); + } else { + list.push(instance); + } + } + + if (definitions.length === 0 && instances.length === 0) { + return ( + } + title="No agents yet" + description="Create your first agent — a name, a system prompt, and optionally a model — and it appears here immediately, ready to invite into a channel." + actions={[ + { + label: "Create agent", + onClick: () => setCreateOpen(true), + variant: "primary", + }, + ]} + /> + ); + } + + return ( + + {(active) => { + if (active === "definitions") { + if (visibleDefinitions.length === 0) { + return ( + } + title="Nothing matches" + description={`No agent definition matches "${query}".`} + /> + ); } - /> - )} - - ) - } + return viewMode === "rows" ? ( +
+ +
+ ) : ( +
+ {visibleDefinitions.map((definition) => ( + + ))} +
+ ); + } + if (visibleInstances.length === 0) { + return ( + } + title="Nothing matches" + description={ + instances.length === 0 + ? "No agent instance is deployed in this bench yet. Invite a definition into a channel to launch one." + : `No agent instance matches "${query}".` + } + /> + ); + } + return viewMode === "rows" ? ( +
+ +
+ ) : ( +
+ {visibleInstances.map((instance) => ( + + ))} +
+ ); + }} +
+ ); + }}
-
+ + {isReady && ( + + )} ); } +// A thin, named wrapper around `PageShell` so the two very different +// child shapes above (`RichEmptyState`/`Tabs`) share one shell call +// site instead of duplicating its props at both return points. +function PageShellBody({ children }: { readonly children: ReactNode }) { + return ( + + {children} + + ); +} + export function AgentsRoute() { const principals = useAPIQuery("/api/me/principals", PrincipalsSchema); - const [channels, setChannels] = useState>({ - kind: "loading", - }); - - useEffect(() => { - if (principals.kind !== "ready") { - setChannels(principals); - return; - } - const membership = principals.data.data[0]; - if (membership === undefined) { - setChannels({ kind: "ready", data: { tenantId: "", channels: [] } }); - return; - } - let cancelled = false; - setChannels({ kind: "loading" }); - listChannels(membership.tenantId, "channel") - .then((items) => { - if (!cancelled) { - setChannels({ + const [reloadKey, setReloadKey] = useState(0); + const membership = + principals.kind === "ready" ? principals.data.data[0] : undefined; + const directory = useAgentDirectory(membership?.tenantId, reloadKey); + + const resolvedDirectory: APIQuery = + principals.kind !== "ready" + ? principals + : membership === undefined + ? { kind: "ready", - data: { tenantId: membership.tenantId, channels: items }, - }); - } - }) - .catch((cause: unknown) => { - if (!cancelled) { - setChannels({ - kind: "error", - message: cause instanceof Error ? cause.message : String(cause), - }); - } - }); - return () => { - cancelled = true; - }; - }, [principals]); - - return ; + data: { + tenantId: "", + definitions: [], + instances: [], + models: [], + }, + } + : directory; + + return ( + setReloadKey((key) => key + 1)} + /> + ); } diff --git a/apps/web/src/pages/create-agent-dialog.tsx b/apps/web/src/pages/create-agent-dialog.tsx new file mode 100644 index 000000000..204e052f3 --- /dev/null +++ b/apps/web/src/pages/create-agent-dialog.tsx @@ -0,0 +1,255 @@ +// The create-agent form: identity (name, handle, description) and +// definition (system prompt, model). Every field maps onto something +// `POST /api/tenants/:t/agent-definitions` can actually honor — see +// `@corbits/agent-directory`'s `CreateAgentDefinitionInput` — so this +// component asks for nothing the platform would silently ignore. + +import { + Button, + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + IntakeForm, + intakeFieldsComplete, +} from "@corbits/react-ui"; +import type { IntakeField } from "@corbits/react-ui"; +import { useState } from "react"; + +import type { CatalogModel } from "../agents-api"; +import { AgentDirectoryError, createAgentDefinition } from "../agents-api"; +import type { AgentDefinition } from "../agents-api"; + +const HANDLE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; + +function slugify(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +type FormValues = { + readonly name: string; + readonly handle: string; + readonly description: string; + readonly systemPrompt: string; + readonly model: string; +}; + +const EMPTY_VALUES: FormValues = { + name: "", + handle: "", + description: "", + systemPrompt: "", + model: "", +}; + +function fieldsFor(models: readonly CatalogModel[]): readonly IntakeField[] { + const base: IntakeField[] = [ + { + name: "name", + label: "Name", + type: "text", + required: true, + placeholder: "Research Buddy", + }, + { + name: "handle", + label: "Handle", + type: "text", + required: true, + placeholder: "research-buddy", + help: "Lowercase letters, digits, and hyphens only — this becomes the agent's address.", + }, + { + name: "description", + label: "Description", + type: "textarea", + placeholder: "What this agent is for", + }, + { + name: "systemPrompt", + label: "System prompt", + type: "textarea", + required: true, + placeholder: "You are...", + help: "Instructions the agent follows on every turn.", + }, + ]; + if (models.length === 0) return base; + return [ + ...base, + { + name: "model", + label: "Model", + type: "select", + options: models.map((model) => ({ + value: model.canonicalName, + label: model.displayName ?? model.canonicalName, + })), + help: "Left unset, the bench's catalog default is used.", + }, + ]; +} + +/** Every reason a submission is not yet valid, in plain language — never + * a generic "invalid form". */ +function validationIssues(values: FormValues): readonly string[] { + const issues: string[] = []; + if (values.name.trim() === "") issues.push("Name is required."); + if (values.handle.trim() === "") { + issues.push("Handle is required."); + } else if (!HANDLE_PATTERN.test(values.handle)) { + issues.push( + "Handle must be lowercase letters, digits, and hyphens only, with no leading or trailing hyphen.", + ); + } + if (values.systemPrompt.trim() === "") { + issues.push("System prompt is required."); + } + return issues; +} + +export function CreateAgentDialog({ + open, + onOpenChange, + tenantId, + models, + onCreated, +}: { + readonly open: boolean; + readonly onOpenChange: (open: boolean) => void; + readonly tenantId: string; + readonly models: readonly CatalogModel[]; + readonly onCreated: (definition: AgentDefinition) => void; +}) { + const [values, setValues] = useState(EMPTY_VALUES); + const [handleTouched, setHandleTouched] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [submitError, setSubmitError] = useState(null); + const [showIssues, setShowIssues] = useState(false); + + function reset() { + setValues(EMPTY_VALUES); + setHandleTouched(false); + setSubmitError(null); + setShowIssues(false); + } + + function handleOpenChange(next: boolean) { + if (!next) reset(); + onOpenChange(next); + } + + function handleFormChange(next: Record) { + const name = typeof next.name === "string" ? next.name : values.name; + const handleEdited = + typeof next.handle === "string" && next.handle !== values.handle; + setValues({ + name, + handle: + typeof next.handle === "string" + ? next.handle + : handleTouched + ? values.handle + : slugify(name), + description: typeof next.description === "string" ? next.description : "", + systemPrompt: + typeof next.systemPrompt === "string" ? next.systemPrompt : "", + model: typeof next.model === "string" ? next.model : "", + }); + if (handleEdited) setHandleTouched(true); + } + + const issues = validationIssues(values); + const fields = fieldsFor(models); + + async function handleSubmit() { + if (issues.length > 0) { + setShowIssues(true); + return; + } + setSubmitting(true); + setSubmitError(null); + try { + const created = await createAgentDefinition(tenantId, { + name: values.name.trim(), + handle: values.handle.trim(), + systemPrompt: values.systemPrompt.trim(), + ...(values.description.trim() !== "" + ? { description: values.description.trim() } + : {}), + ...(values.model.trim() !== "" ? { model: values.model.trim() } : {}), + }); + reset(); + onOpenChange(false); + onCreated(created); + } catch (cause) { + setSubmitError( + cause instanceof AgentDirectoryError + ? cause.message + : "Could not create the agent.", + ); + } finally { + setSubmitting(false); + } + } + + return ( + + + + Create agent + + Define a new agent this bench can invite into a channel and launch. + + + + {submitError !== null && ( +

+ {submitError} +

+ )} + {showIssues && issues.length > 0 && ( +
    + {issues.map((issue) => ( +
  • {issue}
  • + ))} +
+ )} + +
+ + + + +
+
+ ); +} diff --git a/bun.lock b/bun.lock index d08b1c09c..060a88c6c 100644 --- a/bun.lock +++ b/bun.lock @@ -21,6 +21,7 @@ "name": "@workbench/hub", "version": "0.0.1", "dependencies": { + "@corbits/agent-directory": "workspace:*", "@corbits/chat": "workspace:*", "@corbits/commands": "workspace:*", "@corbits/folded-runs": "workspace:*", @@ -107,6 +108,24 @@ "vite": "^7.1.0", }, }, + "packages/agent-directory": { + "name": "@corbits/agent-directory", + "version": "0.0.1", + "dependencies": { + "@intx/agent": "workspace:*", + "@intx/db": "workspace:*", + "@intx/hub-api": "workspace:*", + "@intx/hub-sessions": "workspace:*", + "@intx/workflow": "workspace:*", + "arktype": "catalog:", + "drizzle-orm": "catalog:", + "hono": "^4.11.9", + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:", + }, + }, "packages/agent-lifecycle": { "name": "@corbits/agent-lifecycle", "version": "0.0.1", @@ -725,6 +744,8 @@ "@better-fetch/fetch": ["@better-fetch/fetch@1.3.1", "", {}, "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g=="], + "@corbits/agent-directory": ["@corbits/agent-directory@workspace:packages/agent-directory"], + "@corbits/agent-lifecycle": ["@corbits/agent-lifecycle@workspace:packages/agent-lifecycle"], "@corbits/artifact-ui": ["@corbits/artifact-ui@workspace:packages/artifact-ui"], @@ -907,9 +928,9 @@ "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], - "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], + "@noble/ciphers": ["@noble/ciphers@2.3.0", "", {}, "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw=="], - "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + "@noble/hashes": ["@noble/hashes@2.3.0", "", {}, "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ=="], "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], @@ -1047,7 +1068,7 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], "@types/npm-package-arg": ["@types/npm-package-arg@6.1.4", "", {}, "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q=="], @@ -1123,7 +1144,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ=="], "better-auth": ["better-auth@1.6.26", "", { "dependencies": { "@better-auth/core": "1.6.26", "@better-auth/drizzle-adapter": "1.6.26", "@better-auth/kysely-adapter": "1.6.26", "@better-auth/memory-adapter": "1.6.26", "@better-auth/mongo-adapter": "1.6.26", "@better-auth/prisma-adapter": "1.6.26", "@better-auth/telemetry": "1.6.26", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.7", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-nhXWrDDj+EnZsHq1j0z1c6DowOMFZWZHe6LCaXbBfLIgHHZm6dyazBQcbRspM4spUIcUt250Mc1BFOSuP7eniQ=="], @@ -1131,7 +1152,7 @@ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], - "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], @@ -1149,7 +1170,7 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], @@ -1189,7 +1210,7 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "electron-to-chromium": ["electron-to-chromium@1.5.401", "", {}, "sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.402", "", {}, "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA=="], "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -1205,7 +1226,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ=="], + "eslint": ["eslint@10.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], @@ -1275,7 +1296,7 @@ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - "hono": ["hono@4.13.0", "", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="], + "hono": ["hono@4.13.1", "", {}, "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw=="], "hono-openapi": ["hono-openapi@1.3.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.11.2", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw=="], @@ -1311,7 +1332,7 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "isomorphic-git": ["isomorphic-git@1.40.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-/CbnxwZqIm17y3c/z0INbkgEKSvFerXtO/NGgaRxZ8nvL3eoMtbjuAS7f4Pj7lZzj8HaultvDD1ClJTBVDl89g=="], + "isomorphic-git": ["isomorphic-git@1.41.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-YADpOKD/pLemtcyZ9jssNXnPVhfDObGl/BAKMtvmU17svgNzOKTT6AHX68DzFHpie5hAZHRtutC0Cka3lYdmBA=="], "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], @@ -1341,7 +1362,7 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "lucide-react": ["lucide-react@1.28.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg=="], + "lucide-react": ["lucide-react@1.30.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tUIr2jXLbWpCkdtH8XP7P7YppM9ueWgTky99lpWDY6z5REs6B+O6ZQ3U5tHkUUY59ANyOv/PBcs8E4Fe3KO3eA=="], "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], @@ -1371,7 +1392,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="], + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], "nanostores": ["nanostores@1.4.2", "", {}, "sha512-Wxv8Roefr2nqtiRG0bnaFlpYqpIVtOEeJZHaH+4nGgOK1/7n6OHOuHCb/bhqrNQgZM8fyd0s1PqhdrJc9Ib44g=="], @@ -1379,7 +1400,7 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "node-releases": ["node-releases@2.0.52", "", {}, "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A=="], + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], "npm-install-checks": ["npm-install-checks@7.1.2", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ=="], @@ -1419,7 +1440,7 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - "postcss": ["postcss@8.5.25", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw=="], + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], @@ -1507,7 +1528,7 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsx": ["tsx@4.23.8", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-8W675THjbzfFmLOQzjDBIBna+WjqMGIxmSZ1mMc1+o9qoVsEuAgQu5j5ueLhau8inOkDu9OslVg0FmfBs1RIHw=="], + "tsx": ["tsx@4.23.11", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -1519,7 +1540,7 @@ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], @@ -1581,9 +1602,9 @@ "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "tsx/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - "vite/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "vite/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], @@ -1639,109 +1660,109 @@ "npm-registry-fetch/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - "vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - "vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], "npm-registry-fetch/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], } diff --git a/packages/agent-directory/package.json b/packages/agent-directory/package.json new file mode 100644 index 000000000..56686da5f --- /dev/null +++ b/packages/agent-directory/package.json @@ -0,0 +1,29 @@ +{ + "name": "@corbits/agent-directory", + "private": true, + "description": "Creates agent definitions as workflow assets the tenant can browse, invite, and launch", + "version": "0.0.1", + "license": "SEE LICENSE IN LICENSE.md", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "bun test" + }, + "dependencies": { + "@intx/agent": "workspace:*", + "@intx/db": "workspace:*", + "@intx/hub-api": "workspace:*", + "@intx/hub-sessions": "workspace:*", + "@intx/workflow": "workspace:*", + "arktype": "catalog:", + "drizzle-orm": "catalog:", + "hono": "^4.11.9" + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:" + } +} diff --git a/packages/agent-directory/src/agent-workflow.ts b/packages/agent-directory/src/agent-workflow.ts new file mode 100644 index 000000000..9390c8730 --- /dev/null +++ b/packages/agent-directory/src/agent-workflow.ts @@ -0,0 +1,134 @@ +// Builds the single-step, folded workflow definition a hand-authored +// agent materializes as: exactly the shape `@corbits/chat`'s own +// `buildChannelHostWorkflow`/`@corbits/assistant-workflow`'s +// `buildAssistantWorkflow` produce, but with the system prompt and +// model left to the caller instead of fixed at build time — this is +// the one difference that makes a defined-by-a-person agent possible +// alongside the platform's own fixed starter agents. +// +// This package is installable data, exactly like `@corbits/chat`'s +// channel-host builder: nothing imports it statically, and a host +// publishes the serialized definition as a workflow asset before +// deploying or launching it. + +import { defineAgent } from "@intx/agent"; +import { defineWorkflow, step } from "@intx/workflow"; +import type { WorkflowDefinition } from "@intx/workflow"; + +export const AGENT_DEFINITION_STEP_ID = "agent"; + +/** Everything a hand-authored agent definition needs baked in at + * creation time. */ +export interface AgentDefinitionWorkflowInput { + /** The definition's mail handle; only used to give the definition's + * placeholder trigger a readable address — an invited launch mints + * its own per-instance address and never reads this one. */ + readonly handle: string; + readonly tenantDomain: string; + readonly description: string; + readonly systemPrompt: string; + /** A canonical model name from the tenant's catalog, or omitted to + * resolve against whatever catalog default the tenant has seeded. + * Never a provider — provider resolution happens at launch time + * against the live catalog (see `resolveDefinitionSources`), not + * baked into the definition. */ + readonly model?: string; +} + +/** + * Builds the definition. Exactly one step, on purpose — the same + * contract every other folded builder in this codebase holds to: a + * second step would trade away the conversational, warm-agent memory + * a folded launch depends on. + */ +export function buildAgentDefinitionWorkflow( + input: AgentDefinitionWorkflowInput, +): WorkflowDefinition { + if (input.handle === "") { + throw new Error("buildAgentDefinitionWorkflow requires a non-empty handle"); + } + if (input.systemPrompt === "") { + throw new Error( + "buildAgentDefinitionWorkflow requires a non-empty systemPrompt", + ); + } + return defineWorkflow({ + id: `wf_agent_${input.handle}`, + trigger: { type: "mail", to: `${input.handle}@${input.tenantDomain}` }, + steps: { + [AGENT_DEFINITION_STEP_ID]: step({ + agent: defineAgent({ + id: AGENT_DEFINITION_STEP_ID, + description: input.description, + systemPrompt: input.systemPrompt, + tools: [], + capabilities: [], + inference: { + // `provider` only participates in deploy-hash bookkeeping — + // launch-time resolution reads `model` alone and resolves a + // provider fresh against the tenant catalog (see + // `resolveDefinitionSources`), so a placeholder here costs + // nothing real. + sources: + input.model !== undefined + ? [{ provider: "catalog", model: input.model }] + : [], + }, + }), + timeout: AGENT_DEFINITION_TURN_TIMEOUT_MS, + }), + }, + }); +} + +const AGENT_DEFINITION_TURN_TIMEOUT_MS = 2 * 60 * 1000; + +/** + * Serializes a definition to the JSON a workflow asset carries. + * Re-implemented rather than shared: `assertJsonPortable` is + * module-private in every builder package that carries a copy of it, + * by design (see `@corbits/chat`'s `channel-workflow.ts`), so this + * copy stays consistent with that convention rather than reaching + * into another package's internals. + */ +export function serializeAgentDefinitionWorkflow( + definition: WorkflowDefinition, +): string { + assertJsonPortable(definition, "definition"); + return JSON.stringify(definition); +} + +function assertJsonPortable(value: unknown, path: string): void { + if (value === null) return; + switch (typeof value) { + case "string": + case "boolean": + return; + case "number": + if (!Number.isFinite(value)) { + throw new Error(`${path} is a non-finite number; JSON drops it`); + } + return; + case "object": + break; + default: + throw new Error( + `${path} is a ${typeof value}, which does not survive JSON serialization`, + ); + } + if (Array.isArray(value)) { + value.forEach((element, index) => { + assertJsonPortable(element, `${path}[${index}]`); + }); + return; + } + const proto: unknown = Object.getPrototypeOf(value); + if (proto !== Object.prototype && proto !== null) { + throw new Error( + `${path} is a non-plain object; JSON would flatten it lossily`, + ); + } + for (const [key, entry] of Object.entries(value)) { + assertJsonPortable(entry, `${path}.${key}`); + } +} diff --git a/packages/agent-directory/src/index.ts b/packages/agent-directory/src/index.ts new file mode 100644 index 000000000..d8dbb4b78 --- /dev/null +++ b/packages/agent-directory/src/index.ts @@ -0,0 +1,12 @@ +export { + buildAgentDefinitionWorkflow, + serializeAgentDefinitionWorkflow, + AGENT_DEFINITION_STEP_ID, + type AgentDefinitionWorkflowInput, +} from "./agent-workflow"; +export { CreateAgentDefinitionInput } from "./validation"; +export type { CreateAgentDefinitionInput as CreateAgentDefinitionInputType } from "./validation"; +export { + createAgentDefinitionRoutes, + type CreateAgentDefinitionRoutesDeps, +} from "./routes"; diff --git a/packages/agent-directory/src/routes.ts b/packages/agent-directory/src/routes.ts new file mode 100644 index 000000000..154fcaf14 --- /dev/null +++ b/packages/agent-directory/src/routes.ts @@ -0,0 +1,148 @@ +// The create-agent-definition surface: a tenant member submits a +// name/handle/description/system-prompt/model, and this route +// materializes it exactly the way the platform's own starter agents +// (`@corbits/assistant-workflow`, `@corbits/chat`'s channel host) are +// materialized — a `workflow`-kind asset carrying a single-step +// `workflow.json`, projected onto a first-class `workflow_definition` +// row. No git subprocess: `AssetService.populateAsset` writes the +// commit in-process, the same seam `createAsset` used to hydrate a +// channel host's asset lives beside. +// +// The definition lands with the schema's own default status +// ("deployed") and a non-null assetId, which is exactly what +// `ChatPlatform.listInvitableDefinitions`/`launchInvite` require to +// treat it as launchable — a freshly created agent is invitable and +// launchable the moment this route returns, no separate "deploy" step. + +import { type } from "arktype"; +import { and, eq } from "drizzle-orm"; +import { Hono } from "hono"; + +import type { DB } from "@intx/db"; +import { workflowDefinition } from "@intx/db/schema"; +import type { TenantEnv, RequireGrant } from "@intx/hub-api"; +import { + AssetServiceError, + DEFAULT_ASSET_REF, + ensureWorkflowDefinitionForAsset, +} from "@intx/hub-sessions"; +import type { AssetService } from "@intx/hub-sessions"; + +import { + buildAgentDefinitionWorkflow, + serializeAgentDefinitionWorkflow, +} from "./agent-workflow"; +import { CreateAgentDefinitionInput } from "./validation"; + +export type CreateAgentDefinitionRoutesDeps = { + db: DB["db"]; + assetService: AssetService; + requireGrant: RequireGrant; +}; + +function errorEnvelope(code: string, message: string) { + return { error: { code, message } }; +} + +export function createAgentDefinitionRoutes({ + db, + assetService, + requireGrant, +}: CreateAgentDefinitionRoutesDeps): Hono { + const app = new Hono(); + + app.post("/", requireGrant("workflow-definition:*", "create"), async (c) => { + const body = CreateAgentDefinitionInput( + await c.req.json().catch(() => undefined), + ); + if (body instanceof type.errors) { + return c.json( + errorEnvelope( + "bad_request", + `invalid agent definition: ${body.summary}`, + ), + 400, + ); + } + + const tenant = c.get("tenant"); + const principal = c.get("principal"); + + const definition = buildAgentDefinitionWorkflow({ + handle: body.handle, + tenantDomain: tenant.domain, + description: body.description ?? "", + systemPrompt: body.systemPrompt, + ...(body.model !== undefined ? { model: body.model } : {}), + }); + const workflowJson = serializeAgentDefinitionWorkflow(definition); + + let asset; + try { + asset = await assetService.createAsset({ + tenantId: tenant.id, + kind: "workflow", + name: body.handle, + displayName: body.name, + creatorPrincipalId: principal.id, + }); + } catch (cause) { + if ( + cause instanceof AssetServiceError && + cause.reason === "duplicate_asset" + ) { + return c.json( + errorEnvelope( + "conflict", + `An agent with the handle "${body.handle}" already exists`, + ), + 409, + ); + } + throw cause; + } + + await assetService.populateAsset({ + assetId: asset.id, + ref: DEFAULT_ASSET_REF, + principal: { kind: "hub" }, + tree: { + files: { "workflow.json": workflowJson }, + message: `Define agent ${body.name}`, + }, + }); + + const { definitionId } = await ensureWorkflowDefinitionForAsset( + db, + asset.id, + ); + + const row = await db.query.workflowDefinition.findFirst({ + where: and( + eq(workflowDefinition.id, definitionId), + eq(workflowDefinition.tenantId, tenant.id), + ), + }); + if (row === undefined) { + throw new Error( + `agent definition "${definitionId}" was created but is not readable back`, + ); + } + + return c.json( + { + id: row.id, + tenantId: row.tenantId, + name: row.name, + description: row.description ?? null, + currentVersion: row.currentVersion, + status: row.status, + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }, + 201, + ); + }); + + return app; +} diff --git a/packages/agent-directory/src/validation.ts b/packages/agent-directory/src/validation.ts new file mode 100644 index 000000000..5b5b483ad --- /dev/null +++ b/packages/agent-directory/src/validation.ts @@ -0,0 +1,36 @@ +// The create-agent-definition request shape, validated at the REST +// boundary before anything touches the asset service. + +import { type } from "arktype"; + +// Mirrors `@intx/hub-sessions`' `ASSET_NAME_PATTERN` exactly (not +// imported: the constant is internal to that package). A definition's +// handle becomes its workflow asset's name, so it is bound by the same +// lowercase-kebab rule the asset service enforces at creation — failing +// here gives a specific, field-scoped error instead of a generic +// asset-service rejection three calls deeper. +const HANDLE_PATTERN = type(/^[a-z0-9]+(?:-[a-z0-9]+)*$/); + +/** A string that is non-blank once trimmed and at most `max` characters + * untrimmed. Used for every free-text field a person types into the + * create-agent form, so a whitespace-only submission reads as the same + * "required" error a truly empty one would. */ +function boundedNonBlankString(max: number) { + return type("string").narrow((value, ctx) => { + if (value.trim() === "") return ctx.mustBe("a non-blank string"); + if (value.length > max) return ctx.mustBe(`at most ${max} characters`); + return true; + }); +} + +export const CreateAgentDefinitionInput = type({ + name: boundedNonBlankString(100), + handle: HANDLE_PATTERN.describe( + "lowercase letters, digits, and hyphens only, no leading or trailing hyphen", + ), + "description?": type("string <= 500"), + systemPrompt: boundedNonBlankString(8000), + "model?": boundedNonBlankString(200), +}); +export type CreateAgentDefinitionInput = + typeof CreateAgentDefinitionInput.infer; diff --git a/packages/agent-directory/tsconfig.json b/packages/agent-directory/tsconfig.json new file mode 100644 index 000000000..e956ddd88 --- /dev/null +++ b/packages/agent-directory/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["bun"] + }, + "include": ["src", "test"] +} From 1579b68662e9068977d3ae25568c73229a73c17a Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 8 Aug 2026 18:16:43 -0700 Subject: [PATCH 3/3] Document the Agents page create-and-list flow --- docs/AGENTS-PAGE.md | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/AGENTS-PAGE.md diff --git a/docs/AGENTS-PAGE.md b/docs/AGENTS-PAGE.md new file mode 100644 index 000000000..0e677f690 --- /dev/null +++ b/docs/AGENTS-PAGE.md @@ -0,0 +1,62 @@ +# The Agents page + +The Agents page (`apps/web/src/pages/agents-page.tsx`) is a bench's one +surface for its agents: the definitions a person can launch from, and the +instances currently running from them. + +## Definitions and instances + +A **definition** (`workflow_definition`) is a reusable template: a name, an +optional description, a system prompt, and (optionally) a model, folded into +a single-step workflow asset. A definition's status is `deployed` +(launchable) or `stopped`. + +An **instance** (`workflow_run`) is a live launch of a definition — a +running agent with its own mailbox address. An instance's status is one of +`deployed`, `running`, `updating`, `error`, or `stopped`, exactly the +vocabulary `GET /api/tenants/:tenantId/workflows/runs` reports. + +The page lists both in one place, as two tabs sharing one search box and one +grid/table view toggle. An instance whose `definitionId` does not resolve +against the tenant's own definitions listing is marked **Unlinked +definition** rather than hidden — the page never silently drops a row it +cannot fully explain. + +Every list excludes the chat surface's channel-host machinery +(`@corbits/chat/channel-host-naming`'s `isChannelHostDefinitionName`): a +channel's anchor run is internal plumbing, not an agent a person created. + +## Creating an agent + +The "Create agent" action opens a form for identity (name, handle, +description) and definition (system prompt, model) and posts to +`POST /api/tenants/:tenantId/agent-definitions`, added by +`@corbits/agent-directory`. The route: + +1. Builds a single-step, folded `workflow.json` from the submitted fields + (`buildAgentDefinitionWorkflow`) — the same shape + `@corbits/assistant-workflow` and `@corbits/chat`'s channel host produce, + parametrized instead of fixed. +2. Creates a `workflow`-kind asset and writes that JSON into it in-process + (`AssetService.populateAsset` — no git subprocess). +3. Projects a first-class `workflow_definition` row over the asset + (`ensureWorkflowDefinitionForAsset`). + +The definition lands with the schema's default status (`deployed`) and a +materialized asset, so it is immediately invitable and launchable — no +separate deploy step, and no page reload needed to see it appear. + +**Tools and a model provider are not exposed on the create form.** The +platform's wire contract for a workflow definition +(`WorkflowDefinitionResponse` in `@intx/types`) carries no tool-package +field, and `@intx/agent`'s `defineAgent` does not thread a caller-supplied +`toolPackagePins` onto the built definition — no production builder in this +codebase sets one today. A model is accepted as a bare canonical name only; +the provider is resolved fresh against the tenant's catalog at launch time +(`resolveDefinitionSources`), never baked into the definition. + +## The mailbox address + +An instance's mailbox address (`WorkflowRunResponse.address`) is never +rendered as visible text on this page. It is reachable only through a +"Copy address" control that writes it to the clipboard.