@ryuhq/sdk 0.1.14 → 0.2.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.
Files changed (105) hide show
  1. package/README.md +69 -7
  2. package/dist/action.cjs +839 -0
  3. package/dist/action.d.cts +88 -0
  4. package/dist/action.d.ts +88 -0
  5. package/dist/action.js +8 -0
  6. package/dist/agent-plugin.cjs +46 -0
  7. package/dist/agent-plugin.d.cts +43 -33
  8. package/dist/agent-plugin.d.ts +43 -33
  9. package/dist/agent-plugin.js +1 -1
  10. package/dist/agent.cjs +16 -0
  11. package/dist/agent.d.cts +377 -1
  12. package/dist/agent.d.ts +377 -1
  13. package/dist/agent.js +4 -2
  14. package/dist/app-B0Z9Ew_R.d.cts +141 -0
  15. package/dist/app-C-BDJwfG.d.ts +141 -0
  16. package/dist/builder.cjs +1194 -0
  17. package/dist/builder.d.cts +217 -0
  18. package/dist/builder.d.ts +217 -0
  19. package/dist/builder.js +29 -0
  20. package/dist/chunk-4TPUZDTI.js +94 -0
  21. package/dist/chunk-BC3A7HMO.js +164 -0
  22. package/dist/chunk-FZSFZOIN.js +200 -0
  23. package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
  24. package/dist/chunk-HLKJZAFK.js +361 -0
  25. package/dist/chunk-IKEDLLFY.js +19 -0
  26. package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
  27. package/dist/{chunk-AO2KJRDD.js → chunk-NZKVOSC2.js} +192 -3
  28. package/dist/chunk-QYFUNJOH.js +83 -0
  29. package/dist/chunk-SN2QBJUF.js +148 -0
  30. package/dist/chunk-TLDPEGC7.js +21 -0
  31. package/dist/chunk-TXSHHZF2.js +0 -0
  32. package/dist/chunk-VLIRNNAE.js +154 -0
  33. package/dist/{chunk-MTUBUPIV.js → chunk-Z57QDDJR.js} +8 -227
  34. package/dist/chunk-ZTJWBRUL.js +282 -0
  35. package/dist/cli.cjs +347 -18
  36. package/dist/cli.js +128 -23
  37. package/dist/client-D5U6ssPc.d.cts +84 -0
  38. package/dist/client-D5U6ssPc.d.ts +84 -0
  39. package/dist/index.cjs +810 -86
  40. package/dist/index.d.cts +19 -634
  41. package/dist/index.d.ts +19 -634
  42. package/dist/index.js +82 -699
  43. package/dist/manifest.cjs +200 -4
  44. package/dist/manifest.d.cts +260 -5
  45. package/dist/manifest.d.ts +260 -5
  46. package/dist/manifest.js +15 -1
  47. package/dist/mcp/client.cjs +180 -0
  48. package/dist/mcp/client.d.cts +49 -0
  49. package/dist/mcp/client.d.ts +49 -0
  50. package/dist/mcp/client.js +10 -0
  51. package/dist/mcp/server.cjs +370 -0
  52. package/dist/mcp/server.d.cts +127 -0
  53. package/dist/mcp/server.d.ts +127 -0
  54. package/dist/mcp/server.js +9 -0
  55. package/dist/mcp.cjs +376 -0
  56. package/dist/mcp.d.cts +2 -0
  57. package/dist/mcp.d.ts +2 -0
  58. package/dist/mcp.js +17 -0
  59. package/dist/model.cjs +141 -0
  60. package/dist/model.d.cts +33 -0
  61. package/dist/model.d.ts +33 -0
  62. package/dist/model.js +18 -0
  63. package/dist/plugin.cjs +46 -0
  64. package/dist/plugin.d.cts +215 -0
  65. package/dist/plugin.d.ts +215 -0
  66. package/dist/plugin.js +8 -0
  67. package/dist/runnable.cjs +1438 -0
  68. package/dist/runnable.d.cts +274 -0
  69. package/dist/runnable.d.ts +274 -0
  70. package/dist/runnable.js +34 -0
  71. package/dist/{index-B6SkaAjJ.d.ts → tool-AjkdFvhE.d.ts} +88 -461
  72. package/dist/{index-BvAB5eMk.d.cts → tool-CgzW92O_.d.cts} +88 -461
  73. package/package.json +48 -3
  74. package/src/agent/loop.test.ts +4 -4
  75. package/src/agent/tools.ts +3 -3
  76. package/src/agent-plugin.test.ts +58 -0
  77. package/src/agent-plugin.ts +109 -36
  78. package/src/builder.ts +9 -0
  79. package/src/cli/dev.test.ts +26 -47
  80. package/src/cli/dev.ts +10 -2
  81. package/src/cli-security.test.ts +109 -0
  82. package/src/cli.ts +141 -25
  83. package/src/contracts-lockstep.test.ts +16 -2
  84. package/src/exports-lockstep.test.ts +93 -0
  85. package/src/generated/plugin-manifest.ts +322 -29
  86. package/src/index.ts +53 -0
  87. package/src/manifest-schema.test.ts +30 -1
  88. package/src/manifest.fixtures.test.ts +22 -6
  89. package/src/manifest.test.ts +165 -10
  90. package/src/manifest.ts +454 -188
  91. package/src/mcp/index.ts +18 -0
  92. package/src/mcp/server.ts +2 -1
  93. package/src/model/index.ts +22 -0
  94. package/src/plugin/ryu-plugin.ts +82 -0
  95. package/src/runnable/action.test.ts +128 -0
  96. package/src/runnable/action.ts +202 -0
  97. package/src/runnable/app.test.ts +2 -0
  98. package/src/runnable/app.ts +50 -14
  99. package/src/runnable/index.ts +20 -3
  100. package/src/runnable/primitives.test.ts +34 -0
  101. package/src/runnable/primitives.ts +116 -0
  102. package/src/runnable/runnable-types.ts +3 -0
  103. package/src/runnable/tool.ts +36 -5
  104. package/src/runnable/turn-hook.ts +8 -3
  105. package/src/slash-command.test.ts +69 -0
@@ -146,8 +146,38 @@ var WidgetContributionSchema = z.object({
146
146
  /** Default display mode (`inline` | `fullscreen` | `pip`). */
147
147
  default_display_mode: z.string().default(DEFAULT_WIDGET_DISPLAY_MODE)
148
148
  });
149
+ var ChatWidgetTemplateSchema = z.object({
150
+ id: z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
151
+ title: z.string().min(1),
152
+ description: z.string().optional(),
153
+ triggers: z.array(z.string()).default([]),
154
+ examples: z.array(z.string()).default([]),
155
+ backing: z.object({
156
+ tool_id: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
157
+ view_id: z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional()
158
+ }),
159
+ display_mode: z.string().min(1),
160
+ safe_action_ids: z.array(z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
161
+ availability: z.string().default("available")
162
+ }).superRefine((value, ctx) => {
163
+ const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
164
+ if (count !== 1 && value.availability === "available") {
165
+ ctx.addIssue({
166
+ code: "custom",
167
+ path: ["backing"],
168
+ message: "available templates need exactly one backing tool_id or view_id"
169
+ });
170
+ }
171
+ if (count > 1) {
172
+ ctx.addIssue({
173
+ code: "custom",
174
+ path: ["backing"],
175
+ message: "backing must declare at most one of tool_id or view_id"
176
+ });
177
+ }
178
+ });
149
179
  var ToolAppConfigSchema = z.object({
150
- /** MCP tool slug this runnable wraps — the fully-qualified `<server>__<name>` id. */
180
+ /** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
151
181
  slug: z.string().min(1),
152
182
  /** The tool description the model reads when choosing it. Carried here because a
153
183
  * packed app's manifest is the only channel (there is no `generated.rs`); Core's
@@ -166,6 +196,33 @@ var ToolAppConfigSchema = z.object({
166
196
  /** Optional status label shown when the render tool finishes. */
167
197
  invoked: z.string().optional()
168
198
  });
199
+ var SlashCommandOptionSchema = z.object({
200
+ description: z.string().optional(),
201
+ label: z.string().min(1),
202
+ value: z.string().min(1)
203
+ }).passthrough();
204
+ var SlashCommandCustomOptionSchema = z.union([
205
+ z.literal(true),
206
+ z.object({
207
+ description: z.string().optional(),
208
+ label: z.string().min(1).optional()
209
+ }).passthrough()
210
+ ]);
211
+ var SlashCommandArgumentSchema = z.object({
212
+ allow_custom: z.boolean().optional(),
213
+ custom: SlashCommandCustomOptionSchema.optional(),
214
+ description: z.string().optional(),
215
+ name: z.string().min(1),
216
+ options: z.array(SlashCommandOptionSchema).optional()
217
+ }).passthrough();
218
+ var SlashCommandContributionSchema = z.object({
219
+ args: z.array(SlashCommandArgumentSchema).optional(),
220
+ body: z.string().optional(),
221
+ command: z.string().min(1),
222
+ description: z.string().optional(),
223
+ id: z.string().optional(),
224
+ parameters: z.array(SlashCommandArgumentSchema).optional()
225
+ }).passthrough();
169
226
  var ContributesSchema = z.object({
170
227
  turn_hooks: z.array(TurnHookContributionSchema).default([]),
171
228
  /** App events this plugin EMITS — the provider half of the hook system, whose
@@ -174,13 +231,20 @@ var ContributesSchema = z.object({
174
231
  * signing, leaving an app that emits events nothing is allowed to subscribe to. */
175
232
  hook_events: z.array(HookEventContributionSchema).default([]),
176
233
  composer_controls: z.array(z.record(z.string(), z.unknown())).default([]),
234
+ /** Chat feature descriptors whose behavior is implemented by the host shell.
235
+ * Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
236
+ * authoring schema prevents `ryu pack` from silently deleting a plugin's chat
237
+ * feature declaration before signing. */
238
+ chat_features: z.array(z.record(z.string(), z.unknown())).default([]),
177
239
  settings_tabs: z.array(z.record(z.string(), z.unknown())).default([]),
178
- slash_commands: z.array(z.record(z.string(), z.unknown())).default([]),
240
+ slash_commands: z.array(SlashCommandContributionSchema).default([]),
179
241
  /** App widgets (Ryu Apps). Each binds a render tool id to its
180
242
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
181
243
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
182
244
  * every widget an app authored here declares. */
183
245
  widgets: z.array(WidgetContributionSchema).default([]),
246
+ /** Metadata-only chat widget templates. */
247
+ chat_widget_templates: z.array(ChatWidgetTemplateSchema).optional(),
184
248
  /** App-registered sidebar sections (header + live list) and buttons (single nav
185
249
  * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
186
250
  * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
@@ -193,6 +257,13 @@ var ContributesSchema = z.object({
193
257
  * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
194
258
  * strip the dock panel an app declares here. */
195
259
  dock_panels: z.array(z.record(z.string(), z.unknown())).default([]),
260
+ /** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
261
+ * typed for the same reason as the surfaces above — the shell owns the
262
+ * `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
263
+ * without it the CLI's zod parse would strip every live activity an app
264
+ * declares here, so a packed bundle would ship a dock that silently stays
265
+ * empty. */
266
+ live_activities: z.array(z.record(z.string(), z.unknown())).default([]),
196
267
  /** Deletable data categories the app owns — one "Delete all X" row in Settings
197
268
  * → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
198
269
  * it the CLI's zod parse would strip the declaration before signing, and the
@@ -230,7 +301,13 @@ var ContributesSchema = z.object({
230
301
  * bug: the styles' `.md` files are not carried by the bundle either, so there
231
302
  * would be no residue to notice — the plugin would install clean and contribute
232
303
  * nothing. */
233
- output_styles: z.array(OutputStyleContributionSchema).default([])
304
+ output_styles: z.array(OutputStyleContributionSchema).default([]),
305
+ /** Per-message actions contributed by an enabled plugin. Kept as loose records
306
+ * so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
307
+ message_actions: z.array(z.record(z.string(), z.unknown())).default([]),
308
+ /** Buttons contributed to the floating text-selection toolbar. Kept as loose
309
+ * records so host-owned dispatch args survive `ryu pack` unchanged. */
310
+ selection_actions: z.array(z.record(z.string(), z.unknown())).default([])
234
311
  });
235
312
  var SetupStepSchema = z.object({
236
313
  /** Card heading (e.g. the companion app name). */
@@ -311,6 +388,61 @@ var EnginesReqSchema = z.object({
311
388
  /** Floor for the Next.js web app. */
312
389
  web: z.string().optional()
313
390
  });
391
+ var McpServerAuthSchema = z.object({
392
+ client_id: z.string().min(1).optional(),
393
+ type: z.literal("oauth")
394
+ }).strict();
395
+ var McpServerDeclSchema = z.object({
396
+ args: z.array(z.string()).default([]),
397
+ auth: McpServerAuthSchema.optional(),
398
+ command: z.string().optional(),
399
+ command_env: z.string().optional(),
400
+ description: z.string().optional(),
401
+ enabled: z.boolean().default(true),
402
+ env: z.record(z.string(), z.string()).default({}),
403
+ headers: z.record(z.string(), z.string()).default({}),
404
+ type: z.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"]).optional(),
405
+ url: z.url().optional()
406
+ }).superRefine((server, context) => {
407
+ if (!(server.command || server.url)) {
408
+ context.addIssue({
409
+ code: "custom",
410
+ message: "an MCP server requires command or url"
411
+ });
412
+ }
413
+ if (!server.auth) {
414
+ return;
415
+ }
416
+ if (server.command || server.type === "stdio" || !server.url) {
417
+ context.addIssue({
418
+ code: "custom",
419
+ message: "OAuth is supported only for remote HTTP MCP servers"
420
+ });
421
+ return;
422
+ }
423
+ if (Object.keys(server.headers).some(
424
+ (name) => name.toLowerCase() === "authorization"
425
+ )) {
426
+ context.addIssue({
427
+ code: "custom",
428
+ message: "OAuth cannot be combined with a static Authorization header"
429
+ });
430
+ }
431
+ const url = new URL(server.url);
432
+ const loopback = url.hostname === "localhost" || url.hostname.startsWith("127.") || url.hostname === "[::1]" || url.hostname === "::1";
433
+ if (url.username || url.password || url.hash) {
434
+ context.addIssue({
435
+ code: "custom",
436
+ message: "OAuth MCP URLs cannot contain credentials or fragments"
437
+ });
438
+ }
439
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
440
+ context.addIssue({
441
+ code: "custom",
442
+ message: "OAuth MCP URLs must use HTTPS except on loopback"
443
+ });
444
+ }
445
+ });
314
446
  var PluginManifestSchema = z.object({
315
447
  /** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
316
448
  id: z.string().min(1, "id is required"),
@@ -325,6 +457,9 @@ var PluginManifestSchema = z.object({
325
457
  /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/,
326
458
  "version must be a valid semver string (e.g. 1.0.0)"
327
459
  ),
460
+ /** Core-owned release maturity metadata. The Rust contract validates the
461
+ * richer shape; the SDK authoring parser must preserve it for pack/publish. */
462
+ stability: z.unknown().optional(),
328
463
  /**
329
464
  * Lower-case hex `sha256(utf8_bytes(ui_code))` binding the plugin's bundled
330
465
  * sandboxed-UI code to this manifest. `ryu pack` / `ryu publish` compute it and
@@ -342,6 +477,26 @@ var PluginManifestSchema = z.object({
342
477
  * Declarations only — grant enforcement is the Gateway's responsibility.
343
478
  */
344
479
  permission_grants: z.array(z.string()).default([]),
480
+ /** Deny-by-default sandbox permissions. Core remains authoritative; this
481
+ * schema mirrors the fields so `ryu pack` cannot strip them. */
482
+ permissions: z.object({
483
+ fs: z.object({
484
+ read: z.array(z.string()).default([]),
485
+ write: z.array(z.string()).default([])
486
+ }).optional(),
487
+ child_process: z.boolean().optional(),
488
+ run: z.array(z.string()).default([]),
489
+ network: z.union([z.boolean(), z.array(z.string())]).optional(),
490
+ tool: z.array(z.string()).default([])
491
+ }).optional(),
492
+ /** Core-owned permission presentation levels. Preserved verbatim here and
493
+ * validated by Core's authoritative manifest contract. */
494
+ permission_levels: z.unknown().optional(),
495
+ /** Remote or stdio MCP servers registered by this plugin. */
496
+ mcp_servers: z.record(z.string(), McpServerDeclSchema).optional(),
497
+ /** Core-owned sidecar declarations. Their full process/HTTP schema stays in
498
+ * the Rust contract; this authoring layer must never strip them. */
499
+ sidecars: z.unknown().optional(),
345
500
  /**
346
501
  * Optional Companion surface (an in-desktop overlay or sidebar panel).
347
502
  * Absent when the plugin has no Companion surface.
@@ -377,6 +532,9 @@ var PluginManifestSchema = z.object({
377
532
  * an unsupported-target plugin stays installable and inspectable.
378
533
  */
379
534
  targets: z.array(SurfaceSchema).default([]),
535
+ /** Rich per-surface declarations (`support`, UI, contributed commands, …).
536
+ * Core owns and validates the nested vocabulary. */
537
+ surfaces: z.unknown().optional(),
380
538
  /**
381
539
  * Host version floors — the semver requirement each surface must satisfy for
382
540
  * this plugin to install. Mirrors Core's `EnginesReq`
@@ -435,10 +593,16 @@ var PluginManifestSchema = z.object({
435
593
  url: z.string().optional()
436
594
  })
437
595
  ]).optional(),
596
+ /** Public source repository URL (Claude/Codex `repository`). */
597
+ repository: z.string().url().optional(),
598
+ /** True when the provider operates outside the local Ryu runtime. */
599
+ external: z.boolean().optional(),
438
600
  /** Project/marketing homepage — maps to the listing `website` (Claude field). */
439
601
  homepage: z.string().optional(),
440
602
  /** Free-text search keywords (Claude field). */
441
603
  keywords: z.array(z.string()).optional(),
604
+ /** Stable Marketplace filter labels (Ryu extension). */
605
+ tags: z.array(z.string()).optional(),
442
606
  /** Taxonomy category beyond the runnable kinds (Claude field). */
443
607
  category: z.string().optional(),
444
608
  /** SPDX-ish license identifier (Claude field). */
@@ -452,6 +616,8 @@ var PluginManifestSchema = z.object({
452
616
  * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
453
617
  */
454
618
  icon: z.string().optional(),
619
+ /** Optional detail-page hero banner metadata forwarded to the marketplace. */
620
+ banner: z.record(z.string(), z.unknown()).optional(),
455
621
  /**
456
622
  * Dithered-gradient background for the card's icon square (Ryu extension),
457
623
  * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
@@ -483,6 +649,22 @@ var PluginManifestSchema = z.object({
483
649
  * array of steps guiding the user through post-install configuration.
484
650
  */
485
651
  setup: z.union([SetupStepSchema, z.array(SetupStepSchema)]).optional()
652
+ }).passthrough().superRefine((manifest, context) => {
653
+ const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
654
+ (server) => server.auth?.type === "oauth"
655
+ );
656
+ if (!hasOAuthServer) {
657
+ return;
658
+ }
659
+ for (const grant of ["mcp:server", "identity.read"]) {
660
+ if (!manifest.permission_grants.includes(grant)) {
661
+ context.addIssue({
662
+ code: "custom",
663
+ message: `OAuth MCP servers require the ${grant} permission grant`,
664
+ path: ["permission_grants"]
665
+ });
666
+ }
667
+ }
486
668
  });
487
669
  function validatePluginId(id) {
488
670
  loadNative().validatePluginId(id);
@@ -504,7 +686,12 @@ export {
504
686
  PiExtensionContributionSchema,
505
687
  OutputStyleContributionSchema,
506
688
  WidgetContributionSchema,
689
+ ChatWidgetTemplateSchema,
507
690
  ToolAppConfigSchema,
691
+ SlashCommandOptionSchema,
692
+ SlashCommandCustomOptionSchema,
693
+ SlashCommandArgumentSchema,
694
+ SlashCommandContributionSchema,
508
695
  ContributesSchema,
509
696
  SetupStepSchema,
510
697
  AppDependencySchema,
@@ -512,6 +699,8 @@ export {
512
699
  RequiresSchema,
513
700
  SurfaceSchema,
514
701
  EnginesReqSchema,
702
+ McpServerAuthSchema,
703
+ McpServerDeclSchema,
515
704
  PluginManifestSchema,
516
705
  validatePluginId,
517
706
  validateManifestStrict,
@@ -0,0 +1,83 @@
1
+ // src/runnable/tool.ts
2
+ function validateInput(input, schema, toolId) {
3
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
4
+ throw new Error(
5
+ `[ryu-sdk] Tool "${toolId}" input must be an object, got ${JSON.stringify(input)}`
6
+ );
7
+ }
8
+ const record = input;
9
+ for (const requiredKey of schema.required ?? []) {
10
+ if (!(requiredKey in record)) {
11
+ throw new Error(
12
+ `[ryu-sdk] Tool "${toolId}" input missing required field "${requiredKey}"`
13
+ );
14
+ }
15
+ }
16
+ for (const [key, prop] of Object.entries(schema.properties)) {
17
+ if (!(key in record)) {
18
+ continue;
19
+ }
20
+ const value = record[key];
21
+ if (!checkType(value, prop.type)) {
22
+ throw new Error(
23
+ `[ryu-sdk] Tool "${toolId}" input field "${key}" expected type "${prop.type}", got ${JSON.stringify(value)}`
24
+ );
25
+ }
26
+ }
27
+ }
28
+ function checkType(value, type) {
29
+ switch (type) {
30
+ case "string":
31
+ return typeof value === "string";
32
+ case "number":
33
+ case "integer":
34
+ return typeof value === "number";
35
+ case "boolean":
36
+ return typeof value === "boolean";
37
+ case "array":
38
+ return Array.isArray(value);
39
+ case "object":
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ default:
42
+ return false;
43
+ }
44
+ }
45
+ function defineTool(options) {
46
+ const { description, id, name, schema, run } = options;
47
+ const code = `return await (${run.toString()})(input, host);`;
48
+ return {
49
+ id,
50
+ name,
51
+ kind: "tool",
52
+ schema,
53
+ ...description === void 0 ? {} : { description },
54
+ code,
55
+ run(input, ctx) {
56
+ validateInput(input, schema, id);
57
+ return run(input, ctx);
58
+ }
59
+ };
60
+ }
61
+ function inlineToolRunnable(tool, options) {
62
+ return {
63
+ id: tool.id,
64
+ name: tool.name,
65
+ kind: "tool",
66
+ config: {
67
+ slug: tool.id,
68
+ backend: "inline_deno",
69
+ code: tool.code,
70
+ input_schema: tool.schema,
71
+ ...options?.description === void 0 ? tool.description === void 0 ? {} : { description: tool.description } : { description: options.description },
72
+ ...options?.outputSchema ? { output_schema: options.outputSchema } : {},
73
+ ...options?.annotations ? { annotations: options.annotations } : {},
74
+ ...options?.action === void 0 ? {} : { action: options.action },
75
+ ...options?.needsApproval === void 0 ? {} : { needs_approval: options.needsApproval }
76
+ }
77
+ };
78
+ }
79
+
80
+ export {
81
+ defineTool,
82
+ inlineToolRunnable
83
+ };
@@ -0,0 +1,148 @@
1
+ import {
2
+ inlineToolRunnable
3
+ } from "./chunk-QYFUNJOH.js";
4
+ import {
5
+ PluginManifestSchema
6
+ } from "./chunk-NZKVOSC2.js";
7
+
8
+ // src/runnable/app.ts
9
+ var DEFAULT_APP_WIDGET_MIME = "text/html+skybridge";
10
+ var DEFAULT_APP_DISPLAY_MODE = "inline";
11
+ var WIDGET_RENDER_GRANT = "widget:render";
12
+ function withWidgetRenderGrant(grants, widgets) {
13
+ const out = [...grants];
14
+ if (widgets.length > 0 && !out.includes(WIDGET_RENDER_GRANT)) {
15
+ out.push(WIDGET_RENDER_GRANT);
16
+ }
17
+ return out;
18
+ }
19
+ function withToolExecuteGrant(grants, tools) {
20
+ const out = [...grants];
21
+ if (tools.some((tool) => tool.runnable !== void 0) && !out.includes("tool:execute")) {
22
+ out.push("tool:execute");
23
+ }
24
+ return out;
25
+ }
26
+ function appToolId(server, name) {
27
+ return `${server}.${name}`;
28
+ }
29
+ function defineApp(options) {
30
+ const server = options.server ?? options.slug;
31
+ const uri = `ui://widget/${options.slug}.html`;
32
+ const mime = options.mime ?? DEFAULT_APP_WIDGET_MIME;
33
+ const displayMode = options.displayMode ?? DEFAULT_APP_DISPLAY_MODE;
34
+ const hasCompanions = options.tools.some((t) => t.accessible === true);
35
+ const runnables = [];
36
+ const widgets = [];
37
+ for (const spec of options.tools) {
38
+ const isRender = spec.accessible !== true;
39
+ const id = appToolId(server, spec.name);
40
+ const executable = spec.runnable ? inlineToolRunnable(spec.runnable) : void 0;
41
+ const config = {
42
+ ...executable?.config ?? {},
43
+ slug: id,
44
+ description: spec.description,
45
+ widget: isRender,
46
+ widget_accessible: isRender ? hasCompanions : true,
47
+ ...spec.inputSchema ? { input_schema: spec.inputSchema } : {},
48
+ ...spec.invoking ? { invoking: spec.invoking } : {},
49
+ ...spec.invoked ? { invoked: spec.invoked } : {}
50
+ };
51
+ runnables.push({
52
+ id,
53
+ name: spec.name,
54
+ kind: "tool",
55
+ config
56
+ });
57
+ if (isRender) {
58
+ widgets.push({
59
+ tool_id: id,
60
+ uri,
61
+ ui_entry: options.uiEntry,
62
+ mime,
63
+ default_display_mode: displayMode
64
+ });
65
+ }
66
+ }
67
+ const contributes = {
68
+ turn_hooks: [],
69
+ chat_features: [],
70
+ // This builder synthesises an app from its runnables; an app that emits
71
+ // events declares them in a hand-authored `manifest.json`, same as
72
+ // `lsp_servers` below.
73
+ hook_events: [],
74
+ composer_controls: [],
75
+ settings_tabs: [],
76
+ slash_commands: options.slashCommands ?? [],
77
+ sidebar_sections: [],
78
+ sidebar_buttons: [],
79
+ dock_panels: [],
80
+ live_activities: [],
81
+ // Empty for the same reason as every sibling family above: this builder
82
+ // synthesises `widgets` from the app's own runnables and nothing else, and
83
+ // takes no `contributes` passthrough. An app that wants to declare language
84
+ // servers writes them in a hand-authored `manifest.json`.
85
+ lsp_servers: {},
86
+ // Same reason again: a danger-zone category, a Pi extension and an output
87
+ // style are all hand-authored declarations, not something derivable from
88
+ // runnables. An output style in particular points at a Markdown file next
89
+ // to the manifest, which this builder never writes.
90
+ data_categories: [],
91
+ pi_extensions: [],
92
+ output_styles: [],
93
+ message_actions: [],
94
+ selection_actions: [],
95
+ widgets
96
+ };
97
+ const raw = {
98
+ id: options.id,
99
+ name: options.title,
100
+ version: options.version,
101
+ runnables,
102
+ // An app that synthesises widgets MUST hold `widget:render`, so this
103
+ // builder declares it rather than leaving the author to discover it.
104
+ //
105
+ // Core gates widget promotion on declared-AND-enabled-AND-granted, and a
106
+ // missing grant fails as `DeniedNoGrant` — which is an `info!` log and
107
+ // nothing else. The widget silently renders as plain text, with no error
108
+ // in the UI and nothing pointing at the manifest.
109
+ //
110
+ // Added only when there is a widget to render, and unioned rather than
111
+ // overwritten so an author's own `grants` list survives and re-declaring
112
+ // it is not an error.
113
+ permission_grants: withToolExecuteGrant(
114
+ withWidgetRenderGrant(options.grants ?? [], widgets),
115
+ options.tools
116
+ ),
117
+ activation_events: options.activationEvents ?? ["*"],
118
+ contributes,
119
+ // `targets: []` means EVERY surface, so an app that declares none is
120
+ // unrestricted — the backward-compatible default.
121
+ targets: options.targets ?? [],
122
+ // `requires` stays ABSENT (not `{apps:[],grants:[]}`) when undeclared, so the
123
+ // emitted manifest carries no key at all — matching Core's
124
+ // `Option<Requires>` + `skip_serializing_if = "Option::is_none"`.
125
+ ...options.requires ? {
126
+ requires: {
127
+ apps: options.requires.apps ?? [],
128
+ capabilities: options.requires.capabilities ?? [],
129
+ grants: options.requires.grants ?? []
130
+ }
131
+ } : {}
132
+ };
133
+ const result = PluginManifestSchema.safeParse(raw);
134
+ if (!result.success) {
135
+ const first = result.error.issues[0];
136
+ const field = first?.path.join(".") ?? "unknown";
137
+ const message = first?.message ?? "validation failed";
138
+ throw new Error(
139
+ `manifest.json validation failed at '${field}': ${message}`
140
+ );
141
+ }
142
+ return result.data;
143
+ }
144
+
145
+ export {
146
+ appToolId,
147
+ defineApp
148
+ };
@@ -0,0 +1,21 @@
1
+ // src/plugin/ryu-plugin.ts
2
+ function toDisposable(teardown) {
3
+ let done = false;
4
+ return {
5
+ dispose() {
6
+ if (done) {
7
+ return;
8
+ }
9
+ done = true;
10
+ teardown();
11
+ }
12
+ };
13
+ }
14
+ function definePlugin(plugin) {
15
+ return plugin;
16
+ }
17
+
18
+ export {
19
+ toDisposable,
20
+ definePlugin
21
+ };
File without changes