@ryuhq/sdk 0.0.5 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -194,7 +194,10 @@ function dataUrlToBytes(dataUrl) {
194
194
  }
195
195
  return { bytes, mediaType };
196
196
  }
197
- return { bytes: new TextEncoder().encode(decodeURIComponent(payload)), mediaType };
197
+ return {
198
+ bytes: new TextEncoder().encode(decodeURIComponent(payload)),
199
+ mediaType
200
+ };
198
201
  }
199
202
  function bytesToDataUrl(bytes, mediaType) {
200
203
  let binary = "";
@@ -903,7 +906,47 @@ var TurnHookContributionSchema = import_zod.z.object({
903
906
  /** Turn boundary this fires on. Today only `"post_assistant_turn"`. */
904
907
  on: import_zod.z.string().min(1).default("post_assistant_turn"),
905
908
  /** The JS hook body executed in the sandbox (returns a directive). */
906
- code: import_zod.z.string().min(1)
909
+ code: import_zod.z.string().min(1).optional(),
910
+ /** Path to the hook body, relative to the plugin root (`hooks/<name>.js`). */
911
+ code_file: import_zod.z.string().min(1).optional(),
912
+ /**
913
+ * Cheap pre-gate mirroring Core's `HookMatch` (serde name `match` on
914
+ * `TurnHookContribution.run_when`). MUST round-trip through this schema:
915
+ * `ryu pack`/`publish` persist `safeParse(...).data`, so a field missing here
916
+ * is silently STRIPPED before signing — a tool-gated `pre_tool_use` hook
917
+ * (e.g. `tools: ["bash*"]`) would lose its gate and run on EVERY tool call.
918
+ */
919
+ match: import_zod.z.object({
920
+ /** Run only if the request set this composer flag true. */
921
+ flag: import_zod.z.string().optional(),
922
+ /** Run if the last user message starts with any of these prefixes. */
923
+ commands: import_zod.z.array(import_zod.z.string()).default([]),
924
+ /** Run if the plugin has stored state for this conversation. */
925
+ stateful: import_zod.z.boolean().default(false),
926
+ /** Run if `ctx.tool_name` matches any of these `*`-wildcard patterns. */
927
+ tools: import_zod.z.array(import_zod.z.string()).default([])
928
+ }).optional()
929
+ }).refine((h) => Boolean(h.code) !== Boolean(h.code_file), {
930
+ message: "a turn hook must declare exactly one of 'code' (inline body) or 'code_file' (path to hooks/<name>.js)",
931
+ path: ["code_file"]
932
+ });
933
+ var HookEventContributionSchema = import_zod.z.object({
934
+ /** Fully-qualified event id: `<plugin id>#<event name>`, e.g. `@acme/meetings#meeting.ended`. */
935
+ id: import_zod.z.string().min(1),
936
+ /** Human-readable title for the event picker. */
937
+ title: import_zod.z.string().min(1),
938
+ /** What the event means and when it fires. */
939
+ description: import_zod.z.string().optional(),
940
+ /** Example of the `ctx.event` payload. Documentation, not a validated schema. */
941
+ payload_example: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
942
+ });
943
+ var PiExtensionContributionSchema = import_zod.z.object({
944
+ /** Stable id for this extension within the plugin (`[a-z0-9][a-z0-9._-]*`). */
945
+ id: import_zod.z.string().min(1),
946
+ /** Path to the source, relative to the plugin root: `pi-extensions/<name>.ts`. */
947
+ file: import_zod.z.string().min(1),
948
+ /** Optional one-liner describing what the extension adds to the agent. */
949
+ description: import_zod.z.string().optional()
907
950
  });
908
951
  var DEFAULT_WIDGET_MIME = "text/html+skybridge";
909
952
  var DEFAULT_WIDGET_DISPLAY_MODE = "inline";
@@ -941,6 +984,11 @@ var ToolAppConfigSchema = import_zod.z.object({
941
984
  });
942
985
  var ContributesSchema = import_zod.z.object({
943
986
  turn_hooks: import_zod.z.array(TurnHookContributionSchema).default([]),
987
+ /** App events this plugin EMITS — the provider half of the hook system, whose
988
+ * consumer half is `turn_hooks`. Mirrors the Rust `Contributes.hook_events`;
989
+ * omitting it here would have `ryu pack` strip every declared event before
990
+ * signing, leaving an app that emits events nothing is allowed to subscribe to. */
991
+ hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
944
992
  composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
945
993
  settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
946
994
  slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
@@ -948,7 +996,49 @@ var ContributesSchema = import_zod.z.object({
948
996
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
949
997
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
950
998
  * every widget an app authored here declares. */
951
- widgets: import_zod.z.array(WidgetContributionSchema).default([])
999
+ widgets: import_zod.z.array(WidgetContributionSchema).default([]),
1000
+ /** App-registered sidebar sections (header + live list) and buttons (single nav
1001
+ * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
1002
+ * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
1003
+ * `Contributes.sidebar_sections` / `Contributes.sidebar_buttons`. */
1004
+ sidebar_sections: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1005
+ sidebar_buttons: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1006
+ /** App-registered workspace dock panels (a tab in the desktop's bottom/right
1007
+ * dock). Loosely typed for the same reason as the surfaces above — the shell
1008
+ * owns the `panel` render-mode vocabulary and the `spec` payload. Mirrors the
1009
+ * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
1010
+ * strip the dock panel an app declares here. */
1011
+ dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1012
+ /** Deletable data categories the app owns — one "Delete all X" row in Settings
1013
+ * → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
1014
+ * it the CLI's zod parse would strip the declaration before signing, and the
1015
+ * app's danger-zone row would simply never appear on any node that installed
1016
+ * the packed bundle. Loosely typed here for the same reason as the surfaces
1017
+ * above — Core is the layer that types it, because Core is the layer that has
1018
+ * to resolve the id to something that can actually delete the rows. */
1019
+ data_categories: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1020
+ /** Language servers the plugin declares, keyed by server name — the mirror of
1021
+ * Claude Code's `.lsp.json` / `lspServers`, so a config written for either host
1022
+ * loads in the other. Mirrors the Rust-side `Contributes.lsp_servers`; without
1023
+ * it the CLI's zod parse would strip every language server a plugin declares,
1024
+ * before the manifest is signed.
1025
+ *
1026
+ * The ENTRY is deliberately a loose record and not a 13-field `z.object()`
1027
+ * mirroring `LspServerContribution`. Claude Code owns this field vocabulary,
1028
+ * not Ryu: a typed object here would strip a field from a newer Claude release
1029
+ * on its way through `ryu pack` — the same silent-deletion bug this field
1030
+ * exists to fix, one level down. Core is the layer that types it, because Core
1031
+ * is the layer that acts on it. */
1032
+ lsp_servers: import_zod.z.record(import_zod.z.string(), import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default({}),
1033
+ /** Pi extensions the plugin ships — TypeScript the managed `ryu` (Pi) agent
1034
+ * loads at process start. Mirrors the Rust-side `Contributes.pi_extensions`;
1035
+ * without it the CLI's zod parse would strip the declaration before signing,
1036
+ * and the packed plugin would ship a `pi-extensions/` folder nothing loads.
1037
+ *
1038
+ * Typed (not a loose record) because Ryu owns this vocabulary — three fields,
1039
+ * all of them Core-interpreted — unlike `lsp_servers`, whose entry shape is
1040
+ * Claude Code's to extend. */
1041
+ pi_extensions: import_zod.z.array(PiExtensionContributionSchema).default([])
952
1042
  });
953
1043
  var SetupStepSchema = import_zod.z.object({
954
1044
  /** Card heading (e.g. the companion app name). */
@@ -1129,6 +1219,25 @@ var PluginManifestSchema = import_zod.z.object({
1129
1219
  license: import_zod.z.string().optional(),
1130
1220
  /** Square logo/icon URL for the listing card + detail header. */
1131
1221
  iconUrl: import_zod.z.string().optional(),
1222
+ /**
1223
+ * Icon-primitive id for the listing card (Ryu extension): an Iconify/icons0
1224
+ * `prefix:name`, a bare Hugeicons name, or a URL, resolved by the shared `Icon`
1225
+ * primitive. A monochrome GLYPH masked with the current text colour — distinct
1226
+ * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
1227
+ */
1228
+ icon: import_zod.z.string().optional(),
1229
+ /**
1230
+ * Dithered-gradient background for the card's icon square (Ryu extension),
1231
+ * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
1232
+ * name (`green`, `blue`, `purple`, `pink`, `orange`, `red`, `grey`) or a hue
1233
+ * number (0–360); `direction` is where `to` ends up. Renders behind the glyph in
1234
+ * place of a flat `iconBackground`; the render layer validates + falls back.
1235
+ */
1236
+ iconDither: import_zod.z.object({
1237
+ from: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]),
1238
+ to: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
1239
+ direction: import_zod.z.enum(["up", "down", "left", "right"]).optional()
1240
+ }).optional(),
1132
1241
  /** Ordered App-Store-style screenshot gallery URLs (Ryu extension). */
1133
1242
  screenshots: import_zod.z.array(import_zod.z.string()).optional(),
1134
1243
  /** Privacy policy URL surfaced on detail (Ryu extension). */
@@ -1162,6 +1271,14 @@ function coreManifestJsonSchema() {
1162
1271
  // src/runnable/app.ts
1163
1272
  var DEFAULT_APP_WIDGET_MIME = "text/html+skybridge";
1164
1273
  var DEFAULT_APP_DISPLAY_MODE = "inline";
1274
+ var WIDGET_RENDER_GRANT = "widget:render";
1275
+ function withWidgetRenderGrant(grants, widgets) {
1276
+ const out = [...grants];
1277
+ if (widgets.length > 0 && !out.includes(WIDGET_RENDER_GRANT)) {
1278
+ out.push(WIDGET_RENDER_GRANT);
1279
+ }
1280
+ return out;
1281
+ }
1165
1282
  function appToolId(server, name) {
1166
1283
  return `${server}__${name}`;
1167
1284
  }
@@ -1203,9 +1320,25 @@ function defineApp(options) {
1203
1320
  }
1204
1321
  const contributes = {
1205
1322
  turn_hooks: [],
1323
+ // This builder synthesises an app from its runnables; an app that emits
1324
+ // events declares them in a hand-authored `manifest.json`, same as
1325
+ // `lsp_servers` below.
1326
+ hook_events: [],
1206
1327
  composer_controls: [],
1207
1328
  settings_tabs: [],
1208
1329
  slash_commands: [],
1330
+ sidebar_sections: [],
1331
+ sidebar_buttons: [],
1332
+ dock_panels: [],
1333
+ // Empty for the same reason as every sibling family above: this builder
1334
+ // synthesises `widgets` from the app's own runnables and nothing else, and
1335
+ // takes no `contributes` passthrough. An app that wants to declare language
1336
+ // servers writes them in a hand-authored `manifest.json`.
1337
+ lsp_servers: {},
1338
+ // Same reason again: a danger-zone category and a Pi extension are both
1339
+ // hand-authored declarations, not something derivable from runnables.
1340
+ data_categories: [],
1341
+ pi_extensions: [],
1209
1342
  widgets
1210
1343
  };
1211
1344
  const raw = {
@@ -1213,7 +1346,21 @@ function defineApp(options) {
1213
1346
  name: options.title,
1214
1347
  version: options.version,
1215
1348
  runnables,
1216
- permission_grants: options.grants ?? [],
1349
+ // An app that synthesises widgets MUST hold `widget:render`, so this
1350
+ // builder declares it rather than leaving the author to discover it.
1351
+ //
1352
+ // Core gates widget promotion on declared-AND-enabled-AND-granted, and a
1353
+ // missing grant fails as `DeniedNoGrant` — which is an `info!` log and
1354
+ // nothing else. The widget silently renders as plain text, with no error
1355
+ // in the UI and nothing pointing at the manifest. Every app scaffolded
1356
+ // through `defineApp` hit that, because the only fix was a grant string
1357
+ // the templates never mention and the builder never added; the one
1358
+ // working example on disk hand-writes it.
1359
+ //
1360
+ // Added only when there is a widget to render, and unioned rather than
1361
+ // overwritten so an author's own `grants` list survives and re-declaring
1362
+ // it is not an error.
1363
+ permission_grants: withWidgetRenderGrant(options.grants ?? [], widgets),
1217
1364
  activation_events: options.activationEvents ?? ["*"],
1218
1365
  contributes,
1219
1366
  // `targets: []` means EVERY surface, so an app that declares none is
@@ -1235,7 +1382,9 @@ function defineApp(options) {
1235
1382
  const first = result.error.issues[0];
1236
1383
  const field = first?.path.join(".") ?? "unknown";
1237
1384
  const message = first?.message ?? "validation failed";
1238
- throw new Error(`plugin.json validation failed at '${field}': ${message}`);
1385
+ throw new Error(
1386
+ `manifest.json validation failed at '${field}': ${message}`
1387
+ );
1239
1388
  }
1240
1389
  return result.data;
1241
1390
  }
@@ -1431,7 +1580,7 @@ var PluginBuilder = class {
1431
1580
  const field = first?.path.join(".") ?? "unknown";
1432
1581
  const message = first?.message ?? "validation failed";
1433
1582
  throw new Error(
1434
- `plugin.json validation failed at '${field}': ${message}`
1583
+ `manifest.json validation failed at '${field}': ${message}`
1435
1584
  );
1436
1585
  }
1437
1586
  return result.data;
@@ -1804,12 +1953,21 @@ function defineTurnHook(options) {
1804
1953
  function definePlugin(options) {
1805
1954
  const contributes = {
1806
1955
  turn_hooks: options.turnHooks ?? [],
1956
+ hook_events: options.hookEvents ?? [],
1807
1957
  composer_controls: options.composerControls ?? [],
1808
1958
  settings_tabs: options.settingsTabs ?? [],
1809
1959
  slash_commands: options.slashCommands ?? [],
1810
- // A turn-hook plugin contributes no app widgets; the field is required on the
1811
- // resolved `Contributes` type (zod default applied), so set it explicitly.
1812
- widgets: []
1960
+ lsp_servers: options.lspServers ?? {},
1961
+ // A turn-hook plugin contributes no app widgets, sidebar entries, dock
1962
+ // panels, danger-zone categories or Pi extensions; the fields are required
1963
+ // on the resolved `Contributes` type (zod defaults applied), so set them
1964
+ // explicitly.
1965
+ widgets: [],
1966
+ sidebar_sections: [],
1967
+ sidebar_buttons: [],
1968
+ dock_panels: [],
1969
+ data_categories: [],
1970
+ pi_extensions: []
1813
1971
  };
1814
1972
  const tools = options.tools ?? [];
1815
1973
  const runnables = tools.map((t) => inlineToolRunnable(t));
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { R as Runnable, a as RunnableContext, T as ToolRunnable } from './index-CEbS1SlS.cjs';
2
- export { A as Agent, b as AgentConfig, c as AgentEvent, d as AgentTool, C as ChatDelta, e as ChatMessage, f as ChatResult, D as DurableClient, E as Endpoint, g as EnginesClient, G as GatewayClient, h as GenerateResult, H as HttpPrimitiveTransportOptions, I as ImageClient, J as JsonSchemaProperty, M as MemoryClient, i as MemoryItem, j as ModelClient, k as ModelClientOptions, P as PRIMITIVE_BINDINGS, l as PrimitiveBinding, m as PrimitiveTransport, Q as QueryInput, n as QueryOptions, o as RagChunk, p as RagClient, q as RagRerankResult, r as RealtimeClient, s as RealtimeSubscription, t as RemoteToolRef, u as RyuPrimitives, S as SttClient, v as ToolOptions, w as ToolSchema, x as TtsClient, y as createAgent, z as createPrimitives, B as defineModel, F as defineTool, K as httpPrimitiveTransport, L as inlineToolRunnable, N as query, O as ryuTool } from './index-CEbS1SlS.cjs';
3
- import { Requires, Surface, PluginManifest, RunnableMeta, CompanionSurface, CapabilityReq, TurnHookContribution } from './manifest.cjs';
1
+ import { R as Runnable, a as RunnableContext, T as ToolRunnable } from './index-BvAB5eMk.cjs';
2
+ export { A as Agent, b as AgentConfig, c as AgentEvent, d as AgentTool, C as ChatDelta, e as ChatMessage, f as ChatResult, D as DurableClient, E as Endpoint, g as EnginesClient, G as GatewayClient, h as GenerateResult, H as HttpPrimitiveTransportOptions, I as ImageClient, J as JsonSchemaProperty, M as MemoryClient, i as MemoryItem, j as ModelClient, k as ModelClientOptions, P as PRIMITIVE_BINDINGS, l as PrimitiveBinding, m as PrimitiveTransport, Q as QueryInput, n as QueryOptions, o as RagChunk, p as RagClient, q as RagRerankResult, r as RealtimeClient, s as RealtimeSubscription, t as RemoteToolRef, u as RyuPrimitives, S as SttClient, v as ToolOptions, w as ToolSchema, x as TtsClient, y as createAgent, z as createPrimitives, B as defineModel, F as defineTool, K as httpPrimitiveTransport, L as inlineToolRunnable, N as query, O as ryuTool } from './index-BvAB5eMk.cjs';
3
+ import { Requires, Surface, PluginManifest, RunnableMeta, CompanionSurface, CapabilityReq, HookEventContribution, TurnHookContribution } from './manifest.cjs';
4
4
  export { AppDependency, AppDependencySchema, CapabilityReqSchema, CompanionSurfaceSchema, Contributes, PluginManifestSchema, RequiresSchema, RunnableKind, RunnableKindSchema, RunnableMetaSchema, SurfaceSchema, ToolAppConfig, ToolAppConfigSchema, WidgetContribution, WidgetContributionSchema, coreManifestJsonSchema, validateManifestStrict, validatePluginId } from './manifest.cjs';
5
5
  import 'zod';
6
6
 
@@ -9,7 +9,7 @@ import 'zod';
9
9
  *
10
10
  * A "Ryu App" bundles one or more tools whose results render an interactive
11
11
  * widget inline in chat (the ChatGPT-Apps-style surface). `defineApp` assembles a
12
- * complete `plugin.json` `PluginManifest` from a declarative description, deriving
12
+ * complete `manifest.json` `PluginManifest` from a declarative description, deriving
13
13
  * the render-vs-companion split exactly the way Core's in-process provider does
14
14
  * (`apps/core/src/sidecar/mcp/apps/mod.rs` `tools()`):
15
15
  *
@@ -107,7 +107,7 @@ interface DefineAppOptions {
107
107
  /** Build a fully-qualified tool id from a server namespace and tool name. */
108
108
  declare function appToolId(server: string, name: string): string;
109
109
  /**
110
- * Assemble a `plugin.json` manifest for a Ryu App. The result matches Core's
110
+ * Assemble a `manifest.json` manifest for a Ryu App. The result matches Core's
111
111
  * `PluginManifest` serde shape (validated through `PluginManifestSchema`) and can
112
112
  * be written to disk, packed with `ryu pack`, or published with `ryu publish`.
113
113
  *
@@ -132,7 +132,7 @@ declare function defineApp(options: DefineAppOptions): PluginManifest;
132
132
 
133
133
  /**
134
134
  * Ryu SDK typed builders — one builder per RunnableKind plus a PluginBuilder that
135
- * assembles a complete, validated `plugin.json` manifest.
135
+ * assembles a complete, validated `manifest.json` manifest.
136
136
  *
137
137
  * Each builder follows a fluent interface: construct, chain setter calls, then
138
138
  * call `.build()` to get a validated result. Invalid manifests throw a
@@ -174,7 +174,7 @@ declare const tool: () => ToolBuilder;
174
174
  /** Create a SkillBuilder. */
175
175
  declare const skill: () => SkillBuilder;
176
176
  /**
177
- * Fluent builder for a complete `plugin.json` Plugin manifest. Produces a
177
+ * Fluent builder for a complete `manifest.json` Plugin manifest. Produces a
178
178
  * validated `PluginManifest` on `.build()` or throws a descriptive `Error`
179
179
  * naming the first invalid field.
180
180
  *
@@ -252,7 +252,7 @@ declare class PluginBuilder {
252
252
  build(): PluginManifest;
253
253
  }
254
254
  /**
255
- * Fluent builder for a Ryu App — a `plugin.json` whose tools render interactive
255
+ * Fluent builder for a Ryu App — a `manifest.json` whose tools render interactive
256
256
  * widgets inline in chat. Delegates to {@link defineApp} on `.build()`, so it
257
257
  * derives the render-vs-companion split and validates through
258
258
  * `PluginManifestSchema` (throwing a descriptive `Error` on bad input) exactly
@@ -470,7 +470,7 @@ interface AgentRunnable<TInput = unknown, TOutput = unknown> extends Runnable<TI
470
470
  /** The lowered slot card (empty edges when no slots were declared). */
471
471
  readonly card: AgentCard;
472
472
  /**
473
- * Lower this agent (card + run identity) to a single-agent `plugin.json`
473
+ * Lower this agent (card + run identity) to a single-agent `manifest.json`
474
474
  * `PluginManifest`: the agent `RunnableMeta` carries the persona/model
475
475
  * config; `requires.capabilities` carries the slot edges. Throws if the
476
476
  * assembled manifest is invalid.
@@ -493,7 +493,7 @@ interface AgentManifestOptions {
493
493
  *
494
494
  * The returned value satisfies `Runnable<TInput, TOutput>` with `kind = "agent"`
495
495
  * and additionally exposes the lowered {@link AgentCard} + a `toManifest()`
496
- * lowering, so a slot-composed agent round-trips to a valid `plugin.json`.
496
+ * lowering, so a slot-composed agent round-trips to a valid `manifest.json`.
497
497
  *
498
498
  * @example Classic (unchanged, back-compat):
499
499
  * ```ts
@@ -651,7 +651,9 @@ interface DefineTurnHookOptions {
651
651
  * is serialized into the sandbox `code` string and invoked with `ctx`/`host` at
652
652
  * run time.
653
653
  */
654
- declare function defineTurnHook(options: DefineTurnHookOptions): TurnHookContribution;
654
+ declare function defineTurnHook(options: DefineTurnHookOptions): TurnHookContribution & {
655
+ code: string;
656
+ };
655
657
  interface DefinePluginOptions {
656
658
  /** Activation events (default `["*"]` — driven by the enabled flag). */
657
659
  activationEvents?: string[];
@@ -659,8 +661,24 @@ interface DefinePluginOptions {
659
661
  composerControls?: Record<string, unknown>[];
660
662
  /** Capability grants the hooks need (e.g. `["hook:side-model", "storage:kv"]`). */
661
663
  grants?: string[];
664
+ /**
665
+ * App events this plugin EMITS — the provider half of the hook system whose
666
+ * consumer half is {@link DefinePluginOptions.turnHooks}. Each `id` must be
667
+ * namespaced to this plugin's own `id`; Core validates that at load and again
668
+ * on every emit.
669
+ */
670
+ hookEvents?: HookEventContribution[];
662
671
  /** Reverse-domain id (e.g. `"com.example.my-plugin"`). */
663
672
  id: string;
673
+ /**
674
+ * Language servers the plugin declares, keyed by server name — the same shape
675
+ * as Claude Code's `lspServers` / `.lsp.json`, passed verbatim. Loose records
676
+ * rather than a typed entry on purpose: Claude Code owns this field
677
+ * vocabulary, so typing it here would strip a field from a newer Claude
678
+ * release on its way through `ryu pack`. Core types it, because Core acts on
679
+ * it.
680
+ */
681
+ lspServers?: Record<string, Record<string, unknown>>;
664
682
  /** Display name. */
665
683
  name: string;
666
684
  /**
@@ -690,7 +708,7 @@ interface DefinePluginOptions {
690
708
  version: string;
691
709
  }
692
710
  /**
693
- * Assemble a `plugin.json` manifest for a turn-hook plugin. The result matches
711
+ * Assemble a `manifest.json` manifest for a turn-hook plugin. The result matches
694
712
  * Core's `PluginManifest` serde shape and can be written to disk or validated via
695
713
  * `validateManifestStrict`.
696
714
  */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { R as Runnable, a as RunnableContext, T as ToolRunnable } from './index-DAxq7Y0R.js';
2
- export { A as Agent, b as AgentConfig, c as AgentEvent, d as AgentTool, C as ChatDelta, e as ChatMessage, f as ChatResult, D as DurableClient, E as Endpoint, g as EnginesClient, G as GatewayClient, h as GenerateResult, H as HttpPrimitiveTransportOptions, I as ImageClient, J as JsonSchemaProperty, M as MemoryClient, i as MemoryItem, j as ModelClient, k as ModelClientOptions, P as PRIMITIVE_BINDINGS, l as PrimitiveBinding, m as PrimitiveTransport, Q as QueryInput, n as QueryOptions, o as RagChunk, p as RagClient, q as RagRerankResult, r as RealtimeClient, s as RealtimeSubscription, t as RemoteToolRef, u as RyuPrimitives, S as SttClient, v as ToolOptions, w as ToolSchema, x as TtsClient, y as createAgent, z as createPrimitives, B as defineModel, F as defineTool, K as httpPrimitiveTransport, L as inlineToolRunnable, N as query, O as ryuTool } from './index-DAxq7Y0R.js';
3
- import { Requires, Surface, PluginManifest, RunnableMeta, CompanionSurface, CapabilityReq, TurnHookContribution } from './manifest.js';
1
+ import { R as Runnable, a as RunnableContext, T as ToolRunnable } from './index-B6SkaAjJ.js';
2
+ export { A as Agent, b as AgentConfig, c as AgentEvent, d as AgentTool, C as ChatDelta, e as ChatMessage, f as ChatResult, D as DurableClient, E as Endpoint, g as EnginesClient, G as GatewayClient, h as GenerateResult, H as HttpPrimitiveTransportOptions, I as ImageClient, J as JsonSchemaProperty, M as MemoryClient, i as MemoryItem, j as ModelClient, k as ModelClientOptions, P as PRIMITIVE_BINDINGS, l as PrimitiveBinding, m as PrimitiveTransport, Q as QueryInput, n as QueryOptions, o as RagChunk, p as RagClient, q as RagRerankResult, r as RealtimeClient, s as RealtimeSubscription, t as RemoteToolRef, u as RyuPrimitives, S as SttClient, v as ToolOptions, w as ToolSchema, x as TtsClient, y as createAgent, z as createPrimitives, B as defineModel, F as defineTool, K as httpPrimitiveTransport, L as inlineToolRunnable, N as query, O as ryuTool } from './index-B6SkaAjJ.js';
3
+ import { Requires, Surface, PluginManifest, RunnableMeta, CompanionSurface, CapabilityReq, HookEventContribution, TurnHookContribution } from './manifest.js';
4
4
  export { AppDependency, AppDependencySchema, CapabilityReqSchema, CompanionSurfaceSchema, Contributes, PluginManifestSchema, RequiresSchema, RunnableKind, RunnableKindSchema, RunnableMetaSchema, SurfaceSchema, ToolAppConfig, ToolAppConfigSchema, WidgetContribution, WidgetContributionSchema, coreManifestJsonSchema, validateManifestStrict, validatePluginId } from './manifest.js';
5
5
  import 'zod';
6
6
 
@@ -9,7 +9,7 @@ import 'zod';
9
9
  *
10
10
  * A "Ryu App" bundles one or more tools whose results render an interactive
11
11
  * widget inline in chat (the ChatGPT-Apps-style surface). `defineApp` assembles a
12
- * complete `plugin.json` `PluginManifest` from a declarative description, deriving
12
+ * complete `manifest.json` `PluginManifest` from a declarative description, deriving
13
13
  * the render-vs-companion split exactly the way Core's in-process provider does
14
14
  * (`apps/core/src/sidecar/mcp/apps/mod.rs` `tools()`):
15
15
  *
@@ -107,7 +107,7 @@ interface DefineAppOptions {
107
107
  /** Build a fully-qualified tool id from a server namespace and tool name. */
108
108
  declare function appToolId(server: string, name: string): string;
109
109
  /**
110
- * Assemble a `plugin.json` manifest for a Ryu App. The result matches Core's
110
+ * Assemble a `manifest.json` manifest for a Ryu App. The result matches Core's
111
111
  * `PluginManifest` serde shape (validated through `PluginManifestSchema`) and can
112
112
  * be written to disk, packed with `ryu pack`, or published with `ryu publish`.
113
113
  *
@@ -132,7 +132,7 @@ declare function defineApp(options: DefineAppOptions): PluginManifest;
132
132
 
133
133
  /**
134
134
  * Ryu SDK typed builders — one builder per RunnableKind plus a PluginBuilder that
135
- * assembles a complete, validated `plugin.json` manifest.
135
+ * assembles a complete, validated `manifest.json` manifest.
136
136
  *
137
137
  * Each builder follows a fluent interface: construct, chain setter calls, then
138
138
  * call `.build()` to get a validated result. Invalid manifests throw a
@@ -174,7 +174,7 @@ declare const tool: () => ToolBuilder;
174
174
  /** Create a SkillBuilder. */
175
175
  declare const skill: () => SkillBuilder;
176
176
  /**
177
- * Fluent builder for a complete `plugin.json` Plugin manifest. Produces a
177
+ * Fluent builder for a complete `manifest.json` Plugin manifest. Produces a
178
178
  * validated `PluginManifest` on `.build()` or throws a descriptive `Error`
179
179
  * naming the first invalid field.
180
180
  *
@@ -252,7 +252,7 @@ declare class PluginBuilder {
252
252
  build(): PluginManifest;
253
253
  }
254
254
  /**
255
- * Fluent builder for a Ryu App — a `plugin.json` whose tools render interactive
255
+ * Fluent builder for a Ryu App — a `manifest.json` whose tools render interactive
256
256
  * widgets inline in chat. Delegates to {@link defineApp} on `.build()`, so it
257
257
  * derives the render-vs-companion split and validates through
258
258
  * `PluginManifestSchema` (throwing a descriptive `Error` on bad input) exactly
@@ -470,7 +470,7 @@ interface AgentRunnable<TInput = unknown, TOutput = unknown> extends Runnable<TI
470
470
  /** The lowered slot card (empty edges when no slots were declared). */
471
471
  readonly card: AgentCard;
472
472
  /**
473
- * Lower this agent (card + run identity) to a single-agent `plugin.json`
473
+ * Lower this agent (card + run identity) to a single-agent `manifest.json`
474
474
  * `PluginManifest`: the agent `RunnableMeta` carries the persona/model
475
475
  * config; `requires.capabilities` carries the slot edges. Throws if the
476
476
  * assembled manifest is invalid.
@@ -493,7 +493,7 @@ interface AgentManifestOptions {
493
493
  *
494
494
  * The returned value satisfies `Runnable<TInput, TOutput>` with `kind = "agent"`
495
495
  * and additionally exposes the lowered {@link AgentCard} + a `toManifest()`
496
- * lowering, so a slot-composed agent round-trips to a valid `plugin.json`.
496
+ * lowering, so a slot-composed agent round-trips to a valid `manifest.json`.
497
497
  *
498
498
  * @example Classic (unchanged, back-compat):
499
499
  * ```ts
@@ -651,7 +651,9 @@ interface DefineTurnHookOptions {
651
651
  * is serialized into the sandbox `code` string and invoked with `ctx`/`host` at
652
652
  * run time.
653
653
  */
654
- declare function defineTurnHook(options: DefineTurnHookOptions): TurnHookContribution;
654
+ declare function defineTurnHook(options: DefineTurnHookOptions): TurnHookContribution & {
655
+ code: string;
656
+ };
655
657
  interface DefinePluginOptions {
656
658
  /** Activation events (default `["*"]` — driven by the enabled flag). */
657
659
  activationEvents?: string[];
@@ -659,8 +661,24 @@ interface DefinePluginOptions {
659
661
  composerControls?: Record<string, unknown>[];
660
662
  /** Capability grants the hooks need (e.g. `["hook:side-model", "storage:kv"]`). */
661
663
  grants?: string[];
664
+ /**
665
+ * App events this plugin EMITS — the provider half of the hook system whose
666
+ * consumer half is {@link DefinePluginOptions.turnHooks}. Each `id` must be
667
+ * namespaced to this plugin's own `id`; Core validates that at load and again
668
+ * on every emit.
669
+ */
670
+ hookEvents?: HookEventContribution[];
662
671
  /** Reverse-domain id (e.g. `"com.example.my-plugin"`). */
663
672
  id: string;
673
+ /**
674
+ * Language servers the plugin declares, keyed by server name — the same shape
675
+ * as Claude Code's `lspServers` / `.lsp.json`, passed verbatim. Loose records
676
+ * rather than a typed entry on purpose: Claude Code owns this field
677
+ * vocabulary, so typing it here would strip a field from a newer Claude
678
+ * release on its way through `ryu pack`. Core types it, because Core acts on
679
+ * it.
680
+ */
681
+ lspServers?: Record<string, Record<string, unknown>>;
664
682
  /** Display name. */
665
683
  name: string;
666
684
  /**
@@ -690,7 +708,7 @@ interface DefinePluginOptions {
690
708
  version: string;
691
709
  }
692
710
  /**
693
- * Assemble a `plugin.json` manifest for a turn-hook plugin. The result matches
711
+ * Assemble a `manifest.json` manifest for a turn-hook plugin. The result matches
694
712
  * Core's `PluginManifest` serde shape and can be written to disk or validated via
695
713
  * `validateManifestStrict`.
696
714
  */
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  coreManifestJsonSchema,
13
13
  validateManifestStrict,
14
14
  validatePluginId
15
- } from "./chunk-GXHL5CO7.js";
15
+ } from "./chunk-XTUK5I6I.js";
16
16
  import {
17
17
  Agent,
18
18
  PRIMITIVE_BINDINGS,
@@ -21,7 +21,7 @@ import {
21
21
  httpPrimitiveTransport,
22
22
  query,
23
23
  ryuTool
24
- } from "./chunk-KPKMMGVC.js";
24
+ } from "./chunk-MTUBUPIV.js";
25
25
  import {
26
26
  DEFAULT_GATEWAY_URL,
27
27
  ModelClient,
@@ -34,6 +34,14 @@ import {
34
34
  // src/runnable/app.ts
35
35
  var DEFAULT_APP_WIDGET_MIME = "text/html+skybridge";
36
36
  var DEFAULT_APP_DISPLAY_MODE = "inline";
37
+ var WIDGET_RENDER_GRANT = "widget:render";
38
+ function withWidgetRenderGrant(grants, widgets) {
39
+ const out = [...grants];
40
+ if (widgets.length > 0 && !out.includes(WIDGET_RENDER_GRANT)) {
41
+ out.push(WIDGET_RENDER_GRANT);
42
+ }
43
+ return out;
44
+ }
37
45
  function appToolId(server, name) {
38
46
  return `${server}__${name}`;
39
47
  }
@@ -75,9 +83,25 @@ function defineApp(options) {
75
83
  }
76
84
  const contributes = {
77
85
  turn_hooks: [],
86
+ // This builder synthesises an app from its runnables; an app that emits
87
+ // events declares them in a hand-authored `manifest.json`, same as
88
+ // `lsp_servers` below.
89
+ hook_events: [],
78
90
  composer_controls: [],
79
91
  settings_tabs: [],
80
92
  slash_commands: [],
93
+ sidebar_sections: [],
94
+ sidebar_buttons: [],
95
+ dock_panels: [],
96
+ // Empty for the same reason as every sibling family above: this builder
97
+ // synthesises `widgets` from the app's own runnables and nothing else, and
98
+ // takes no `contributes` passthrough. An app that wants to declare language
99
+ // servers writes them in a hand-authored `manifest.json`.
100
+ lsp_servers: {},
101
+ // Same reason again: a danger-zone category and a Pi extension are both
102
+ // hand-authored declarations, not something derivable from runnables.
103
+ data_categories: [],
104
+ pi_extensions: [],
81
105
  widgets
82
106
  };
83
107
  const raw = {
@@ -85,7 +109,21 @@ function defineApp(options) {
85
109
  name: options.title,
86
110
  version: options.version,
87
111
  runnables,
88
- permission_grants: options.grants ?? [],
112
+ // An app that synthesises widgets MUST hold `widget:render`, so this
113
+ // builder declares it rather than leaving the author to discover it.
114
+ //
115
+ // Core gates widget promotion on declared-AND-enabled-AND-granted, and a
116
+ // missing grant fails as `DeniedNoGrant` — which is an `info!` log and
117
+ // nothing else. The widget silently renders as plain text, with no error
118
+ // in the UI and nothing pointing at the manifest. Every app scaffolded
119
+ // through `defineApp` hit that, because the only fix was a grant string
120
+ // the templates never mention and the builder never added; the one
121
+ // working example on disk hand-writes it.
122
+ //
123
+ // Added only when there is a widget to render, and unioned rather than
124
+ // overwritten so an author's own `grants` list survives and re-declaring
125
+ // it is not an error.
126
+ permission_grants: withWidgetRenderGrant(options.grants ?? [], widgets),
89
127
  activation_events: options.activationEvents ?? ["*"],
90
128
  contributes,
91
129
  // `targets: []` means EVERY surface, so an app that declares none is
@@ -107,7 +145,9 @@ function defineApp(options) {
107
145
  const first = result.error.issues[0];
108
146
  const field = first?.path.join(".") ?? "unknown";
109
147
  const message = first?.message ?? "validation failed";
110
- throw new Error(`plugin.json validation failed at '${field}': ${message}`);
148
+ throw new Error(
149
+ `manifest.json validation failed at '${field}': ${message}`
150
+ );
111
151
  }
112
152
  return result.data;
113
153
  }
@@ -303,7 +343,7 @@ var PluginBuilder = class {
303
343
  const field = first?.path.join(".") ?? "unknown";
304
344
  const message = first?.message ?? "validation failed";
305
345
  throw new Error(
306
- `plugin.json validation failed at '${field}': ${message}`
346
+ `manifest.json validation failed at '${field}': ${message}`
307
347
  );
308
348
  }
309
349
  return result.data;
@@ -676,12 +716,21 @@ function defineTurnHook(options) {
676
716
  function definePlugin(options) {
677
717
  const contributes = {
678
718
  turn_hooks: options.turnHooks ?? [],
719
+ hook_events: options.hookEvents ?? [],
679
720
  composer_controls: options.composerControls ?? [],
680
721
  settings_tabs: options.settingsTabs ?? [],
681
722
  slash_commands: options.slashCommands ?? [],
682
- // A turn-hook plugin contributes no app widgets; the field is required on the
683
- // resolved `Contributes` type (zod default applied), so set it explicitly.
684
- widgets: []
723
+ lsp_servers: options.lspServers ?? {},
724
+ // A turn-hook plugin contributes no app widgets, sidebar entries, dock
725
+ // panels, danger-zone categories or Pi extensions; the fields are required
726
+ // on the resolved `Contributes` type (zod defaults applied), so set them
727
+ // explicitly.
728
+ widgets: [],
729
+ sidebar_sections: [],
730
+ sidebar_buttons: [],
731
+ dock_panels: [],
732
+ data_categories: [],
733
+ pi_extensions: []
685
734
  };
686
735
  const tools = options.tools ?? [];
687
736
  const runnables = tools.map((t) => inlineToolRunnable(t));