@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
package/dist/index.cjs CHANGED
@@ -37,6 +37,10 @@ __export(src_exports, {
37
37
  CapabilityReqSchema: () => CapabilityReqSchema,
38
38
  CompanionSurfaceSchema: () => CompanionSurfaceSchema,
39
39
  DEFAULT_GATEWAY_URL: () => DEFAULT_GATEWAY_URL,
40
+ MCP_PROTOCOL_VERSION: () => MCP_PROTOCOL_VERSION,
41
+ McpServer: () => McpServer,
42
+ McpServerAuthSchema: () => McpServerAuthSchema,
43
+ McpServerDeclSchema: () => McpServerDeclSchema,
40
44
  ModelClient: () => ModelClient2,
41
45
  PRIMITIVE_BINDINGS: () => PRIMITIVE_BINDINGS,
42
46
  PluginBuilder: () => PluginBuilder,
@@ -45,6 +49,10 @@ __export(src_exports, {
45
49
  RunnableKindSchema: () => RunnableKindSchema,
46
50
  RunnableMetaSchema: () => RunnableMetaSchema,
47
51
  SkillBuilder: () => SkillBuilder,
52
+ SlashCommandArgumentSchema: () => SlashCommandArgumentSchema,
53
+ SlashCommandContributionSchema: () => SlashCommandContributionSchema,
54
+ SlashCommandCustomOptionSchema: () => SlashCommandCustomOptionSchema,
55
+ SlashCommandOptionSchema: () => SlashCommandOptionSchema,
48
56
  SurfaceSchema: () => SurfaceSchema,
49
57
  ToolAppConfigSchema: () => ToolAppConfigSchema,
50
58
  ToolBuilder: () => ToolBuilder,
@@ -54,9 +62,11 @@ __export(src_exports, {
54
62
  app: () => app,
55
63
  appToolId: () => appToolId,
56
64
  assertAllowedEgressUrl: () => assertAllowedEgressUrl,
65
+ callTool: () => callTool,
57
66
  coreManifestJsonSchema: () => coreManifestJsonSchema,
58
67
  createAgent: () => createAgent,
59
68
  createPrimitives: () => createPrimitives,
69
+ defineAction: () => defineAction,
60
70
  defineAgent: () => defineAgent,
61
71
  defineApp: () => defineApp,
62
72
  defineModel: () => defineModel,
@@ -67,19 +77,22 @@ __export(src_exports, {
67
77
  defineWorkflow: () => defineWorkflow,
68
78
  httpPrimitiveTransport: () => httpPrimitiveTransport,
69
79
  inlineToolRunnable: () => inlineToolRunnable,
80
+ listTools: () => listTools,
70
81
  query: () => query,
71
82
  resolveGatewayToken: () => resolveGatewayToken2,
72
83
  resolveGatewayUrl: () => resolveGatewayUrl3,
73
84
  ryuTool: () => ryuTool,
74
85
  skill: () => skill,
86
+ toDisposable: () => toDisposable,
75
87
  tool: () => tool,
88
+ unwrapContent: () => unwrapContent,
76
89
  validateManifestStrict: () => validateManifestStrict,
77
90
  validatePluginId: () => validatePluginId,
78
91
  workflow: () => workflow
79
92
  });
80
93
  module.exports = __toCommonJS(src_exports);
81
94
 
82
- // ../../node_modules/.bun/tsup@8.5.1+7a7b737fa55947b7/node_modules/tsup/assets/cjs_shims.js
95
+ // ../../node_modules/.bun/tsup@8.5.1+68ce2442ac00f52e/node_modules/tsup/assets/cjs_shims.js
83
96
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
84
97
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
85
98
 
@@ -317,6 +330,41 @@ var PRIMITIVE_BINDINGS = {
317
330
  method: "model.complete",
318
331
  grant: "hook:side-model"
319
332
  },
333
+ "background.list": {
334
+ transport: "bridge",
335
+ method: "background.list",
336
+ grant: "background:control"
337
+ },
338
+ "background.stop": {
339
+ transport: "bridge",
340
+ method: "background.stop",
341
+ grant: "background:control"
342
+ },
343
+ "storage.get": {
344
+ transport: "bridge",
345
+ method: "storage.get",
346
+ grant: "storage:kv"
347
+ },
348
+ "storage.set": {
349
+ transport: "bridge",
350
+ method: "storage.set",
351
+ grant: "storage:kv"
352
+ },
353
+ "storage.delete": {
354
+ transport: "bridge",
355
+ method: "storage.delete",
356
+ grant: "storage:kv"
357
+ },
358
+ "storage.keys": {
359
+ transport: "bridge",
360
+ method: "storage.keys",
361
+ grant: "storage:kv"
362
+ },
363
+ "storage.compareAndSet": {
364
+ transport: "bridge",
365
+ method: "storage.compareAndSet",
366
+ grant: "storage:kv"
367
+ },
320
368
  // Host-direct media data-path (the host holds the node token; returns data: URLs).
321
369
  "image.generate": {
322
370
  transport: "direct",
@@ -350,6 +398,22 @@ function brokerCall(transport, cap, op, input) {
350
398
  }
351
399
  function createPrimitives(transport) {
352
400
  return {
401
+ background: {
402
+ list: (input) => transport.bridge("background.list", {
403
+ producer: input?.producer,
404
+ running_only: input?.runningOnly
405
+ }),
406
+ stop: (input) => transport.bridge("background.stop", {
407
+ process_id: input.processId
408
+ })
409
+ },
410
+ storage: {
411
+ get: (input) => transport.bridge("storage.get", input),
412
+ set: (input) => transport.bridge("storage.set", input).then(() => void 0),
413
+ delete: (input) => transport.bridge("storage.delete", input).then(() => void 0),
414
+ keys: (input) => transport.bridge("storage.keys", input ?? {}).then((value) => Array.isArray(value) ? value : []),
415
+ compareAndSet: (input) => transport.bridge("storage.compareAndSet", input)
416
+ },
353
417
  rag: {
354
418
  retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
355
419
  embed: (input) => brokerCall(transport, "rag", "embed", input),
@@ -976,8 +1040,38 @@ var WidgetContributionSchema = import_zod.z.object({
976
1040
  /** Default display mode (`inline` | `fullscreen` | `pip`). */
977
1041
  default_display_mode: import_zod.z.string().default(DEFAULT_WIDGET_DISPLAY_MODE)
978
1042
  });
1043
+ var ChatWidgetTemplateSchema = import_zod.z.object({
1044
+ id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
1045
+ title: import_zod.z.string().min(1),
1046
+ description: import_zod.z.string().optional(),
1047
+ triggers: import_zod.z.array(import_zod.z.string()).default([]),
1048
+ examples: import_zod.z.array(import_zod.z.string()).default([]),
1049
+ backing: import_zod.z.object({
1050
+ tool_id: import_zod.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
1051
+ view_id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional()
1052
+ }),
1053
+ display_mode: import_zod.z.string().min(1),
1054
+ safe_action_ids: import_zod.z.array(import_zod.z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
1055
+ availability: import_zod.z.string().default("available")
1056
+ }).superRefine((value, ctx) => {
1057
+ const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
1058
+ if (count !== 1 && value.availability === "available") {
1059
+ ctx.addIssue({
1060
+ code: "custom",
1061
+ path: ["backing"],
1062
+ message: "available templates need exactly one backing tool_id or view_id"
1063
+ });
1064
+ }
1065
+ if (count > 1) {
1066
+ ctx.addIssue({
1067
+ code: "custom",
1068
+ path: ["backing"],
1069
+ message: "backing must declare at most one of tool_id or view_id"
1070
+ });
1071
+ }
1072
+ });
979
1073
  var ToolAppConfigSchema = import_zod.z.object({
980
- /** MCP tool slug this runnable wraps — the fully-qualified `<server>__<name>` id. */
1074
+ /** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
981
1075
  slug: import_zod.z.string().min(1),
982
1076
  /** The tool description the model reads when choosing it. Carried here because a
983
1077
  * packed app's manifest is the only channel (there is no `generated.rs`); Core's
@@ -996,6 +1090,33 @@ var ToolAppConfigSchema = import_zod.z.object({
996
1090
  /** Optional status label shown when the render tool finishes. */
997
1091
  invoked: import_zod.z.string().optional()
998
1092
  });
1093
+ var SlashCommandOptionSchema = import_zod.z.object({
1094
+ description: import_zod.z.string().optional(),
1095
+ label: import_zod.z.string().min(1),
1096
+ value: import_zod.z.string().min(1)
1097
+ }).passthrough();
1098
+ var SlashCommandCustomOptionSchema = import_zod.z.union([
1099
+ import_zod.z.literal(true),
1100
+ import_zod.z.object({
1101
+ description: import_zod.z.string().optional(),
1102
+ label: import_zod.z.string().min(1).optional()
1103
+ }).passthrough()
1104
+ ]);
1105
+ var SlashCommandArgumentSchema = import_zod.z.object({
1106
+ allow_custom: import_zod.z.boolean().optional(),
1107
+ custom: SlashCommandCustomOptionSchema.optional(),
1108
+ description: import_zod.z.string().optional(),
1109
+ name: import_zod.z.string().min(1),
1110
+ options: import_zod.z.array(SlashCommandOptionSchema).optional()
1111
+ }).passthrough();
1112
+ var SlashCommandContributionSchema = import_zod.z.object({
1113
+ args: import_zod.z.array(SlashCommandArgumentSchema).optional(),
1114
+ body: import_zod.z.string().optional(),
1115
+ command: import_zod.z.string().min(1),
1116
+ description: import_zod.z.string().optional(),
1117
+ id: import_zod.z.string().optional(),
1118
+ parameters: import_zod.z.array(SlashCommandArgumentSchema).optional()
1119
+ }).passthrough();
999
1120
  var ContributesSchema = import_zod.z.object({
1000
1121
  turn_hooks: import_zod.z.array(TurnHookContributionSchema).default([]),
1001
1122
  /** App events this plugin EMITS — the provider half of the hook system, whose
@@ -1004,13 +1125,20 @@ var ContributesSchema = import_zod.z.object({
1004
1125
  * signing, leaving an app that emits events nothing is allowed to subscribe to. */
1005
1126
  hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
1006
1127
  composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1128
+ /** Chat feature descriptors whose behavior is implemented by the host shell.
1129
+ * Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
1130
+ * authoring schema prevents `ryu pack` from silently deleting a plugin's chat
1131
+ * feature declaration before signing. */
1132
+ chat_features: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1007
1133
  settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1008
- slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1134
+ slash_commands: import_zod.z.array(SlashCommandContributionSchema).default([]),
1009
1135
  /** App widgets (Ryu Apps). Each binds a render tool id to its
1010
1136
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
1011
1137
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
1012
1138
  * every widget an app authored here declares. */
1013
1139
  widgets: import_zod.z.array(WidgetContributionSchema).default([]),
1140
+ /** Metadata-only chat widget templates. */
1141
+ chat_widget_templates: import_zod.z.array(ChatWidgetTemplateSchema).optional(),
1014
1142
  /** App-registered sidebar sections (header + live list) and buttons (single nav
1015
1143
  * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
1016
1144
  * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
@@ -1023,6 +1151,13 @@ var ContributesSchema = import_zod.z.object({
1023
1151
  * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
1024
1152
  * strip the dock panel an app declares here. */
1025
1153
  dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1154
+ /** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
1155
+ * typed for the same reason as the surfaces above — the shell owns the
1156
+ * `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
1157
+ * without it the CLI's zod parse would strip every live activity an app
1158
+ * declares here, so a packed bundle would ship a dock that silently stays
1159
+ * empty. */
1160
+ live_activities: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1026
1161
  /** Deletable data categories the app owns — one "Delete all X" row in Settings
1027
1162
  * → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
1028
1163
  * it the CLI's zod parse would strip the declaration before signing, and the
@@ -1060,7 +1195,13 @@ var ContributesSchema = import_zod.z.object({
1060
1195
  * bug: the styles' `.md` files are not carried by the bundle either, so there
1061
1196
  * would be no residue to notice — the plugin would install clean and contribute
1062
1197
  * nothing. */
1063
- output_styles: import_zod.z.array(OutputStyleContributionSchema).default([])
1198
+ output_styles: import_zod.z.array(OutputStyleContributionSchema).default([]),
1199
+ /** Per-message actions contributed by an enabled plugin. Kept as loose records
1200
+ * so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
1201
+ message_actions: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1202
+ /** Buttons contributed to the floating text-selection toolbar. Kept as loose
1203
+ * records so host-owned dispatch args survive `ryu pack` unchanged. */
1204
+ selection_actions: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([])
1064
1205
  });
1065
1206
  var SetupStepSchema = import_zod.z.object({
1066
1207
  /** Card heading (e.g. the companion app name). */
@@ -1141,6 +1282,61 @@ var EnginesReqSchema = import_zod.z.object({
1141
1282
  /** Floor for the Next.js web app. */
1142
1283
  web: import_zod.z.string().optional()
1143
1284
  });
1285
+ var McpServerAuthSchema = import_zod.z.object({
1286
+ client_id: import_zod.z.string().min(1).optional(),
1287
+ type: import_zod.z.literal("oauth")
1288
+ }).strict();
1289
+ var McpServerDeclSchema = import_zod.z.object({
1290
+ args: import_zod.z.array(import_zod.z.string()).default([]),
1291
+ auth: McpServerAuthSchema.optional(),
1292
+ command: import_zod.z.string().optional(),
1293
+ command_env: import_zod.z.string().optional(),
1294
+ description: import_zod.z.string().optional(),
1295
+ enabled: import_zod.z.boolean().default(true),
1296
+ env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
1297
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
1298
+ type: import_zod.z.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"]).optional(),
1299
+ url: import_zod.z.url().optional()
1300
+ }).superRefine((server, context) => {
1301
+ if (!(server.command || server.url)) {
1302
+ context.addIssue({
1303
+ code: "custom",
1304
+ message: "an MCP server requires command or url"
1305
+ });
1306
+ }
1307
+ if (!server.auth) {
1308
+ return;
1309
+ }
1310
+ if (server.command || server.type === "stdio" || !server.url) {
1311
+ context.addIssue({
1312
+ code: "custom",
1313
+ message: "OAuth is supported only for remote HTTP MCP servers"
1314
+ });
1315
+ return;
1316
+ }
1317
+ if (Object.keys(server.headers).some(
1318
+ (name) => name.toLowerCase() === "authorization"
1319
+ )) {
1320
+ context.addIssue({
1321
+ code: "custom",
1322
+ message: "OAuth cannot be combined with a static Authorization header"
1323
+ });
1324
+ }
1325
+ const url = new URL(server.url);
1326
+ const loopback = url.hostname === "localhost" || url.hostname.startsWith("127.") || url.hostname === "[::1]" || url.hostname === "::1";
1327
+ if (url.username || url.password || url.hash) {
1328
+ context.addIssue({
1329
+ code: "custom",
1330
+ message: "OAuth MCP URLs cannot contain credentials or fragments"
1331
+ });
1332
+ }
1333
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
1334
+ context.addIssue({
1335
+ code: "custom",
1336
+ message: "OAuth MCP URLs must use HTTPS except on loopback"
1337
+ });
1338
+ }
1339
+ });
1144
1340
  var PluginManifestSchema = import_zod.z.object({
1145
1341
  /** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
1146
1342
  id: import_zod.z.string().min(1, "id is required"),
@@ -1155,6 +1351,9 @@ var PluginManifestSchema = import_zod.z.object({
1155
1351
  /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/,
1156
1352
  "version must be a valid semver string (e.g. 1.0.0)"
1157
1353
  ),
1354
+ /** Core-owned release maturity metadata. The Rust contract validates the
1355
+ * richer shape; the SDK authoring parser must preserve it for pack/publish. */
1356
+ stability: import_zod.z.unknown().optional(),
1158
1357
  /**
1159
1358
  * Lower-case hex `sha256(utf8_bytes(ui_code))` binding the plugin's bundled
1160
1359
  * sandboxed-UI code to this manifest. `ryu pack` / `ryu publish` compute it and
@@ -1172,6 +1371,26 @@ var PluginManifestSchema = import_zod.z.object({
1172
1371
  * Declarations only — grant enforcement is the Gateway's responsibility.
1173
1372
  */
1174
1373
  permission_grants: import_zod.z.array(import_zod.z.string()).default([]),
1374
+ /** Deny-by-default sandbox permissions. Core remains authoritative; this
1375
+ * schema mirrors the fields so `ryu pack` cannot strip them. */
1376
+ permissions: import_zod.z.object({
1377
+ fs: import_zod.z.object({
1378
+ read: import_zod.z.array(import_zod.z.string()).default([]),
1379
+ write: import_zod.z.array(import_zod.z.string()).default([])
1380
+ }).optional(),
1381
+ child_process: import_zod.z.boolean().optional(),
1382
+ run: import_zod.z.array(import_zod.z.string()).default([]),
1383
+ network: import_zod.z.union([import_zod.z.boolean(), import_zod.z.array(import_zod.z.string())]).optional(),
1384
+ tool: import_zod.z.array(import_zod.z.string()).default([])
1385
+ }).optional(),
1386
+ /** Core-owned permission presentation levels. Preserved verbatim here and
1387
+ * validated by Core's authoritative manifest contract. */
1388
+ permission_levels: import_zod.z.unknown().optional(),
1389
+ /** Remote or stdio MCP servers registered by this plugin. */
1390
+ mcp_servers: import_zod.z.record(import_zod.z.string(), McpServerDeclSchema).optional(),
1391
+ /** Core-owned sidecar declarations. Their full process/HTTP schema stays in
1392
+ * the Rust contract; this authoring layer must never strip them. */
1393
+ sidecars: import_zod.z.unknown().optional(),
1175
1394
  /**
1176
1395
  * Optional Companion surface (an in-desktop overlay or sidebar panel).
1177
1396
  * Absent when the plugin has no Companion surface.
@@ -1207,6 +1426,9 @@ var PluginManifestSchema = import_zod.z.object({
1207
1426
  * an unsupported-target plugin stays installable and inspectable.
1208
1427
  */
1209
1428
  targets: import_zod.z.array(SurfaceSchema).default([]),
1429
+ /** Rich per-surface declarations (`support`, UI, contributed commands, …).
1430
+ * Core owns and validates the nested vocabulary. */
1431
+ surfaces: import_zod.z.unknown().optional(),
1210
1432
  /**
1211
1433
  * Host version floors — the semver requirement each surface must satisfy for
1212
1434
  * this plugin to install. Mirrors Core's `EnginesReq`
@@ -1265,10 +1487,16 @@ var PluginManifestSchema = import_zod.z.object({
1265
1487
  url: import_zod.z.string().optional()
1266
1488
  })
1267
1489
  ]).optional(),
1490
+ /** Public source repository URL (Claude/Codex `repository`). */
1491
+ repository: import_zod.z.string().url().optional(),
1492
+ /** True when the provider operates outside the local Ryu runtime. */
1493
+ external: import_zod.z.boolean().optional(),
1268
1494
  /** Project/marketing homepage — maps to the listing `website` (Claude field). */
1269
1495
  homepage: import_zod.z.string().optional(),
1270
1496
  /** Free-text search keywords (Claude field). */
1271
1497
  keywords: import_zod.z.array(import_zod.z.string()).optional(),
1498
+ /** Stable Marketplace filter labels (Ryu extension). */
1499
+ tags: import_zod.z.array(import_zod.z.string()).optional(),
1272
1500
  /** Taxonomy category beyond the runnable kinds (Claude field). */
1273
1501
  category: import_zod.z.string().optional(),
1274
1502
  /** SPDX-ish license identifier (Claude field). */
@@ -1282,6 +1510,8 @@ var PluginManifestSchema = import_zod.z.object({
1282
1510
  * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
1283
1511
  */
1284
1512
  icon: import_zod.z.string().optional(),
1513
+ /** Optional detail-page hero banner metadata forwarded to the marketplace. */
1514
+ banner: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
1285
1515
  /**
1286
1516
  * Dithered-gradient background for the card's icon square (Ryu extension),
1287
1517
  * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
@@ -1313,6 +1543,22 @@ var PluginManifestSchema = import_zod.z.object({
1313
1543
  * array of steps guiding the user through post-install configuration.
1314
1544
  */
1315
1545
  setup: import_zod.z.union([SetupStepSchema, import_zod.z.array(SetupStepSchema)]).optional()
1546
+ }).passthrough().superRefine((manifest, context) => {
1547
+ const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
1548
+ (server) => server.auth?.type === "oauth"
1549
+ );
1550
+ if (!hasOAuthServer) {
1551
+ return;
1552
+ }
1553
+ for (const grant of ["mcp:server", "identity.read"]) {
1554
+ if (!manifest.permission_grants.includes(grant)) {
1555
+ context.addIssue({
1556
+ code: "custom",
1557
+ message: `OAuth MCP servers require the ${grant} permission grant`,
1558
+ path: ["permission_grants"]
1559
+ });
1560
+ }
1561
+ }
1316
1562
  });
1317
1563
  function validatePluginId(id) {
1318
1564
  loadNative().validatePluginId(id);
@@ -1324,6 +1570,85 @@ function coreManifestJsonSchema() {
1324
1570
  return JSON.parse(loadNative().pluginManifestJsonSchema());
1325
1571
  }
1326
1572
 
1573
+ // src/runnable/tool.ts
1574
+ function validateInput(input, schema, toolId) {
1575
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
1576
+ throw new Error(
1577
+ `[ryu-sdk] Tool "${toolId}" input must be an object, got ${JSON.stringify(input)}`
1578
+ );
1579
+ }
1580
+ const record = input;
1581
+ for (const requiredKey of schema.required ?? []) {
1582
+ if (!(requiredKey in record)) {
1583
+ throw new Error(
1584
+ `[ryu-sdk] Tool "${toolId}" input missing required field "${requiredKey}"`
1585
+ );
1586
+ }
1587
+ }
1588
+ for (const [key, prop] of Object.entries(schema.properties)) {
1589
+ if (!(key in record)) {
1590
+ continue;
1591
+ }
1592
+ const value = record[key];
1593
+ if (!checkType(value, prop.type)) {
1594
+ throw new Error(
1595
+ `[ryu-sdk] Tool "${toolId}" input field "${key}" expected type "${prop.type}", got ${JSON.stringify(value)}`
1596
+ );
1597
+ }
1598
+ }
1599
+ }
1600
+ function checkType(value, type) {
1601
+ switch (type) {
1602
+ case "string":
1603
+ return typeof value === "string";
1604
+ case "number":
1605
+ case "integer":
1606
+ return typeof value === "number";
1607
+ case "boolean":
1608
+ return typeof value === "boolean";
1609
+ case "array":
1610
+ return Array.isArray(value);
1611
+ case "object":
1612
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1613
+ default:
1614
+ return false;
1615
+ }
1616
+ }
1617
+ function defineTool(options) {
1618
+ const { description, id, name, schema, run } = options;
1619
+ const code = `return await (${run.toString()})(input, host);`;
1620
+ return {
1621
+ id,
1622
+ name,
1623
+ kind: "tool",
1624
+ schema,
1625
+ ...description === void 0 ? {} : { description },
1626
+ code,
1627
+ run(input, ctx) {
1628
+ validateInput(input, schema, id);
1629
+ return run(input, ctx);
1630
+ }
1631
+ };
1632
+ }
1633
+ function inlineToolRunnable(tool2, options) {
1634
+ return {
1635
+ id: tool2.id,
1636
+ name: tool2.name,
1637
+ kind: "tool",
1638
+ config: {
1639
+ slug: tool2.id,
1640
+ backend: "inline_deno",
1641
+ code: tool2.code,
1642
+ input_schema: tool2.schema,
1643
+ ...options?.description === void 0 ? tool2.description === void 0 ? {} : { description: tool2.description } : { description: options.description },
1644
+ ...options?.outputSchema ? { output_schema: options.outputSchema } : {},
1645
+ ...options?.annotations ? { annotations: options.annotations } : {},
1646
+ ...options?.action === void 0 ? {} : { action: options.action },
1647
+ ...options?.needsApproval === void 0 ? {} : { needs_approval: options.needsApproval }
1648
+ }
1649
+ };
1650
+ }
1651
+
1327
1652
  // src/runnable/app.ts
1328
1653
  var DEFAULT_APP_WIDGET_MIME = "text/html+skybridge";
1329
1654
  var DEFAULT_APP_DISPLAY_MODE = "inline";
@@ -1335,8 +1660,15 @@ function withWidgetRenderGrant(grants, widgets) {
1335
1660
  }
1336
1661
  return out;
1337
1662
  }
1663
+ function withToolExecuteGrant(grants, tools) {
1664
+ const out = [...grants];
1665
+ if (tools.some((tool2) => tool2.runnable !== void 0) && !out.includes("tool:execute")) {
1666
+ out.push("tool:execute");
1667
+ }
1668
+ return out;
1669
+ }
1338
1670
  function appToolId(server, name) {
1339
- return `${server}__${name}`;
1671
+ return `${server}.${name}`;
1340
1672
  }
1341
1673
  function defineApp(options) {
1342
1674
  const server = options.server ?? options.slug;
@@ -1349,7 +1681,9 @@ function defineApp(options) {
1349
1681
  for (const spec of options.tools) {
1350
1682
  const isRender = spec.accessible !== true;
1351
1683
  const id = appToolId(server, spec.name);
1684
+ const executable = spec.runnable ? inlineToolRunnable(spec.runnable) : void 0;
1352
1685
  const config = {
1686
+ ...executable?.config ?? {},
1353
1687
  slug: id,
1354
1688
  description: spec.description,
1355
1689
  widget: isRender,
@@ -1376,16 +1710,18 @@ function defineApp(options) {
1376
1710
  }
1377
1711
  const contributes = {
1378
1712
  turn_hooks: [],
1713
+ chat_features: [],
1379
1714
  // This builder synthesises an app from its runnables; an app that emits
1380
1715
  // events declares them in a hand-authored `manifest.json`, same as
1381
1716
  // `lsp_servers` below.
1382
1717
  hook_events: [],
1383
1718
  composer_controls: [],
1384
1719
  settings_tabs: [],
1385
- slash_commands: [],
1720
+ slash_commands: options.slashCommands ?? [],
1386
1721
  sidebar_sections: [],
1387
1722
  sidebar_buttons: [],
1388
1723
  dock_panels: [],
1724
+ live_activities: [],
1389
1725
  // Empty for the same reason as every sibling family above: this builder
1390
1726
  // synthesises `widgets` from the app's own runnables and nothing else, and
1391
1727
  // takes no `contributes` passthrough. An app that wants to declare language
@@ -1398,6 +1734,8 @@ function defineApp(options) {
1398
1734
  data_categories: [],
1399
1735
  pi_extensions: [],
1400
1736
  output_styles: [],
1737
+ message_actions: [],
1738
+ selection_actions: [],
1401
1739
  widgets
1402
1740
  };
1403
1741
  const raw = {
@@ -1411,15 +1749,15 @@ function defineApp(options) {
1411
1749
  // Core gates widget promotion on declared-AND-enabled-AND-granted, and a
1412
1750
  // missing grant fails as `DeniedNoGrant` — which is an `info!` log and
1413
1751
  // nothing else. The widget silently renders as plain text, with no error
1414
- // in the UI and nothing pointing at the manifest. Every app scaffolded
1415
- // through `defineApp` hit that, because the only fix was a grant string
1416
- // the templates never mention and the builder never added; the one
1417
- // working example on disk hand-writes it.
1752
+ // in the UI and nothing pointing at the manifest.
1418
1753
  //
1419
1754
  // Added only when there is a widget to render, and unioned rather than
1420
1755
  // overwritten so an author's own `grants` list survives and re-declaring
1421
1756
  // it is not an error.
1422
- permission_grants: withWidgetRenderGrant(options.grants ?? [], widgets),
1757
+ permission_grants: withToolExecuteGrant(
1758
+ withWidgetRenderGrant(options.grants ?? [], widgets),
1759
+ options.tools
1760
+ ),
1423
1761
  activation_events: options.activationEvents ?? ["*"],
1424
1762
  contributes,
1425
1763
  // `targets: []` means EVERY surface, so an app that declares none is
@@ -1660,6 +1998,7 @@ var AppBuilder = class {
1660
1998
  _dependencies = [];
1661
1999
  _requiredCapabilities = [];
1662
2000
  _requiredGrants = [];
2001
+ _slashCommands = [];
1663
2002
  _targets = [];
1664
2003
  /** Set the reverse-domain app id (e.g. `"com.example.checklist"`). */
1665
2004
  id(value) {
@@ -1716,6 +2055,11 @@ var AppBuilder = class {
1716
2055
  this._tools.push(spec);
1717
2056
  return this;
1718
2057
  }
2058
+ /** Add a slash command and its optional sequential argument choices. */
2059
+ slashCommand(command) {
2060
+ this._slashCommands.push(command);
2061
+ return this;
2062
+ }
1719
2063
  /**
1720
2064
  * Declare a **plugin-to-plugin dependency** (auto-enabled, in dependency order,
1721
2065
  * before this app). `minVersion` is a MINIMUM (`"1.2.0"` = `">=1.2.0"`).
@@ -1763,6 +2107,7 @@ var AppBuilder = class {
1763
2107
  uiEntry: this._uiEntry,
1764
2108
  tools: this._tools,
1765
2109
  grants: this._grants,
2110
+ slashCommands: this._slashCommands,
1766
2111
  ...this._server ? { server: this._server } : {},
1767
2112
  ...this._displayMode ? { displayMode: this._displayMode } : {},
1768
2113
  ...this._mime ? { mime: this._mime } : {},
@@ -1781,6 +2126,442 @@ var AppBuilder = class {
1781
2126
  };
1782
2127
  var app = () => new AppBuilder();
1783
2128
 
2129
+ // src/mcp/client.ts
2130
+ var import_node_child_process = require("child_process");
2131
+ var import_node_readline = require("readline");
2132
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
2133
+ var RPC_TIMEOUT_MS = 6e4;
2134
+ var McpConnection = class _McpConnection {
2135
+ proc;
2136
+ nextId = 1;
2137
+ closed = false;
2138
+ waiters = [];
2139
+ constructor(proc) {
2140
+ this.proc = proc;
2141
+ }
2142
+ /** Spawn the server and complete the MCP `initialize` handshake. */
2143
+ static async connect(cmd) {
2144
+ const env2 = { ...process.env, ...cmd.env ?? {} };
2145
+ const proc = (0, import_node_child_process.spawn)(cmd.command, cmd.args ?? [], {
2146
+ stdio: ["pipe", "pipe", "pipe"],
2147
+ env: env2
2148
+ });
2149
+ if (!(proc.stdin && proc.stdout)) {
2150
+ proc.kill();
2151
+ throw new Error(`MCP server '${cmd.command}' stdin/stdout unavailable`);
2152
+ }
2153
+ if (proc.stderr) {
2154
+ proc.stderr.on("data", (chunk) => {
2155
+ process.stderr.write(`[mcp-server] ${chunk.toString()}`);
2156
+ });
2157
+ }
2158
+ const conn = new _McpConnection(proc);
2159
+ const rl = (0, import_node_readline.createInterface)({
2160
+ input: proc.stdout,
2161
+ crlfDelay: Number.POSITIVE_INFINITY
2162
+ });
2163
+ rl.on("line", (rawLine) => {
2164
+ const trimmed = rawLine.trim();
2165
+ if (!trimmed) {
2166
+ return;
2167
+ }
2168
+ let parsed;
2169
+ try {
2170
+ parsed = JSON.parse(trimmed);
2171
+ } catch {
2172
+ return;
2173
+ }
2174
+ if (parsed.id === void 0 || parsed.id === null) {
2175
+ return;
2176
+ }
2177
+ const idx = conn.waiters.findIndex((w) => w.id === parsed.id);
2178
+ if (idx === -1) {
2179
+ return;
2180
+ }
2181
+ const [waiter] = conn.waiters.splice(idx, 1);
2182
+ if (!waiter) {
2183
+ return;
2184
+ }
2185
+ clearTimeout(waiter.timer);
2186
+ if (parsed.error) {
2187
+ waiter.reject(new Error(`MCP error: ${JSON.stringify(parsed.error)}`));
2188
+ } else {
2189
+ waiter.resolve(parsed.result ?? null);
2190
+ }
2191
+ });
2192
+ rl.on("close", () => {
2193
+ for (const waiter of conn.waiters.splice(0)) {
2194
+ clearTimeout(waiter.timer);
2195
+ waiter.reject(new Error("MCP server closed the connection"));
2196
+ }
2197
+ });
2198
+ await conn.request("initialize", {
2199
+ protocolVersion: MCP_PROTOCOL_VERSION,
2200
+ capabilities: {},
2201
+ clientInfo: { name: "ryu-sdk", version: "0.0.1" }
2202
+ });
2203
+ conn.notify("notifications/initialized", {});
2204
+ return conn;
2205
+ }
2206
+ /** Send a JSON-RPC request and return the `result` field. */
2207
+ request(method, params) {
2208
+ const id = this.nextId++;
2209
+ const frame = JSON.stringify({
2210
+ jsonrpc: "2.0",
2211
+ id,
2212
+ method,
2213
+ params
2214
+ });
2215
+ this.write(frame);
2216
+ return new Promise((resolve, reject) => {
2217
+ const timer = setTimeout(
2218
+ () => reject(new Error(`MCP request '${method}' timed out`)),
2219
+ RPC_TIMEOUT_MS
2220
+ );
2221
+ this.waiters.push({ id, resolve, reject, timer });
2222
+ });
2223
+ }
2224
+ /** Send a JSON-RPC notification (no response expected). */
2225
+ notify(method, params) {
2226
+ const frame = JSON.stringify({ jsonrpc: "2.0", method, params });
2227
+ this.write(frame);
2228
+ }
2229
+ write(frame) {
2230
+ if (this.closed) {
2231
+ return;
2232
+ }
2233
+ this.proc.stdin?.write(`${frame}
2234
+ `);
2235
+ }
2236
+ /** Graceful shutdown: close stdin, then kill. */
2237
+ async shutdown() {
2238
+ this.closed = true;
2239
+ this.proc.stdin?.end();
2240
+ await new Promise((resolve) => {
2241
+ this.proc.once("exit", () => resolve());
2242
+ this.proc.kill();
2243
+ setTimeout(resolve, 500);
2244
+ });
2245
+ }
2246
+ };
2247
+ async function listTools(cmd) {
2248
+ const conn = await McpConnection.connect(cmd);
2249
+ let result;
2250
+ try {
2251
+ result = await conn.request("tools/list", {});
2252
+ } finally {
2253
+ await conn.shutdown();
2254
+ }
2255
+ const tools = result?.tools ?? [];
2256
+ return tools.map((t) => {
2257
+ const tool2 = t;
2258
+ const name = tool2.name;
2259
+ if (typeof name !== "string") {
2260
+ return null;
2261
+ }
2262
+ return {
2263
+ name,
2264
+ description: typeof tool2.description === "string" ? tool2.description : void 0,
2265
+ inputSchema: tool2.inputSchema
2266
+ };
2267
+ }).filter((t) => t !== null);
2268
+ }
2269
+ async function callTool(cmd, tool2, args) {
2270
+ const conn = await McpConnection.connect(cmd);
2271
+ try {
2272
+ return await conn.request("tools/call", { name: tool2, arguments: args });
2273
+ } finally {
2274
+ await conn.shutdown();
2275
+ }
2276
+ }
2277
+
2278
+ // src/mcp/server.ts
2279
+ var import_node_readline2 = require("readline");
2280
+ function respond(id, result) {
2281
+ return { jsonrpc: "2.0", id: id ?? null, result };
2282
+ }
2283
+ function respondError(id, code, message) {
2284
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
2285
+ }
2286
+ function wrapContent(value) {
2287
+ return {
2288
+ content: [
2289
+ {
2290
+ type: "text",
2291
+ text: typeof value === "string" ? value : JSON.stringify(value)
2292
+ }
2293
+ ]
2294
+ };
2295
+ }
2296
+ function unwrapContent(raw) {
2297
+ const r = raw;
2298
+ const text = r?.content?.[0]?.text;
2299
+ if (text === void 0) {
2300
+ return raw;
2301
+ }
2302
+ try {
2303
+ return JSON.parse(text);
2304
+ } catch {
2305
+ return text;
2306
+ }
2307
+ }
2308
+ var McpServer = class {
2309
+ runnables = /* @__PURE__ */ new Map();
2310
+ passthroughs = [];
2311
+ /**
2312
+ * Register an SDK Runnable as an MCP tool.
2313
+ * Returns `this` for chaining.
2314
+ */
2315
+ register(runnable) {
2316
+ this.runnables.set(runnable.name, runnable);
2317
+ return this;
2318
+ }
2319
+ /**
2320
+ * Register a passthrough to an external MCP stdio server (e.g. Ghost or
2321
+ * Shadow). Tools from that server are fetched lazily and re-advertised.
2322
+ * Returns `this` for chaining.
2323
+ */
2324
+ passthrough(registration) {
2325
+ this.passthroughs.push(registration);
2326
+ return this;
2327
+ }
2328
+ /** Fetch all tools: local Runnables + passthrough tools. */
2329
+ async allTools() {
2330
+ const local = [...this.runnables.values()].map((r) => ({
2331
+ name: r.name,
2332
+ description: r.description,
2333
+ inputSchema: r.inputSchema
2334
+ }));
2335
+ const remote = [];
2336
+ for (const pt of this.passthroughs) {
2337
+ try {
2338
+ const tools = await listTools(pt.command);
2339
+ remote.push(...tools);
2340
+ } catch (err) {
2341
+ process.stderr.write(
2342
+ `[mcp-server] passthrough '${pt.label}' list_tools failed: ${err}
2343
+ `
2344
+ );
2345
+ }
2346
+ }
2347
+ return [...local, ...remote];
2348
+ }
2349
+ /** Handle a `tools/call` request. */
2350
+ async handleCallTool(name, args) {
2351
+ const local = this.runnables.get(name);
2352
+ if (local) {
2353
+ const result = await local.run(args);
2354
+ return wrapContent(result);
2355
+ }
2356
+ for (const pt of this.passthroughs) {
2357
+ try {
2358
+ const tools = await listTools(pt.command);
2359
+ if (tools.some((t) => t.name === name)) {
2360
+ return await callTool(pt.command, name, args);
2361
+ }
2362
+ } catch {
2363
+ }
2364
+ }
2365
+ throw new Error(`Unknown tool: ${name}`);
2366
+ }
2367
+ /**
2368
+ * Handle a single parsed JSON-RPC request line. Returns the response to write
2369
+ * (or null for notifications that require no response).
2370
+ */
2371
+ async handleRequest(req, initialized, write) {
2372
+ const { id, method, params } = req;
2373
+ if (method === "initialize") {
2374
+ initialized.value = true;
2375
+ write(
2376
+ respond(id, {
2377
+ protocolVersion: MCP_PROTOCOL_VERSION,
2378
+ capabilities: { tools: {} },
2379
+ serverInfo: { name: "ryu-sdk-server", version: "0.0.1" }
2380
+ })
2381
+ );
2382
+ return;
2383
+ }
2384
+ if (method === "notifications/initialized") {
2385
+ return;
2386
+ }
2387
+ if (!initialized.value) {
2388
+ write(respondError(id, -32002, "Server not initialized"));
2389
+ return;
2390
+ }
2391
+ if (method === "tools/list") {
2392
+ try {
2393
+ const tools = await this.allTools();
2394
+ write(respond(id, { tools }));
2395
+ } catch (err) {
2396
+ write(respondError(id, -32603, String(err)));
2397
+ }
2398
+ return;
2399
+ }
2400
+ if (method === "tools/call") {
2401
+ await this.handleToolsCall(id, params, write);
2402
+ return;
2403
+ }
2404
+ write(respondError(id, -32601, `Method not found: ${method}`));
2405
+ }
2406
+ /** Handle a `tools/call` JSON-RPC request. */
2407
+ async handleToolsCall(id, params, write) {
2408
+ const p = params;
2409
+ const toolName = p?.name;
2410
+ const toolArgs = p?.arguments ?? {};
2411
+ if (typeof toolName !== "string") {
2412
+ write(respondError(id, -32602, "tools/call requires 'name'"));
2413
+ return;
2414
+ }
2415
+ try {
2416
+ const result = await this.handleCallTool(toolName, toolArgs);
2417
+ write(respond(id, result));
2418
+ } catch (err) {
2419
+ write(
2420
+ respond(id, {
2421
+ content: [{ type: "text", text: String(err) }],
2422
+ isError: true
2423
+ })
2424
+ );
2425
+ }
2426
+ }
2427
+ /**
2428
+ * Start reading JSON-RPC requests from the given readable stream and writing
2429
+ * responses to the given writable stream.
2430
+ *
2431
+ * Defaults to `process.stdin` / `process.stdout`. Passing explicit streams
2432
+ * lets tests inject a pair of in-process pipes.
2433
+ *
2434
+ * Resolves when the input stream ends (EOF).
2435
+ */
2436
+ serve(input = process.stdin, output = process.stdout) {
2437
+ const write = (obj) => {
2438
+ output.write(`${JSON.stringify(obj)}
2439
+ `);
2440
+ };
2441
+ const initialized = { value: false };
2442
+ return new Promise((resolve) => {
2443
+ const rl = (0, import_node_readline2.createInterface)({
2444
+ input,
2445
+ crlfDelay: Number.POSITIVE_INFINITY
2446
+ });
2447
+ rl.on("line", (rawLine) => {
2448
+ const line = rawLine.trim();
2449
+ if (!line) {
2450
+ return;
2451
+ }
2452
+ let req;
2453
+ try {
2454
+ req = JSON.parse(line);
2455
+ } catch {
2456
+ write(respondError(null, -32700, "Parse error"));
2457
+ return;
2458
+ }
2459
+ this.handleRequest(req, initialized, write).catch((err) => {
2460
+ write(respondError(req.id, -32603, String(err)));
2461
+ });
2462
+ });
2463
+ rl.on("close", resolve);
2464
+ });
2465
+ }
2466
+ };
2467
+
2468
+ // src/plugin/ryu-plugin.ts
2469
+ function toDisposable(teardown) {
2470
+ let done = false;
2471
+ return {
2472
+ dispose() {
2473
+ if (done) {
2474
+ return;
2475
+ }
2476
+ done = true;
2477
+ teardown();
2478
+ }
2479
+ };
2480
+ }
2481
+
2482
+ // src/runnable/action.ts
2483
+ function deriveAnnotations(effect, annotations) {
2484
+ const resolved = {
2485
+ ...annotations,
2486
+ readOnlyHint: annotations?.readOnlyHint ?? effect === "read",
2487
+ destructiveHint: annotations?.destructiveHint ?? effect === "mutate"
2488
+ };
2489
+ if (effect === "read" && resolved.destructiveHint) {
2490
+ throw new Error(
2491
+ "[ryu-sdk] read actions cannot set annotations.destructiveHint=true"
2492
+ );
2493
+ }
2494
+ if (effect === "mutate" && resolved.readOnlyHint) {
2495
+ throw new Error(
2496
+ "[ryu-sdk] mutate actions cannot set annotations.readOnlyHint=true"
2497
+ );
2498
+ }
2499
+ return resolved;
2500
+ }
2501
+ function actionManifestEntry(action) {
2502
+ const options = {
2503
+ action: true,
2504
+ annotations: action.annotations,
2505
+ description: action.description,
2506
+ needsApproval: action.needsApproval,
2507
+ ...action.outputSchema ? { outputSchema: action.outputSchema } : {}
2508
+ };
2509
+ return inlineToolRunnable(action, options);
2510
+ }
2511
+ function actionToManifest(action, options) {
2512
+ const grants = [.../* @__PURE__ */ new Set([...options.grants ?? [], "tool:execute"])];
2513
+ const raw = {
2514
+ id: options.id,
2515
+ name: options.name ?? action.name,
2516
+ version: options.version,
2517
+ runnables: [actionManifestEntry(action)],
2518
+ permission_grants: grants,
2519
+ activation_events: [...options.activationEvents ?? ["*"]],
2520
+ targets: [...options.targets ?? []]
2521
+ };
2522
+ const result = PluginManifestSchema.safeParse(raw);
2523
+ if (!result.success) {
2524
+ const first = result.error.issues[0];
2525
+ const field = first?.path.join(".") ?? "unknown";
2526
+ const message = first?.message ?? "validation failed";
2527
+ throw new Error(
2528
+ `[ryu-sdk] action manifest validation failed at '${field}': ${message}`
2529
+ );
2530
+ }
2531
+ return result.data;
2532
+ }
2533
+ function defineAction(options) {
2534
+ const annotations = deriveAnnotations(options.effect, options.annotations);
2535
+ const tool2 = defineTool({
2536
+ description: options.description,
2537
+ id: options.id,
2538
+ name: options.name,
2539
+ run: options.run,
2540
+ schema: options.schema
2541
+ });
2542
+ const action = {
2543
+ ...tool2,
2544
+ action: true,
2545
+ annotations,
2546
+ description: options.description,
2547
+ effect: options.effect,
2548
+ needsApproval: options.needsApproval ?? false,
2549
+ ...options.outputSchema ? { outputSchema: options.outputSchema } : {},
2550
+ toManifest(manifestOptions) {
2551
+ return actionToManifest(action, manifestOptions);
2552
+ },
2553
+ toMcpTool(context) {
2554
+ return {
2555
+ name: action.id,
2556
+ description: action.description,
2557
+ inputSchema: action.schema,
2558
+ run: (input) => action.run(input, context)
2559
+ };
2560
+ }
2561
+ };
2562
+ return action;
2563
+ }
2564
+
1784
2565
  // src/runnable/agent.ts
1785
2566
  var CAPABILITY_SLOTS = ["rag", "memory", "tts", "stt"];
1786
2567
  function lowerChatSlot(chat) {
@@ -1925,80 +2706,6 @@ function defineSkill(options) {
1925
2706
  };
1926
2707
  }
1927
2708
 
1928
- // src/runnable/tool.ts
1929
- function validateInput(input, schema, toolId) {
1930
- if (typeof input !== "object" || input === null || Array.isArray(input)) {
1931
- throw new Error(
1932
- `[ryu-sdk] Tool "${toolId}" input must be an object, got ${JSON.stringify(input)}`
1933
- );
1934
- }
1935
- const record = input;
1936
- for (const requiredKey of schema.required ?? []) {
1937
- if (!(requiredKey in record)) {
1938
- throw new Error(
1939
- `[ryu-sdk] Tool "${toolId}" input missing required field "${requiredKey}"`
1940
- );
1941
- }
1942
- }
1943
- for (const [key, prop] of Object.entries(schema.properties)) {
1944
- if (!(key in record)) {
1945
- continue;
1946
- }
1947
- const value = record[key];
1948
- if (!checkType(value, prop.type)) {
1949
- throw new Error(
1950
- `[ryu-sdk] Tool "${toolId}" input field "${key}" expected type "${prop.type}", got ${JSON.stringify(value)}`
1951
- );
1952
- }
1953
- }
1954
- }
1955
- function checkType(value, type) {
1956
- switch (type) {
1957
- case "string":
1958
- return typeof value === "string";
1959
- case "number":
1960
- case "integer":
1961
- return typeof value === "number";
1962
- case "boolean":
1963
- return typeof value === "boolean";
1964
- case "array":
1965
- return Array.isArray(value);
1966
- case "object":
1967
- return typeof value === "object" && value !== null && !Array.isArray(value);
1968
- default:
1969
- return false;
1970
- }
1971
- }
1972
- function defineTool(options) {
1973
- const { id, name, schema, run } = options;
1974
- const code = `return await (${run.toString()})(input, host);`;
1975
- return {
1976
- id,
1977
- name,
1978
- kind: "tool",
1979
- schema,
1980
- code,
1981
- run(input, ctx) {
1982
- validateInput(input, schema, id);
1983
- return run(input, ctx);
1984
- }
1985
- };
1986
- }
1987
- function inlineToolRunnable(tool2, options) {
1988
- return {
1989
- id: tool2.id,
1990
- name: tool2.name,
1991
- kind: "tool",
1992
- config: {
1993
- slug: tool2.id,
1994
- backend: "inline_deno",
1995
- code: tool2.code,
1996
- input_schema: tool2.schema,
1997
- ...options?.description ? { description: options.description } : {}
1998
- }
1999
- };
2000
- }
2001
-
2002
2709
  // src/runnable/turn-hook.ts
2003
2710
  function defineTurnHook(options) {
2004
2711
  const source = options.run.toString();
@@ -2014,6 +2721,7 @@ function definePlugin(options) {
2014
2721
  turn_hooks: options.turnHooks ?? [],
2015
2722
  hook_events: options.hookEvents ?? [],
2016
2723
  composer_controls: options.composerControls ?? [],
2724
+ chat_features: [],
2017
2725
  settings_tabs: options.settingsTabs ?? [],
2018
2726
  slash_commands: options.slashCommands ?? [],
2019
2727
  lsp_servers: options.lspServers ?? {},
@@ -2025,9 +2733,12 @@ function definePlugin(options) {
2025
2733
  sidebar_sections: [],
2026
2734
  sidebar_buttons: [],
2027
2735
  dock_panels: [],
2736
+ live_activities: [],
2028
2737
  data_categories: [],
2029
2738
  pi_extensions: [],
2030
- output_styles: []
2739
+ output_styles: [],
2740
+ message_actions: [],
2741
+ selection_actions: []
2031
2742
  };
2032
2743
  const tools = options.tools ?? [];
2033
2744
  const runnables = tools.map((t) => inlineToolRunnable(t));
@@ -2076,6 +2787,10 @@ function defineWorkflow(options) {
2076
2787
  CapabilityReqSchema,
2077
2788
  CompanionSurfaceSchema,
2078
2789
  DEFAULT_GATEWAY_URL,
2790
+ MCP_PROTOCOL_VERSION,
2791
+ McpServer,
2792
+ McpServerAuthSchema,
2793
+ McpServerDeclSchema,
2079
2794
  ModelClient,
2080
2795
  PRIMITIVE_BINDINGS,
2081
2796
  PluginBuilder,
@@ -2084,6 +2799,10 @@ function defineWorkflow(options) {
2084
2799
  RunnableKindSchema,
2085
2800
  RunnableMetaSchema,
2086
2801
  SkillBuilder,
2802
+ SlashCommandArgumentSchema,
2803
+ SlashCommandContributionSchema,
2804
+ SlashCommandCustomOptionSchema,
2805
+ SlashCommandOptionSchema,
2087
2806
  SurfaceSchema,
2088
2807
  ToolAppConfigSchema,
2089
2808
  ToolBuilder,
@@ -2093,9 +2812,11 @@ function defineWorkflow(options) {
2093
2812
  app,
2094
2813
  appToolId,
2095
2814
  assertAllowedEgressUrl,
2815
+ callTool,
2096
2816
  coreManifestJsonSchema,
2097
2817
  createAgent,
2098
2818
  createPrimitives,
2819
+ defineAction,
2099
2820
  defineAgent,
2100
2821
  defineApp,
2101
2822
  defineModel,
@@ -2106,12 +2827,15 @@ function defineWorkflow(options) {
2106
2827
  defineWorkflow,
2107
2828
  httpPrimitiveTransport,
2108
2829
  inlineToolRunnable,
2830
+ listTools,
2109
2831
  query,
2110
2832
  resolveGatewayToken,
2111
2833
  resolveGatewayUrl,
2112
2834
  ryuTool,
2113
2835
  skill,
2836
+ toDisposable,
2114
2837
  tool,
2838
+ unwrapContent,
2115
2839
  validateManifestStrict,
2116
2840
  validatePluginId,
2117
2841
  workflow