gesso-framework 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/README.md +2 -0
- package/dist/ChannelProtocol-ByNoHujM.d.ts +247 -0
- package/dist/{FunctionComponent-CgwLKE5d.d.ts → FunctionComponent-fYMdePtH.d.ts} +3 -247
- package/dist/agent/index.d.ts +317 -0
- package/dist/agent/index.js +184 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/app-BvlIO1G9.js +326 -0
- package/dist/app-BvlIO1G9.js.map +1 -0
- package/dist/{index-C9FAI_Kt.d.ts → index-BDM_gzzZ.d.ts} +377 -268
- package/dist/index.d.ts +5 -3
- package/dist/index.js +971 -89
- package/dist/index.js.map +1 -1
- package/dist/jsx/jsx-runtime.d.ts +1 -1
- package/dist/{persisted-CsTPnjkc.js → persisted-pix1fS1D.js} +6 -1
- package/dist/{persisted-CsTPnjkc.js.map → persisted-pix1fS1D.js.map} +1 -1
- package/dist/remote-xxij8zXe.js +600 -0
- package/dist/remote-xxij8zXe.js.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/ui-U39HjNFA.d.ts +517 -0
- package/dist/webmcp-CJVFvAHe.js +77 -0
- package/dist/webmcp-CJVFvAHe.js.map +1 -0
- package/dist/worker/index.d.ts +4 -2
- package/dist/worker/index.js +1 -1
- package/package.json +12 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-xxij8zXe.js","names":[],"sources":["../src/channel/ChannelSchema.ts","../src/agent/binary.ts","../src/agent/validate.ts","../src/agent/AgentSurface.ts","../src/agent/remote.ts"],"sourcesContent":["/**\n * A channel described at run time: what its view holds and what its\n * commands take, as JSON Schema.\n *\n * A token's types are erased by the compiler, so a running application\n * cannot ask a channel what it accepts. That is no loss to a component,\n * which was typechecked against the token, and all of it to anything\n * that meets the application only at run time: an AI agent being\n * handed the channel as tools, a devtools panel, a test that drives an\n * app by its commands. The schema is the contract again, in the one\n * form those can read.\n *\n * `gesso-vite-plugin` writes it from the contract's own types and\n * JSDoc, so an application that uses the plugin describes its channels\n * by having written them. Without the plugin, `describeChannel` takes a\n * hand-written one.\n */\nimport type { ChannelToken } from './ChannelToken';\n\n/**\n * A JSON Schema (draft 2020-12) object.\n *\n * Deliberately loose: the framework stores and hands back schemas and\n * never interprets one, so a precise type would be a second JSON Schema\n * specification to keep in step with the real one.\n */\nexport type JsonSchema = { readonly [keyword: string]: unknown };\n\n/** One command, described. */\nexport interface CommandSchema {\n /** The command's JSDoc summary. */\n readonly description?: string;\n /**\n * The parameter names, in the order the command takes them.\n *\n * A command is called positionally, `move(from, to)`, while a tool\n * call names its arguments, so this is what maps one onto the other.\n */\n readonly parameters: readonly string[];\n /** The last parameter is a rest parameter, and its array is spread into the call. */\n readonly rest?: true;\n /** An object schema, one property per parameter, keyed by parameter name. */\n readonly input: JsonSchema;\n /** `@destructive`: what it does cannot be undone. */\n readonly destructive?: true;\n /** `@idempotent`: sending it twice changes nothing the first did not. */\n readonly idempotent?: true;\n /** `@confirm`: a person should approve it before anything else sends it. */\n readonly confirm?: true;\n /** `@hidden`: for the application's own components, not for an agent. */\n readonly hidden?: true;\n}\n\n/** A channel, described. */\nexport interface ChannelSchema {\n /** The token's JSDoc summary. */\n readonly description?: string;\n /** An object schema, one property per view key. */\n readonly view: JsonSchema;\n readonly commands: { readonly [name: string]: CommandSchema };\n}\n\n/**\n * The schemas, beside the tokens rather than on them.\n *\n * A token is a plain object both threads import and nothing should\n * grow on, and its interface is a public type that a schema field would\n * widen for every application, described or not. Keyed weakly, so a\n * token the application drops takes its schema with it.\n */\nconst schemas = new WeakMap<object, ChannelSchema>();\n\n/**\n * Attaches a schema to a token.\n *\n * `gesso-vite-plugin` calls this at the bottom of each contract module\n * it reads, so it runs once, when the module is first imported. Call it\n * yourself to describe a channel the plugin does not see. A second call\n * for the same token replaces the first, which is what a hot-replaced\n * contract module does.\n */\nexport function describeChannel(token: ChannelToken<object, object>, schema: ChannelSchema): void {\n schemas.set(token, schema);\n}\n\n/** The schema attached to a token, or undefined when nothing described it. */\nexport function channelSchema(token: ChannelToken<object, object>): ChannelSchema | undefined {\n return schemas.get(token);\n}\n","import type { JsonSchema } from '../channel/ChannelSchema';\n\n/**\n * Turns the base64 an agent wrote back into the bytes a command takes.\n *\n * A command may carry an `ArrayBuffer` or a typed array, which an agent\n * cannot write: it speaks JSON. `gesso-vite-plugin` describes such a\n * field as a base64 string tagged `x-gesso-binary` with the type the\n * command expects, the agent sends the string, and this walks the\n * arguments beside their schema and puts the bytes back before the\n * command is sent, so the application receives what its own components\n * would have sent it.\n *\n * Only what the schema tags is touched, and a value that is not a\n * string where bytes are expected is left for the command to see: the\n * validator has already said whether it was the right shape.\n */\nexport function decodeBinary(schema: JsonSchema | undefined, value: unknown, root: JsonSchema): unknown {\n if (schema === undefined) {\n return value;\n }\n const ref = schema.$ref;\n if (typeof ref === 'string') {\n const target = (root.$defs as Record<string, JsonSchema> | undefined)?.[ref.replace(/^#\\/\\$defs\\//, '')];\n return decodeBinary(target, value, root);\n }\n const kind = schema['x-gesso-binary'];\n if (typeof kind === 'string') {\n return typeof value === 'string' ? bytesOf(value, kind) : value;\n }\n if (Array.isArray(schema.anyOf)) {\n // A union holding bytes is nearly always `bytes | null`; the branch\n // that is bytes is the one a string can be.\n const binary = (schema.anyOf as JsonSchema[]).find(option => typeof option['x-gesso-binary'] === 'string');\n return binary !== undefined && typeof value === 'string' ? decodeBinary(binary, value, root) : value;\n }\n if (Array.isArray(value)) {\n const prefix = (schema.prefixItems as JsonSchema[] | undefined) ?? [];\n const items = schema.items as JsonSchema | boolean | undefined;\n return value.map((item, index) =>\n decodeBinary(index < prefix.length ? prefix[index] : typeof items === 'object' ? items : undefined, item, root)\n );\n }\n if (value !== null && typeof value === 'object') {\n const properties = (schema.properties as Record<string, JsonSchema> | undefined) ?? {};\n const out: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(value)) {\n out[key] = decodeBinary(properties[key], field, root);\n }\n return out;\n }\n return value;\n}\n\nconst TYPED: Record<string, new (buffer: ArrayBuffer) => ArrayBufferView> = {\n Int8Array,\n Uint8Array,\n Uint8ClampedArray,\n Int16Array,\n Uint16Array,\n Int32Array,\n Uint32Array,\n Float32Array,\n Float64Array,\n BigInt64Array,\n BigUint64Array\n};\n\nfunction bytesOf(base64: string, kind: string): ArrayBuffer | ArrayBufferView {\n const text = atob(base64);\n const bytes = new Uint8Array(text.length);\n for (let index = 0; index < text.length; index++) {\n bytes[index] = text.charCodeAt(index);\n }\n if (kind === 'ArrayBuffer') {\n return bytes.buffer;\n }\n const Typed = TYPED[kind];\n return Typed === undefined || Typed === Uint8Array ? bytes : new Typed(bytes.buffer);\n}\n","import type { JsonSchema } from '../channel/ChannelSchema';\n\n/**\n * Checks a value against the JSON Schema a channel description uses.\n *\n * Not a general validator. It understands the keywords\n * `gesso-vite-plugin` writes (`type`, `enum`, `const`, `anyOf`,\n * `items`, `prefixItems`, `minItems`, `properties`, `required`,\n * `additionalProperties`, `not` and `$ref` into `$defs`), and passes\n * anything else. That is enough to stop the case that matters: an\n * agent sending `\"3\"` where a command takes a number, which the\n * command would accept, store, and fail on much later and somewhere\n * else.\n *\n * Returns the first problem as a sentence naming where it is, written\n * for the agent that sent the value so it can correct itself, or null.\n */\nexport function validate(schema: JsonSchema, value: unknown, root: JsonSchema = schema, at = 'input'): string | null {\n const ref = schema.$ref;\n if (typeof ref === 'string') {\n const name = ref.replace(/^#\\/\\$defs\\//, '');\n const target = (root.$defs as Record<string, JsonSchema> | undefined)?.[name];\n return target === undefined ? null : validate(target, value, root, at);\n }\n if (schema.not !== undefined && validate(schema.not as JsonSchema, value, root, at) === null) {\n return `${at} must not be given.`;\n }\n if ('const' in schema && value !== schema.const) {\n return `${at} must be ${JSON.stringify(schema.const)}.`;\n }\n if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {\n return `${at} must be one of ${schema.enum.map(option => JSON.stringify(option)).join(', ')}.`;\n }\n if (Array.isArray(schema.anyOf)) {\n const options = schema.anyOf as JsonSchema[];\n if (options.every(option => validate(option, value, root, at) !== null)) {\n return `${at} does not match any of the ${options.length} shapes it may take.`;\n }\n return null;\n }\n const type = schema.type;\n if (typeof type === 'string' && !hasType(value, type)) {\n return `${at} must be ${article(type)}, not ${describe(value)}.`;\n }\n if (Array.isArray(value)) {\n return validateArray(schema, value, root, at);\n }\n if (type === 'object' && value !== null && typeof value === 'object') {\n return validateObject(schema, value as Record<string, unknown>, root, at);\n }\n return null;\n}\n\nfunction validateArray(schema: JsonSchema, value: readonly unknown[], root: JsonSchema, at: string): string | null {\n const prefix = (schema.prefixItems as JsonSchema[] | undefined) ?? [];\n if (typeof schema.minItems === 'number' && value.length < schema.minItems) {\n return `${at} must have at least ${schema.minItems} items.`;\n }\n for (let index = 0; index < value.length; index++) {\n const item = index < prefix.length ? prefix[index] : schema.items;\n if (item === false) {\n return `${at} must have at most ${prefix.length} items.`;\n }\n if (item !== undefined && item !== true) {\n const problem = validate(item as JsonSchema, value[index], root, `${at}[${index}]`);\n if (problem !== null) {\n return problem;\n }\n }\n }\n return null;\n}\n\nfunction validateObject(\n schema: JsonSchema,\n value: Record<string, unknown>,\n root: JsonSchema,\n at: string\n): string | null {\n const properties = (schema.properties as Record<string, JsonSchema> | undefined) ?? {};\n for (const key of (schema.required as string[] | undefined) ?? []) {\n if (value[key] === undefined) {\n return `${at}.${key} is required.`;\n }\n }\n for (const [key, property] of Object.entries(value)) {\n const declared = properties[key];\n if (declared !== undefined) {\n const problem = property === undefined ? null : validate(declared, property, root, `${at}.${key}`);\n if (problem !== null) {\n return problem;\n }\n continue;\n }\n const extra = schema.additionalProperties;\n if (extra === false) {\n const known = Object.keys(properties);\n return `${at}.${key} is not expected${known.length > 0 ? `; the fields are ${known.join(', ')}` : ''}.`;\n }\n if (extra !== undefined && extra !== true) {\n const problem = validate(extra as JsonSchema, property, root, `${at}.${key}`);\n if (problem !== null) {\n return problem;\n }\n }\n }\n return null;\n}\n\nfunction hasType(value: unknown, type: string): boolean {\n switch (type) {\n case 'string':\n return typeof value === 'string';\n case 'number':\n return typeof value === 'number' && Number.isFinite(value);\n case 'integer':\n return Number.isInteger(value);\n case 'boolean':\n return typeof value === 'boolean';\n case 'null':\n return value === null;\n case 'array':\n return Array.isArray(value);\n case 'object':\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n default:\n return true;\n }\n}\n\nfunction article(type: string): string {\n return /^[aeiou]/.test(type) ? `an ${type}` : `a ${type}`;\n}\n\nfunction describe(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (Array.isArray(value)) {\n return 'an array';\n }\n return typeof value === 'object' ? 'an object' : `${typeof value} ${JSON.stringify(value)}`;\n}\n","import type { Subscription } from 'rxjs';\n\nimport { channelSchema, type CommandSchema, type JsonSchema } from '../channel/ChannelSchema';\nimport type { ServedChannel } from '../channel/serveChannels';\nimport { decodeBinary } from './binary';\nimport { validate } from './validate';\n\n/**\n * An application's channels, as an AI agent sees them.\n *\n * A channel is already the shape an agent wants. Its view is what the\n * application currently holds, as plain data, and its commands are\n * the things it can be asked to do, by name, with typed arguments. A\n * component reaches both through a replica; this reaches them from the\n * other side, through the same `{ token, source }` the application\n * already hands to `serveChannels` or `createDesktopApp`, so an agent\n * sends a command to exactly the handler a click does and sees its\n * effect in exactly the view a screen draws.\n *\n * For each channel it offers:\n *\n * - a resource, `gesso://<channel>/view`, holding the view;\n * - a tool, `<channel>_view`, returning the same thing, because more\n * agents call tools than read resources;\n * - a tool per command, `<channel>_<command>`, whose input schema is\n * the command's parameters by name. Calling it sends the command,\n * waits for the view to settle, and returns the view as it now is,\n * so the agent sees what its call did without a second round trip.\n *\n * The descriptions come from `channelSchema(token)`, which\n * `gesso-vite-plugin` writes from the contract's JSDoc. A command\n * marked `@hidden` is not offered; `@destructive` and `@idempotent`\n * become hints a client shows; `@confirm` means the person is asked,\n * through `confirm`, before the command is sent, and a surface given\n * no way to ask refuses it. A channel nobody described is still\n * offered, with its commands taking their arguments as a positional\n * list, and says so in its description.\n *\n * Nothing here knows about a transport. `mcpHandler` serves it over\n * MCP's HTTP transport; anything else can call `tools`, `call`,\n * `resources` and `read` directly.\n */\n\n/** A tool, in the shape MCP's `tools/list` returns. */\nexport interface AgentTool {\n readonly name: string;\n readonly title?: string;\n readonly description: string;\n readonly inputSchema: JsonSchema;\n readonly outputSchema?: JsonSchema;\n readonly annotations: {\n readonly title?: string;\n readonly readOnlyHint: boolean;\n readonly destructiveHint?: boolean;\n readonly idempotentHint?: boolean;\n readonly openWorldHint: false;\n };\n}\n\n/** A resource, in the shape MCP's `resources/list` returns. */\nexport interface AgentResource {\n readonly uri: string;\n readonly name: string;\n readonly description?: string;\n readonly mimeType: 'application/json';\n}\n\n/** What a tool call returned, in the shape MCP's `tools/call` returns. */\nexport interface AgentToolResult {\n readonly content: readonly { readonly type: 'text'; readonly text: string }[];\n readonly structuredContent?: Record<string, unknown>;\n readonly isError: boolean;\n}\n\n/** A command an agent wants to send that its contract says a person should approve. */\nexport interface AgentConfirmation {\n readonly channel: string;\n readonly command: string;\n readonly description?: string;\n /** The arguments, by parameter name. */\n readonly arguments: Readonly<Record<string, unknown>>;\n readonly destructive: boolean;\n}\n\nexport interface AgentSurfaceOptions {\n /**\n * Asks the person whether a `@confirm` command may be sent. Resolve\n * true to send it. Without this, such a command is refused, which is\n * the safe reading of a contract that asked for a person.\n */\n confirm?: (request: AgentConfirmation) => boolean | Promise<boolean>;\n /**\n * How long the view must be quiet after a command before the call\n * returns it, in milliseconds (default 50). A command whose effect is\n * synchronous settles at once; one that waits on a request settles\n * when the patch lands, or at `settleMs`.\n */\n quietMs?: number;\n /** The longest a call waits for the view to settle (default 1000). */\n settleMs?: number;\n}\n\n/**\n * Any surface an MCP server can speak for: one in this thread, or one\n * across a port whose every answer is a promise.\n */\nexport interface AgentSurfaceLike {\n tools(): readonly AgentTool[] | Promise<readonly AgentTool[]>;\n call(name: string, args: Readonly<Record<string, unknown>> | undefined): Promise<AgentToolResult>;\n resources(): readonly AgentResource[] | Promise<readonly AgentResource[]>;\n read(uri: string): Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;\n dispose?(): void;\n}\n\nexport interface AgentSurface extends AgentSurfaceLike {\n tools(): readonly AgentTool[];\n /** Calls a tool by name. Never throws: a failure is a result with `isError`, which an agent can read and correct. */\n call(name: string, args: Readonly<Record<string, unknown>> | undefined): Promise<AgentToolResult>;\n resources(): readonly AgentResource[];\n /** The current view of the channel a resource names, or undefined for a URI this surface does not hold. */\n read(uri: string): Record<string, unknown> | undefined;\n /** Stops following every view. */\n dispose(): void;\n}\n\n/** The longest tool name MCP clients accept. */\nconst MAX_NAME = 64;\n\ninterface Entry {\n readonly served: ServedChannel;\n readonly name: string;\n readonly description?: string;\n readonly viewSchema?: JsonSchema;\n readonly view: Record<string, unknown>;\n readonly commands: ReadonlyMap<string, CommandSchema | null>;\n}\n\ntype Handler = (request: Readonly<Record<string, unknown>> | undefined) => Promise<AgentToolResult>;\n\nexport function agentSurface(channels: readonly ServedChannel[], options: AgentSurfaceOptions = {}): AgentSurface {\n const quietMs = options.quietMs ?? 50;\n const settleMs = options.settleMs ?? 1000;\n const subscriptions: Subscription[] = [];\n const entries: Entry[] = [];\n const tools: AgentTool[] = [];\n const handlers = new Map<string, Handler>();\n let lastChange = 0;\n let following = false;\n\n /**\n * Subscribed on first use rather than at construction, so a surface\n * built at startup and never called costs the application nothing:\n * a view key may be a cold observable doing real work per subscriber.\n */\n const follow = () => {\n if (following) {\n return;\n }\n following = true;\n for (const entry of entries) {\n for (const [key, observable] of Object.entries(entry.served.source.view)) {\n subscriptions.push(\n observable.subscribe(value => {\n entry.view[key] = value;\n lastChange = Date.now();\n })\n );\n }\n }\n };\n\n const settle = async () => {\n const start = Date.now();\n for (;;) {\n await new Promise(resolve => setTimeout(resolve, quietMs));\n const now = Date.now();\n if (now - lastChange >= quietMs || now - start >= settleMs) {\n return;\n }\n }\n };\n\n const add = (tool: AgentTool, handler: Handler) => {\n if (handlers.has(tool.name)) {\n throw new Error(\n `Two tools would be called '${tool.name}'. A channel and command name pair has to be unique once joined ` +\n 'with an underscore, and a command cannot be called `view`.'\n );\n }\n tools.push(tool);\n handlers.set(tool.name, handler);\n };\n\n for (const served of channels) {\n const schema = channelSchema(served.token as never);\n const name = toolName(served.token.name);\n const commands = new Map<string, CommandSchema | null>();\n for (const command of Object.keys(served.source.commands ?? {})) {\n const described = schema?.commands[command];\n if (described?.hidden !== true) {\n commands.set(command, described ?? null);\n }\n }\n const entry: Entry = {\n served,\n name,\n description: schema?.description,\n viewSchema: schema?.view,\n view: { ...(served.token.initial as Record<string, unknown>) },\n commands\n };\n entries.push(entry);\n\n const about = entry.description === undefined ? '' : `\\n\\n${entry.description}`;\n add(\n {\n name: toolName(`${served.token.name}_view`),\n title: `Read ${served.token.name}`,\n description: `What the ${served.token.name} channel currently holds.${about}`,\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n ...(entry.viewSchema === undefined ? {} : { outputSchema: entry.viewSchema }),\n annotations: { readOnlyHint: true, openWorldHint: false }\n },\n async () => {\n follow();\n return viewResult(entry, `The ${served.token.name} view.`);\n }\n );\n\n for (const [command, described] of commands) {\n add(commandTool(entry, command, described), args => send(entry, command, described, args));\n }\n }\n\n const send = async (\n entry: Entry,\n command: string,\n described: CommandSchema | null,\n args: Readonly<Record<string, unknown>> | undefined\n ): Promise<AgentToolResult> => {\n follow();\n const channel = entry.served.token.name;\n const input = args ?? {};\n let positional: unknown[];\n if (described === null) {\n const list = input.arguments ?? [];\n if (!Array.isArray(list)) {\n return failure('arguments must be an array of the values the command takes, in order.');\n }\n positional = list;\n } else {\n const problem = validate(described.input, input);\n if (problem !== null) {\n return failure(problem);\n }\n // Bytes arrive as base64, which is all JSON can carry, and leave\n // as the typed array or buffer the command was written to take.\n const properties = (described.input.properties as Record<string, JsonSchema> | undefined) ?? {};\n positional = described.parameters.map(parameter =>\n decodeBinary(properties[parameter], input[parameter], described.input)\n );\n if (described.rest === true) {\n const spread = positional.pop();\n positional.push(...(Array.isArray(spread) ? spread : []));\n }\n while (positional.length > 0 && positional[positional.length - 1] === undefined) {\n positional.pop();\n }\n if (described.confirm === true) {\n if (options.confirm === undefined) {\n return failure(\n `${channel}.${command} asks for a person to approve it, and this application has given agents no way ` +\n 'to ask. Ask the person to do it themselves.'\n );\n }\n const approved = await options.confirm({\n channel,\n command,\n description: described.description,\n arguments: input,\n destructive: described.destructive === true\n });\n if (!approved) {\n return failure(`The person declined ${channel}.${command}. Do not send it again unless they ask.`);\n }\n }\n }\n const handler = entry.served.source.commands?.[command] as ((...values: unknown[]) => void) | undefined;\n if (handler === undefined) {\n return failure(`${channel} has no command ${command}.`);\n }\n try {\n handler(...positional);\n } catch (error) {\n return failure(`${channel}.${command} failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n await settle();\n return viewResult(entry, `Sent ${command} to ${channel}. The view afterwards:`);\n };\n\n return {\n tools: () => tools,\n call: async (name, args) => {\n const handler = handlers.get(name);\n if (handler === undefined) {\n return failure(`There is no tool called ${name}. The tools are ${[...handlers.keys()].join(', ')}.`);\n }\n return handler(args);\n },\n resources: () =>\n entries.map(entry => ({\n uri: resourceUri(entry.served.token.name),\n name: entry.served.token.name,\n ...(entry.description === undefined ? {} : { description: entry.description }),\n mimeType: 'application/json' as const\n })),\n read: uri => {\n const entry = entries.find(candidate => resourceUri(candidate.served.token.name) === uri);\n if (entry === undefined) {\n return undefined;\n }\n follow();\n return { ...entry.view };\n },\n dispose: () => {\n for (const subscription of subscriptions) {\n subscription.unsubscribe();\n }\n subscriptions.length = 0;\n following = false;\n }\n };\n}\n\n/** The URI of a channel's view. */\nexport function resourceUri(channel: string): string {\n return `gesso://${encodeURIComponent(channel)}/view`;\n}\n\nfunction commandTool(entry: Entry, command: string, described: CommandSchema | null): AgentTool {\n const channel = entry.served.token.name;\n const destructive = described?.destructive === true;\n const lines = [described?.description ?? `Sends ${command} to the ${channel} channel.`];\n if (described === null) {\n lines.push(\n `This command was not described, so its arguments are a positional list. Read ${toolName(`${channel}_view`)} ` +\n 'first to see what the channel holds.'\n );\n }\n if (described?.confirm === true) {\n lines.push('The person is asked to approve this before it is sent.');\n }\n lines.push('Returns the view after the command has taken effect.');\n return {\n name: toolName(`${channel}_${command}`),\n title: `${channel}: ${command}`,\n description: lines.join('\\n\\n'),\n inputSchema:\n described?.input ??\n ({\n type: 'object',\n properties: { arguments: { type: 'array', description: 'The arguments, in order.' } },\n additionalProperties: false\n } satisfies JsonSchema),\n ...(entry.viewSchema === undefined ? {} : { outputSchema: entry.viewSchema }),\n annotations: {\n readOnlyHint: false,\n ...(destructive ? { destructiveHint: true } : { destructiveHint: false }),\n ...(described?.idempotent === true ? { idempotentHint: true } : {}),\n openWorldHint: false\n }\n };\n}\n\n/**\n * A tool name MCP clients accept: letters, digits, underscores and\n * dashes, at most 64 characters. A channel name is the application's\n * own string, so anything else in it becomes an underscore, and one\n * too long is an error at startup rather than a tool a client drops.\n */\nfunction toolName(raw: string): string {\n const name = raw.replace(/[^A-Za-z0-9_-]/g, '_');\n if (name.length > MAX_NAME) {\n throw new Error(`The tool name '${name}' is longer than ${MAX_NAME} characters. Shorten the channel's name.`);\n }\n return name;\n}\n\nfunction viewResult(entry: Entry, lead: string): AgentToolResult {\n const view = { ...entry.view };\n return {\n content: [{ type: 'text', text: `${lead}\\n${JSON.stringify(view, null, 2)}` }],\n structuredContent: view,\n isError: false\n };\n}\n\nfunction failure(text: string): AgentToolResult {\n return { content: [{ type: 'text', text }], isError: true };\n}\n","import type { AgentConfirmation, AgentResource, AgentSurfaceLike, AgentTool, AgentToolResult } from './AgentSurface';\n\n/**\n * An agent surface across a thread.\n *\n * A web application's channels are served in workers, and an agent\n * reaches the page, so the surface has to cross from one to the other.\n * The worker that serves channels answers a `gesso:agent` port with its\n * own surface (`serveAgentPort`); the page holds the other end as a\n * surface of its own (`remoteSurface`); and a thread that knows several\n * such ports, the render worker with its channel workers behind it,\n * offers them as one (`combineSurfaces`).\n *\n * The four operations cross as they are. MCP itself is spoken only at\n * the end that faces the agent, so nothing in a worker parses JSON-RPC.\n */\n\n/** The port key a thread answers with its agent surface. */\nexport const AGENT_PORT = 'gesso:agent';\n\n/** What crosses an agent port, page to worker. */\nexport type AgentPortRequest =\n | { id: number; op: 'tools' }\n | { id: number; op: 'resources' }\n | { id: number; op: 'call'; name: string; args?: Readonly<Record<string, unknown>> }\n | { id: number; op: 'read'; uri: string };\n\n/** A request before it is given an id. */\ntype Unsent<T> = T extends unknown ? Omit<T, 'id'> : never;\n\n/** What crosses back. */\nexport type AgentPortResponse = { id: number; result: unknown } | { id: number; error: string };\n\n/**\n * The question going the other way: a command marked `@confirm` needs\n * a person, the person is at the far end, so the thread that serves\n * the channel asks down the port it was asked on and waits.\n */\nexport type AgentPortConfirm = { confirm: number; request: AgentConfirmation } | { confirm: number; approved: boolean };\n\n/** How a thread asks the person, wherever the person is. */\nexport type AgentConfirm = (request: AgentConfirmation) => boolean | Promise<boolean>;\n\n/** A `MessagePort`, or anything that posts and receives like one. */\ntype AgentPort = Pick<MessagePort, 'postMessage' | 'onmessage'>;\n\n/**\n * Answers a port with a surface.\n *\n * The surface is made on the first request rather than when the port\n * arrives: a port is opened by a page that may never ask anything, and\n * a surface subscribes to every view key once it is used. It is given\n * a `confirm` that asks the far end of this port, so a command marked\n * `@confirm` reaches the person wherever they are; the far end answers\n * no when it has no way to ask.\n */\nexport function serveAgentPort(port: AgentPort, makeSurface: (confirm: AgentConfirm) => AgentSurfaceLike): () => void {\n let surface: AgentSurfaceLike | undefined;\n let nextConfirm = 1;\n const asking = new Map<number, (approved: boolean) => void>();\n const confirm: AgentConfirm = request =>\n new Promise(resolve => {\n const id = nextConfirm++;\n asking.set(id, resolve);\n port.postMessage({ confirm: id, request } satisfies AgentPortConfirm);\n });\n port.onmessage = async event => {\n const data = event.data as AgentPortRequest | AgentPortConfirm;\n if ('confirm' in data && 'approved' in data) {\n asking.get(data.confirm)?.(data.approved);\n asking.delete(data.confirm);\n return;\n }\n const request = data as AgentPortRequest;\n if (typeof request?.id !== 'number') {\n return;\n }\n surface ??= makeSurface(confirm);\n try {\n port.postMessage({ id: request.id, result: await answer(surface, request) } satisfies AgentPortResponse);\n } catch (error) {\n port.postMessage({\n id: request.id,\n error: error instanceof Error ? error.message : String(error)\n } satisfies AgentPortResponse);\n }\n };\n return () => {\n port.onmessage = null;\n surface?.dispose?.();\n for (const resolve of asking.values()) {\n resolve(false);\n }\n asking.clear();\n };\n}\n\nfunction answer(surface: AgentSurfaceLike, request: AgentPortRequest): unknown {\n switch (request.op) {\n case 'tools':\n return surface.tools();\n case 'resources':\n return surface.resources();\n case 'call':\n return surface.call(request.name, request.args);\n case 'read':\n return surface.read(request.uri) ?? null;\n }\n}\n\n/**\n * The surface at the other end of a port.\n *\n * A request nobody answers within `timeoutMs` (default 2000) is taken\n * as a thread with nothing to offer: a worker that serves no channels\n * never installs the answering side, and an agent asking the whole\n * application should hear about the threads that do rather than wait\n * on one that does not.\n */\nexport function remoteSurface(\n port: AgentPort,\n options: { timeoutMs?: number; confirm?: AgentConfirm } = {}\n): AgentSurfaceLike {\n const timeoutMs = options.timeoutMs ?? 2000;\n let next = 1;\n const pending = new Map<number, { resolve: (value: unknown) => void; reject: (error: Error) => void }>();\n port.onmessage = event => {\n const question = event.data as AgentPortConfirm;\n if (question !== null && typeof question === 'object' && 'request' in question) {\n // Asked to put a command to the person. With nobody to ask, the\n // answer is no, which is what a contract asking for a person means.\n void Promise.resolve(options.confirm?.(question.request) ?? false).then(\n approved => port.postMessage({ confirm: question.confirm, approved } satisfies AgentPortConfirm),\n () => port.postMessage({ confirm: question.confirm, approved: false } satisfies AgentPortConfirm)\n );\n return;\n }\n const response = event.data as AgentPortResponse;\n const waiting = pending.get(response?.id);\n if (waiting === undefined) {\n return;\n }\n pending.delete(response.id);\n if ('error' in response) {\n waiting.reject(new Error(response.error));\n } else {\n waiting.resolve(response.result);\n }\n };\n const ask = <T>(request: Unsent<AgentPortRequest>, fallback: T, timeout = timeoutMs): Promise<T> =>\n new Promise<T>((resolve, reject) => {\n const id = next++;\n const timer = setTimeout(() => {\n pending.delete(id);\n resolve(fallback);\n }, timeout);\n pending.set(id, {\n resolve: value => {\n clearTimeout(timer);\n resolve(value as T);\n },\n reject: error => {\n clearTimeout(timer);\n reject(error);\n }\n });\n port.postMessage({ ...request, id });\n });\n return {\n tools: () => ask<readonly AgentTool[]>({ op: 'tools' }, []),\n resources: () => ask<readonly AgentResource[]>({ op: 'resources' }, []),\n // A command may wait on the person through `confirm`, so a call is\n // given far longer than a listing before it is abandoned.\n call: (name, args) =>\n ask<AgentToolResult>(\n { op: 'call', name, args },\n { content: [{ type: 'text', text: `${name} did not answer in time.` }], isError: true },\n Math.max(timeoutMs, 120_000)\n ),\n read: async uri => (await ask<Record<string, unknown> | null>({ op: 'read', uri }, null)) ?? undefined\n };\n}\n\n/**\n * Several surfaces as one: tools and resources listed together, a call\n * or a read sent to whichever surface listed it. When two list the same\n * name, the first keeps it, as the order of the threads is the order\n * the application registered them in.\n */\nexport function combineSurfaces(surfaces: readonly AgentSurfaceLike[]): AgentSurfaceLike {\n const toolOwners = new Map<string, AgentSurfaceLike>();\n const resourceOwners = new Map<string, AgentSurfaceLike>();\n\n const tools = async () => {\n const lists = await Promise.all(surfaces.map(surface => surface.tools()));\n toolOwners.clear();\n const out: AgentTool[] = [];\n lists.forEach((list, index) => {\n for (const tool of list) {\n if (!toolOwners.has(tool.name)) {\n toolOwners.set(tool.name, surfaces[index]);\n out.push(tool);\n }\n }\n });\n return out;\n };\n\n const resources = async () => {\n const lists = await Promise.all(surfaces.map(surface => surface.resources()));\n resourceOwners.clear();\n const out: AgentResource[] = [];\n lists.forEach((list, index) => {\n for (const resource of list) {\n if (!resourceOwners.has(resource.uri)) {\n resourceOwners.set(resource.uri, surfaces[index]);\n out.push(resource);\n }\n }\n });\n return out;\n };\n\n return {\n tools,\n resources,\n call: async (name, args) => {\n if (!toolOwners.has(name)) {\n await tools();\n }\n const owner = toolOwners.get(name);\n if (owner === undefined) {\n const known = [...toolOwners.keys()].join(', ');\n return {\n content: [{ type: 'text', text: `There is no tool called ${name}. The tools are ${known}.` }],\n isError: true\n };\n }\n return owner.call(name, args);\n },\n read: async uri => {\n if (!resourceOwners.has(uri)) {\n await resources();\n }\n return resourceOwners.get(uri)?.read(uri);\n },\n dispose: () => {\n for (const surface of surfaces) {\n surface.dispose?.();\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { f as ChannelToken, m as CommandMap, p as Command, r as ChannelPort } from "./ChannelProtocol-ByNoHujM.js";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { UiKeyModifiers, UiSemanticsAction, UiSemanticsMap, UiSemanticsRecord } from "gesso-core";
|
|
4
|
+
//#region src/channel/ChannelSchema.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A JSON Schema (draft 2020-12) object.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately loose: the framework stores and hands back schemas and
|
|
9
|
+
* never interprets one, so a precise type would be a second JSON Schema
|
|
10
|
+
* specification to keep in step with the real one.
|
|
11
|
+
*/
|
|
12
|
+
type JsonSchema = {
|
|
13
|
+
readonly [keyword: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
/** One command, described. */
|
|
16
|
+
interface CommandSchema {
|
|
17
|
+
/** The command's JSDoc summary. */
|
|
18
|
+
readonly description?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The parameter names, in the order the command takes them.
|
|
21
|
+
*
|
|
22
|
+
* A command is called positionally, `move(from, to)`, while a tool
|
|
23
|
+
* call names its arguments, so this is what maps one onto the other.
|
|
24
|
+
*/
|
|
25
|
+
readonly parameters: readonly string[];
|
|
26
|
+
/** The last parameter is a rest parameter, and its array is spread into the call. */
|
|
27
|
+
readonly rest?: true;
|
|
28
|
+
/** An object schema, one property per parameter, keyed by parameter name. */
|
|
29
|
+
readonly input: JsonSchema;
|
|
30
|
+
/** `@destructive`: what it does cannot be undone. */
|
|
31
|
+
readonly destructive?: true;
|
|
32
|
+
/** `@idempotent`: sending it twice changes nothing the first did not. */
|
|
33
|
+
readonly idempotent?: true;
|
|
34
|
+
/** `@confirm`: a person should approve it before anything else sends it. */
|
|
35
|
+
readonly confirm?: true;
|
|
36
|
+
/** `@hidden`: for the application's own components, not for an agent. */
|
|
37
|
+
readonly hidden?: true;
|
|
38
|
+
}
|
|
39
|
+
/** A channel, described. */
|
|
40
|
+
interface ChannelSchema {
|
|
41
|
+
/** The token's JSDoc summary. */
|
|
42
|
+
readonly description?: string;
|
|
43
|
+
/** An object schema, one property per view key. */
|
|
44
|
+
readonly view: JsonSchema;
|
|
45
|
+
readonly commands: {
|
|
46
|
+
readonly [name: string]: CommandSchema;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Attaches a schema to a token.
|
|
51
|
+
*
|
|
52
|
+
* `gesso-vite-plugin` calls this at the bottom of each contract module
|
|
53
|
+
* it reads, so it runs once, when the module is first imported. Call it
|
|
54
|
+
* yourself to describe a channel the plugin does not see. A second call
|
|
55
|
+
* for the same token replaces the first, which is what a hot-replaced
|
|
56
|
+
* contract module does.
|
|
57
|
+
*/
|
|
58
|
+
declare function describeChannel(token: ChannelToken<object, object>, schema: ChannelSchema): void;
|
|
59
|
+
/** The schema attached to a token, or undefined when nothing described it. */
|
|
60
|
+
declare function channelSchema(token: ChannelToken<object, object>): ChannelSchema | undefined;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/worker/WorkerPorts.d.ts
|
|
63
|
+
/**
|
|
64
|
+
* Named `MessagePort`s into a worker.
|
|
65
|
+
*
|
|
66
|
+
* A worker's global `onmessage` is a single channel, so a worker that
|
|
67
|
+
* receives messages on it can host exactly one conversation. That is
|
|
68
|
+
* why a store in a data worker used to mean a worker per store: the
|
|
69
|
+
* client claimed the `Worker` object itself, and a second one had
|
|
70
|
+
* nowhere to go.
|
|
71
|
+
*
|
|
72
|
+
* A handshake fixes it. The client opens a `MessageChannel`, keeps one
|
|
73
|
+
* end and transfers the other with a name; the worker serves that name
|
|
74
|
+
* and the two ends talk privately from then on. The global channel is
|
|
75
|
+
* used once per conversation and carries nothing else.
|
|
76
|
+
*
|
|
77
|
+
* Nothing here knows what travels over a port. It is the transport the
|
|
78
|
+
* store replication in `../store/worker` runs on today and the barrier
|
|
79
|
+
* contract will run on next.
|
|
80
|
+
*/
|
|
81
|
+
/** A port-shaped thing: `MessagePort` and `Worker` both satisfy it. */
|
|
82
|
+
interface MessageEndpoint {
|
|
83
|
+
postMessage(message: unknown): void;
|
|
84
|
+
onmessage: ((event: {
|
|
85
|
+
data: unknown;
|
|
86
|
+
}) => void) | null;
|
|
87
|
+
}
|
|
88
|
+
/** The one message the global channel carries. */
|
|
89
|
+
interface PortHandshake {
|
|
90
|
+
type: 'gesso:port';
|
|
91
|
+
key: string;
|
|
92
|
+
}
|
|
93
|
+
declare function isPortHandshake(value: unknown): value is PortHandshake;
|
|
94
|
+
/**
|
|
95
|
+
* A worker spawned at most once, serving any number of named ports.
|
|
96
|
+
*
|
|
97
|
+
* Handed to several `useChannel` calls, it is what lets one worker
|
|
98
|
+
* hold a whole application layer instead of one channel. Spawning is
|
|
99
|
+
* deferred to the first `open`, so a handle nobody uses costs nothing.
|
|
100
|
+
*/
|
|
101
|
+
interface WorkerHandle {
|
|
102
|
+
/**
|
|
103
|
+
* Opens a private channel under `key`, spawning the worker if this
|
|
104
|
+
* is the first one.
|
|
105
|
+
*/
|
|
106
|
+
open(key: string): MessagePort;
|
|
107
|
+
/** Whether the worker has been spawned. */
|
|
108
|
+
readonly spawned: boolean;
|
|
109
|
+
/** Stops the worker, if it was ever started. */
|
|
110
|
+
terminate(): void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Stands for "whichever worker the shell spawned for the application".
|
|
114
|
+
*
|
|
115
|
+
* A registration inside the render worker cannot name that worker: it
|
|
116
|
+
* is created by the shell and its port only arrives with `init`, long
|
|
117
|
+
* after `useChannel` and `useService` have run. This sentinel is what a
|
|
118
|
+
* registration puts there instead, and the render worker swaps it for
|
|
119
|
+
* the real handle once the port shows up.
|
|
120
|
+
*
|
|
121
|
+
* Opening a port on it before then is a bug rather than a race, so it
|
|
122
|
+
* says so.
|
|
123
|
+
*/
|
|
124
|
+
declare const APPLICATION_WORKER: WorkerHandle;
|
|
125
|
+
/**
|
|
126
|
+
* Anything a handshake can be posted to with a port attached.
|
|
127
|
+
* `Worker` and `MessagePort` both satisfy it.
|
|
128
|
+
*/
|
|
129
|
+
interface TransferTarget {
|
|
130
|
+
postMessage(message: unknown, transfer: Transferable[]): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A handle over an endpoint someone else owns.
|
|
134
|
+
*
|
|
135
|
+
* The shell spawns the application worker and hands the render worker
|
|
136
|
+
* one end of a channel to it; this is what the render worker opens
|
|
137
|
+
* named ports over. `terminate` is a no-op — the lifetime belongs to
|
|
138
|
+
* whoever created the endpoint, and a handle that could kill a worker
|
|
139
|
+
* it did not spawn would be a surprising thing to hand out.
|
|
140
|
+
*/
|
|
141
|
+
declare function portHandle(endpoint: TransferTarget): WorkerHandle;
|
|
142
|
+
/**
|
|
143
|
+
* Routes handshakes arriving on a transferred port through the same
|
|
144
|
+
* handlers as the worker's own global channel.
|
|
145
|
+
*
|
|
146
|
+
* The shell owns the application worker and gives the render worker a
|
|
147
|
+
* port to it, so handshakes reach this worker two ways: on its global
|
|
148
|
+
* channel (whoever spawned it) and on that port (whoever was given
|
|
149
|
+
* it). Both should be served by the same handlers, and neither end
|
|
150
|
+
* should have to know which route a channel came in on.
|
|
151
|
+
*/
|
|
152
|
+
interface HubMessage {
|
|
153
|
+
type: 'gesso:hub';
|
|
154
|
+
}
|
|
155
|
+
declare function isHubMessage(value: unknown): value is HubMessage;
|
|
156
|
+
/**
|
|
157
|
+
* Wraps a worker factory so the worker is created once and shared.
|
|
158
|
+
*
|
|
159
|
+
* A factory rather than a URL for the same reason the render worker
|
|
160
|
+
* takes one: a bundler only emits a chunk for a worker it can see
|
|
161
|
+
* constructed literally in the calling module.
|
|
162
|
+
*
|
|
163
|
+
* const data = workerHandle(
|
|
164
|
+
* () => new Worker(new URL('./data.worker.ts', import.meta.url), { type: 'module' })
|
|
165
|
+
* );
|
|
166
|
+
*/
|
|
167
|
+
declare function workerHandle(factory: () => Worker): WorkerHandle;
|
|
168
|
+
/**
|
|
169
|
+
* Minimal view of a worker's global scope, so this module type-checks
|
|
170
|
+
* against the DOM lib without pulling in the WebWorker lib.
|
|
171
|
+
*/
|
|
172
|
+
interface PortHost {
|
|
173
|
+
onmessage: ((event: {
|
|
174
|
+
data: unknown;
|
|
175
|
+
ports?: readonly MessagePort[];
|
|
176
|
+
}) => void) | null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* What a port is answered with when no handler claimed its name.
|
|
180
|
+
*
|
|
181
|
+
* Its own message type rather than a store's or a channel's, because
|
|
182
|
+
* the transport does not know which of them the client is: both
|
|
183
|
+
* recognise it, so a mismatched name is loud either way.
|
|
184
|
+
*/
|
|
185
|
+
interface PortErrorMessage {
|
|
186
|
+
type: 'port:error';
|
|
187
|
+
message: string;
|
|
188
|
+
}
|
|
189
|
+
declare function isPortErrorMessage(value: unknown): value is PortErrorMessage;
|
|
190
|
+
/**
|
|
191
|
+
* Serves named ports inside a worker.
|
|
192
|
+
*
|
|
193
|
+
* Call it synchronously at the top level of the worker module, before
|
|
194
|
+
* any await, so no handshake is missed.
|
|
195
|
+
*
|
|
196
|
+
* `onPort` returns whether it took the port. Returning false passes
|
|
197
|
+
* the handshake to whatever was serving before, which is what lets two
|
|
198
|
+
* kinds of thing — stores and channels, during the migration — share
|
|
199
|
+
* one worker: each answers for its own names and declines the rest.
|
|
200
|
+
* When nobody accepts, the port is answered with an error naming
|
|
201
|
+
* everything the worker does serve, because a handshake that silently
|
|
202
|
+
* matched nothing leaves the client waiting forever with nothing said.
|
|
203
|
+
*
|
|
204
|
+
* `names` is only read to build that message.
|
|
205
|
+
*
|
|
206
|
+
* Returns a function that stops serving.
|
|
207
|
+
*/
|
|
208
|
+
declare function servePorts(onPort: (key: string, port: MessagePort) => boolean, names: () => readonly string[], host?: PortHost): () => void;
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/channel/provide.d.ts
|
|
211
|
+
/** What the owning thread supplies for a channel. */
|
|
212
|
+
interface ChannelSource<View extends object, Commands extends object> {
|
|
213
|
+
/** One observable per declared view key. */
|
|
214
|
+
view: { readonly [K in keyof View]: Observable<View[K]>; };
|
|
215
|
+
/** One handler per declared command. */
|
|
216
|
+
commands?: Commands;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Publishes a channel from the thread that owns its data.
|
|
220
|
+
*
|
|
221
|
+
* Each view key is subscribed, diffed against what the other side last
|
|
222
|
+
* saw, and sent as patches. Whatever produced the observable — a bare
|
|
223
|
+
* subject or a stack of layers — stays here; only plain data crosses.
|
|
224
|
+
*
|
|
225
|
+
* Keys are subscribed on the first sync request, so a channel nobody
|
|
226
|
+
* is watching costs nothing.
|
|
227
|
+
*/
|
|
228
|
+
declare function provide<View extends object, Commands extends object>(token: ChannelToken<View, Commands>, source: ChannelSource<View, Commands>, port: ChannelPort): ProvidedChannel;
|
|
229
|
+
declare class ProvidedChannel {
|
|
230
|
+
private readonly token;
|
|
231
|
+
private readonly source;
|
|
232
|
+
private readonly port;
|
|
233
|
+
private readonly subscriptions;
|
|
234
|
+
/**
|
|
235
|
+
* What the other side is known to hold, seeded from the token's
|
|
236
|
+
* initial value — which the replica also starts from, so an app
|
|
237
|
+
* whose first emission equals the initial sends nothing at all.
|
|
238
|
+
*/
|
|
239
|
+
private readonly previous;
|
|
240
|
+
private readonly checked;
|
|
241
|
+
private synced;
|
|
242
|
+
constructor(token: ChannelToken<object, CommandMap>, source: ChannelSource<object, CommandMap>, port: ChannelPort);
|
|
243
|
+
private receive;
|
|
244
|
+
private runCommand;
|
|
245
|
+
private sync;
|
|
246
|
+
private publish;
|
|
247
|
+
/** Re-sends every key in full, for a client that reattached. */
|
|
248
|
+
private resend;
|
|
249
|
+
private post;
|
|
250
|
+
dispose(): void;
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/channel/serveChannels.d.ts
|
|
254
|
+
/**
|
|
255
|
+
* One channel a worker offers: its token and what feeds it.
|
|
256
|
+
*
|
|
257
|
+
* Types erased structurally, for the same reason `ChannelRegistration`
|
|
258
|
+
* erases them — a list of channels has no single generic
|
|
259
|
+
* instantiation, and making every caller cast to reach one is worse
|
|
260
|
+
* than describing what is actually needed.
|
|
261
|
+
*/
|
|
262
|
+
interface ServedChannel {
|
|
263
|
+
token: {
|
|
264
|
+
name: string;
|
|
265
|
+
initial: object;
|
|
266
|
+
};
|
|
267
|
+
source: {
|
|
268
|
+
view: Record<string, Observable<unknown>>;
|
|
269
|
+
commands?: Record<string, Command>;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* One served channel, with its source checked against its token.
|
|
274
|
+
*
|
|
275
|
+
* `ServedChannel` is erased on purpose, so one list can hold channels
|
|
276
|
+
* of every shape; the cost is that a view key the token declares and
|
|
277
|
+
* the source forgets is found at startup, by the error `provide`
|
|
278
|
+
* reports, rather than by the compiler. This is the typed seam: the
|
|
279
|
+
* source must hold an Observable for every key of the token's view and
|
|
280
|
+
* a handler for every command, and each handler takes the arguments
|
|
281
|
+
* the token declares, so none of them needs an annotation.
|
|
282
|
+
*
|
|
283
|
+
* serveChannels([
|
|
284
|
+
* serve(Catalog, { view: catalog, commands: { add: name => catalog.add(name) } })
|
|
285
|
+
* ]);
|
|
286
|
+
*
|
|
287
|
+
* The view may be any object with the right observables on it, which
|
|
288
|
+
* is often the domain object itself when its properties are named
|
|
289
|
+
* after the keys. Only the declared keys are read from it.
|
|
290
|
+
*/
|
|
291
|
+
declare function serve<View extends object, Commands extends object>(token: ChannelToken<View, Commands>, source: ChannelSource<View, Commands>): ServedChannel;
|
|
292
|
+
/**
|
|
293
|
+
* Publishes channels from an application worker.
|
|
294
|
+
*
|
|
295
|
+
* Call it synchronously at the top level of the worker module, before
|
|
296
|
+
* any await, so no handshake is missed:
|
|
297
|
+
*
|
|
298
|
+
* const catalog = new CatalogViewModel(new CatalogDomain(new OpfsStore()));
|
|
299
|
+
* serveChannels([
|
|
300
|
+
* serve(Catalog, { view: { products: catalog.products$ }, commands: { … } })
|
|
301
|
+
* ]);
|
|
302
|
+
*
|
|
303
|
+
* Everything above this call is the application's own — plain classes,
|
|
304
|
+
* plain observables, no framework import. This function is the entire
|
|
305
|
+
* seam between it and the view.
|
|
306
|
+
*
|
|
307
|
+
* Returns a function that stops serving and disposes what it provided.
|
|
308
|
+
*/
|
|
309
|
+
declare function serveChannels(channels: readonly ServedChannel[], host?: PortHost): () => void;
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/agent/AgentSurface.d.ts
|
|
312
|
+
/**
|
|
313
|
+
* An application's channels, as an AI agent sees them.
|
|
314
|
+
*
|
|
315
|
+
* A channel is already the shape an agent wants. Its view is what the
|
|
316
|
+
* application currently holds, as plain data, and its commands are
|
|
317
|
+
* the things it can be asked to do, by name, with typed arguments. A
|
|
318
|
+
* component reaches both through a replica; this reaches them from the
|
|
319
|
+
* other side, through the same `{ token, source }` the application
|
|
320
|
+
* already hands to `serveChannels` or `createDesktopApp`, so an agent
|
|
321
|
+
* sends a command to exactly the handler a click does and sees its
|
|
322
|
+
* effect in exactly the view a screen draws.
|
|
323
|
+
*
|
|
324
|
+
* For each channel it offers:
|
|
325
|
+
*
|
|
326
|
+
* - a resource, `gesso://<channel>/view`, holding the view;
|
|
327
|
+
* - a tool, `<channel>_view`, returning the same thing, because more
|
|
328
|
+
* agents call tools than read resources;
|
|
329
|
+
* - a tool per command, `<channel>_<command>`, whose input schema is
|
|
330
|
+
* the command's parameters by name. Calling it sends the command,
|
|
331
|
+
* waits for the view to settle, and returns the view as it now is,
|
|
332
|
+
* so the agent sees what its call did without a second round trip.
|
|
333
|
+
*
|
|
334
|
+
* The descriptions come from `channelSchema(token)`, which
|
|
335
|
+
* `gesso-vite-plugin` writes from the contract's JSDoc. A command
|
|
336
|
+
* marked `@hidden` is not offered; `@destructive` and `@idempotent`
|
|
337
|
+
* become hints a client shows; `@confirm` means the person is asked,
|
|
338
|
+
* through `confirm`, before the command is sent, and a surface given
|
|
339
|
+
* no way to ask refuses it. A channel nobody described is still
|
|
340
|
+
* offered, with its commands taking their arguments as a positional
|
|
341
|
+
* list, and says so in its description.
|
|
342
|
+
*
|
|
343
|
+
* Nothing here knows about a transport. `mcpHandler` serves it over
|
|
344
|
+
* MCP's HTTP transport; anything else can call `tools`, `call`,
|
|
345
|
+
* `resources` and `read` directly.
|
|
346
|
+
*/
|
|
347
|
+
/** A tool, in the shape MCP's `tools/list` returns. */
|
|
348
|
+
interface AgentTool {
|
|
349
|
+
readonly name: string;
|
|
350
|
+
readonly title?: string;
|
|
351
|
+
readonly description: string;
|
|
352
|
+
readonly inputSchema: JsonSchema;
|
|
353
|
+
readonly outputSchema?: JsonSchema;
|
|
354
|
+
readonly annotations: {
|
|
355
|
+
readonly title?: string;
|
|
356
|
+
readonly readOnlyHint: boolean;
|
|
357
|
+
readonly destructiveHint?: boolean;
|
|
358
|
+
readonly idempotentHint?: boolean;
|
|
359
|
+
readonly openWorldHint: false;
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/** A resource, in the shape MCP's `resources/list` returns. */
|
|
363
|
+
interface AgentResource {
|
|
364
|
+
readonly uri: string;
|
|
365
|
+
readonly name: string;
|
|
366
|
+
readonly description?: string;
|
|
367
|
+
readonly mimeType: 'application/json';
|
|
368
|
+
}
|
|
369
|
+
/** What a tool call returned, in the shape MCP's `tools/call` returns. */
|
|
370
|
+
interface AgentToolResult {
|
|
371
|
+
readonly content: readonly {
|
|
372
|
+
readonly type: 'text';
|
|
373
|
+
readonly text: string;
|
|
374
|
+
}[];
|
|
375
|
+
readonly structuredContent?: Record<string, unknown>;
|
|
376
|
+
readonly isError: boolean;
|
|
377
|
+
}
|
|
378
|
+
/** A command an agent wants to send that its contract says a person should approve. */
|
|
379
|
+
interface AgentConfirmation {
|
|
380
|
+
readonly channel: string;
|
|
381
|
+
readonly command: string;
|
|
382
|
+
readonly description?: string;
|
|
383
|
+
/** The arguments, by parameter name. */
|
|
384
|
+
readonly arguments: Readonly<Record<string, unknown>>;
|
|
385
|
+
readonly destructive: boolean;
|
|
386
|
+
}
|
|
387
|
+
interface AgentSurfaceOptions {
|
|
388
|
+
/**
|
|
389
|
+
* Asks the person whether a `@confirm` command may be sent. Resolve
|
|
390
|
+
* true to send it. Without this, such a command is refused, which is
|
|
391
|
+
* the safe reading of a contract that asked for a person.
|
|
392
|
+
*/
|
|
393
|
+
confirm?: (request: AgentConfirmation) => boolean | Promise<boolean>;
|
|
394
|
+
/**
|
|
395
|
+
* How long the view must be quiet after a command before the call
|
|
396
|
+
* returns it, in milliseconds (default 50). A command whose effect is
|
|
397
|
+
* synchronous settles at once; one that waits on a request settles
|
|
398
|
+
* when the patch lands, or at `settleMs`.
|
|
399
|
+
*/
|
|
400
|
+
quietMs?: number;
|
|
401
|
+
/** The longest a call waits for the view to settle (default 1000). */
|
|
402
|
+
settleMs?: number;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Any surface an MCP server can speak for: one in this thread, or one
|
|
406
|
+
* across a port whose every answer is a promise.
|
|
407
|
+
*/
|
|
408
|
+
interface AgentSurfaceLike {
|
|
409
|
+
tools(): readonly AgentTool[] | Promise<readonly AgentTool[]>;
|
|
410
|
+
call(name: string, args: Readonly<Record<string, unknown>> | undefined): Promise<AgentToolResult>;
|
|
411
|
+
resources(): readonly AgentResource[] | Promise<readonly AgentResource[]>;
|
|
412
|
+
read(uri: string): Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
413
|
+
dispose?(): void;
|
|
414
|
+
}
|
|
415
|
+
interface AgentSurface extends AgentSurfaceLike {
|
|
416
|
+
tools(): readonly AgentTool[];
|
|
417
|
+
/** Calls a tool by name. Never throws: a failure is a result with `isError`, which an agent can read and correct. */
|
|
418
|
+
call(name: string, args: Readonly<Record<string, unknown>> | undefined): Promise<AgentToolResult>;
|
|
419
|
+
resources(): readonly AgentResource[];
|
|
420
|
+
/** The current view of the channel a resource names, or undefined for a URI this surface does not hold. */
|
|
421
|
+
read(uri: string): Record<string, unknown> | undefined;
|
|
422
|
+
/** Stops following every view. */
|
|
423
|
+
dispose(): void;
|
|
424
|
+
}
|
|
425
|
+
declare function agentSurface(channels: readonly ServedChannel[], options?: AgentSurfaceOptions): AgentSurface;
|
|
426
|
+
/** The URI of a channel's view. */
|
|
427
|
+
declare function resourceUri(channel: string): string;
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/agent/ui.d.ts
|
|
430
|
+
/**
|
|
431
|
+
* The screen, as tools an agent can use.
|
|
432
|
+
*
|
|
433
|
+
* Channels are the application's own vocabulary, and the right way in
|
|
434
|
+
* for anything they cover. Not everything is covered: a dialog's
|
|
435
|
+
* buttons, a tab, a field the person is halfway through filling. For
|
|
436
|
+
* those an agent has to do what a person does, and a canvas gives it
|
|
437
|
+
* nothing to do that with: no DOM to query, no element to click.
|
|
438
|
+
*
|
|
439
|
+
* The semantics tree is the answer a screen reader already gets. Every
|
|
440
|
+
* control has a role and an accessible name, a state, a value, and an
|
|
441
|
+
* id, so an agent can read the screen as an outline and act on a
|
|
442
|
+
* control by naming it. Acting goes through `applySemanticsAction`,
|
|
443
|
+
* the path the accessibility mirror uses, which turns a press into the
|
|
444
|
+
* same click a pointer makes and a value into the same edit a keyboard
|
|
445
|
+
* makes: an agent can do exactly what a person could, no more, and an
|
|
446
|
+
* open focus trap holds it as it holds Tab.
|
|
447
|
+
*
|
|
448
|
+
* ui_snapshot the screen as an outline, each control with a ref
|
|
449
|
+
* ui_press press a control, by ref or by role and name
|
|
450
|
+
* ui_type replace a field's text
|
|
451
|
+
* ui_focus move focus to a control
|
|
452
|
+
* ui_key press a key where focus is: Enter, Escape, Tab, ArrowDown
|
|
453
|
+
*
|
|
454
|
+
* Each action answers with the outline as it is afterwards.
|
|
455
|
+
*/
|
|
456
|
+
/** What the tools need of the running application. */
|
|
457
|
+
interface UiHost {
|
|
458
|
+
semanticsTree(): UiSemanticsMap;
|
|
459
|
+
focusedNodeId(): string | null;
|
|
460
|
+
applySemanticsAction(action: UiSemanticsAction): void;
|
|
461
|
+
key(key: string, modifiers: UiKeyModifiers): void;
|
|
462
|
+
/**
|
|
463
|
+
* Runs a pending frame now. A tab in the background is sent no
|
|
464
|
+
* frames, and without one an action's effect never reaches the tree
|
|
465
|
+
* this reads back.
|
|
466
|
+
*/
|
|
467
|
+
flush(): void;
|
|
468
|
+
}
|
|
469
|
+
interface UiSurfaceOptions {
|
|
470
|
+
/** How long the screen must hold still after an action before it is read back (default 50 ms). */
|
|
471
|
+
quietMs?: number;
|
|
472
|
+
/** The longest an action waits for that (default 1000 ms). */
|
|
473
|
+
settleMs?: number;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Short names for record ids.
|
|
477
|
+
*
|
|
478
|
+
* A record's id is its path through the tree, `root:0:0:component:1:0:1`,
|
|
479
|
+
* which is exact and long, and an outline repeats one on every line an
|
|
480
|
+
* agent reads. A ref is `e` and a number, given the first time a record
|
|
481
|
+
* is seen and kept for as long as the surface lives, so the ref an agent
|
|
482
|
+
* read in one snapshot still names the same control in the next.
|
|
483
|
+
*/
|
|
484
|
+
declare class UiRefs {
|
|
485
|
+
private readonly byId;
|
|
486
|
+
private readonly byRef;
|
|
487
|
+
refOf(id: string): string;
|
|
488
|
+
/** The record id a ref names, or the input itself when it is not a ref this surface gave. */
|
|
489
|
+
idOf(ref: string): string;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The tools, over a host. `host` returns undefined until the
|
|
493
|
+
* application has started, and a tool called before then says so.
|
|
494
|
+
*/
|
|
495
|
+
declare function uiSurface(host: () => UiHost | undefined, options?: UiSurfaceOptions): AgentSurface;
|
|
496
|
+
/**
|
|
497
|
+
* The tree as an indented outline, one control per line:
|
|
498
|
+
*
|
|
499
|
+
* - button "Add one" [e3]
|
|
500
|
+
* - textbox "Title" value="Groceries" [e4] (focused)
|
|
501
|
+
*
|
|
502
|
+
* Text, because an agent reads it, and an outline is a third the size
|
|
503
|
+
* of the same tree as JSON. A record with neither a role nor a name is
|
|
504
|
+
* structure, and its children are lifted to its depth.
|
|
505
|
+
*/
|
|
506
|
+
declare function outline(tree: UiSemanticsMap, focused: string | null, refs?: UiRefs): string;
|
|
507
|
+
/**
|
|
508
|
+
* The record an action names: by ref, or by role and name. A name
|
|
509
|
+
* matches exactly before it matches as a part, ignoring case, so
|
|
510
|
+
* "Save" finds the button called Save rather than "Save as". More than
|
|
511
|
+
* one match is an error listing them, because guessing which of two
|
|
512
|
+
* buttons an agent meant is how the wrong one gets pressed.
|
|
513
|
+
*/
|
|
514
|
+
declare function resolveTarget(tree: UiSemanticsMap, args: Readonly<Record<string, unknown>>, refs?: UiRefs): UiSemanticsRecord | string;
|
|
515
|
+
//#endregion
|
|
516
|
+
export { portHandle as A, MessageEndpoint as C, isHubMessage as D, WorkerHandle as E, JsonSchema as F, channelSchema as I, describeChannel as L, workerHandle as M, ChannelSchema as N, isPortErrorMessage as O, CommandSchema as P, APPLICATION_WORKER as S, PortHost as T, serve as _, resolveTarget as a, ProvidedChannel as b, AgentResource as c, AgentSurfaceOptions as d, AgentTool as f, ServedChannel as g, resourceUri as h, outline as i, servePorts as j, isPortHandshake as k, AgentSurface as l, agentSurface as m, UiRefs as n, uiSurface as o, AgentToolResult as p, UiSurfaceOptions as r, AgentConfirmation as s, UiHost as t, AgentSurfaceLike as u, serveChannels as v, PortHandshake as w, provide as x, ChannelSource as y };
|
|
517
|
+
//# sourceMappingURL=ui-U39HjNFA.d.ts.map
|