@ryuhq/sdk 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +19 -6
  2. package/dist/agent-plugin.cjs +46 -0
  3. package/dist/agent-plugin.d.cts +42 -32
  4. package/dist/agent-plugin.d.ts +42 -32
  5. package/dist/agent-plugin.js +1 -1
  6. package/dist/agent.cjs +9 -0
  7. package/dist/agent.d.cts +377 -1
  8. package/dist/agent.d.ts +377 -1
  9. package/dist/agent.js +4 -2
  10. package/dist/app-Bkw7LlCK.d.ts +129 -0
  11. package/dist/app-DNaGmLVf.d.cts +129 -0
  12. package/dist/builder.cjs +1095 -0
  13. package/dist/builder.d.cts +212 -0
  14. package/dist/builder.d.ts +212 -0
  15. package/dist/builder.js +28 -0
  16. package/dist/chunk-A3RGEPDG.js +250 -0
  17. package/dist/chunk-FZSFZOIN.js +200 -0
  18. package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
  19. package/dist/{chunk-AO2KJRDD.js → chunk-IEUQ3CDG.js} +125 -2
  20. package/dist/chunk-IKEDLLFY.js +19 -0
  21. package/dist/chunk-IOLP5FFE.js +354 -0
  22. package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
  23. package/dist/chunk-T5676WL2.js +240 -0
  24. package/dist/chunk-TLDPEGC7.js +21 -0
  25. package/dist/chunk-TXSHHZF2.js +0 -0
  26. package/dist/{chunk-MTUBUPIV.js → chunk-ULSVL7EC.js} +8 -227
  27. package/dist/chunk-VLIRNNAE.js +154 -0
  28. package/dist/chunk-W3KPP4WN.js +135 -0
  29. package/dist/cli.cjs +258 -14
  30. package/dist/cli.js +96 -16
  31. package/dist/client-D5U6ssPc.d.cts +84 -0
  32. package/dist/client-D5U6ssPc.d.ts +84 -0
  33. package/dist/index.cjs +519 -5
  34. package/dist/index.d.cts +16 -632
  35. package/dist/index.d.ts +16 -632
  36. package/dist/index.js +63 -695
  37. package/dist/manifest.cjs +129 -3
  38. package/dist/manifest.d.cts +105 -2
  39. package/dist/manifest.d.ts +105 -2
  40. package/dist/manifest.js +7 -1
  41. package/dist/mcp/client.cjs +180 -0
  42. package/dist/mcp/client.d.cts +49 -0
  43. package/dist/mcp/client.d.ts +49 -0
  44. package/dist/mcp/client.js +10 -0
  45. package/dist/mcp/server.cjs +370 -0
  46. package/dist/mcp/server.d.cts +126 -0
  47. package/dist/mcp/server.d.ts +126 -0
  48. package/dist/mcp/server.js +9 -0
  49. package/dist/mcp.cjs +376 -0
  50. package/dist/mcp.d.cts +2 -0
  51. package/dist/mcp.d.ts +2 -0
  52. package/dist/mcp.js +17 -0
  53. package/dist/model.cjs +141 -0
  54. package/dist/model.d.cts +33 -0
  55. package/dist/model.d.ts +33 -0
  56. package/dist/model.js +18 -0
  57. package/dist/plugin.cjs +46 -0
  58. package/dist/plugin.d.cts +215 -0
  59. package/dist/plugin.d.ts +215 -0
  60. package/dist/plugin.js +8 -0
  61. package/dist/runnable.cjs +1230 -0
  62. package/dist/runnable.d.cts +271 -0
  63. package/dist/runnable.d.ts +271 -0
  64. package/dist/runnable.js +28 -0
  65. package/dist/{index-B6SkaAjJ.d.ts → tool-DSx2bFx8.d.ts} +35 -458
  66. package/dist/{index-BvAB5eMk.d.cts → tool-u-VR0fLF.d.cts} +35 -458
  67. package/package.json +38 -2
  68. package/src/agent/loop.test.ts +4 -4
  69. package/src/agent/tools.ts +3 -3
  70. package/src/agent-plugin.test.ts +58 -0
  71. package/src/agent-plugin.ts +108 -35
  72. package/src/cli/dev.test.ts +26 -47
  73. package/src/cli/dev.ts +10 -2
  74. package/src/cli.ts +98 -15
  75. package/src/exports-lockstep.test.ts +92 -0
  76. package/src/generated/plugin-manifest.ts +243 -27
  77. package/src/index.ts +35 -0
  78. package/src/manifest-schema.test.ts +30 -1
  79. package/src/manifest.fixtures.test.ts +13 -3
  80. package/src/manifest.test.ts +96 -10
  81. package/src/manifest.ts +334 -187
  82. package/src/mcp/index.ts +18 -0
  83. package/src/model/index.ts +22 -0
  84. package/src/plugin/ryu-plugin.ts +82 -0
  85. package/src/runnable/app.test.ts +2 -0
  86. package/src/runnable/app.ts +5 -2
  87. package/src/runnable/index.ts +2 -0
  88. package/src/runnable/primitives.ts +57 -0
  89. package/src/runnable/tool.ts +1 -1
  90. package/src/runnable/turn-hook.ts +4 -1
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,
@@ -54,6 +58,7 @@ __export(src_exports, {
54
58
  app: () => app,
55
59
  appToolId: () => appToolId,
56
60
  assertAllowedEgressUrl: () => assertAllowedEgressUrl,
61
+ callTool: () => callTool,
57
62
  coreManifestJsonSchema: () => coreManifestJsonSchema,
58
63
  createAgent: () => createAgent,
59
64
  createPrimitives: () => createPrimitives,
@@ -67,19 +72,22 @@ __export(src_exports, {
67
72
  defineWorkflow: () => defineWorkflow,
68
73
  httpPrimitiveTransport: () => httpPrimitiveTransport,
69
74
  inlineToolRunnable: () => inlineToolRunnable,
75
+ listTools: () => listTools,
70
76
  query: () => query,
71
77
  resolveGatewayToken: () => resolveGatewayToken2,
72
78
  resolveGatewayUrl: () => resolveGatewayUrl3,
73
79
  ryuTool: () => ryuTool,
74
80
  skill: () => skill,
81
+ toDisposable: () => toDisposable,
75
82
  tool: () => tool,
83
+ unwrapContent: () => unwrapContent,
76
84
  validateManifestStrict: () => validateManifestStrict,
77
85
  validatePluginId: () => validatePluginId,
78
86
  workflow: () => workflow
79
87
  });
80
88
  module.exports = __toCommonJS(src_exports);
81
89
 
82
- // ../../node_modules/.bun/tsup@8.5.1+7a7b737fa55947b7/node_modules/tsup/assets/cjs_shims.js
90
+ // ../../node_modules/.bun/tsup@8.5.1+276f2dd5f44b8d39/node_modules/tsup/assets/cjs_shims.js
83
91
  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
92
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
85
93
 
@@ -317,6 +325,16 @@ var PRIMITIVE_BINDINGS = {
317
325
  method: "model.complete",
318
326
  grant: "hook:side-model"
319
327
  },
328
+ "background.list": {
329
+ transport: "bridge",
330
+ method: "background.list",
331
+ grant: "background:control"
332
+ },
333
+ "background.stop": {
334
+ transport: "bridge",
335
+ method: "background.stop",
336
+ grant: "background:control"
337
+ },
320
338
  // Host-direct media data-path (the host holds the node token; returns data: URLs).
321
339
  "image.generate": {
322
340
  transport: "direct",
@@ -350,6 +368,15 @@ function brokerCall(transport, cap, op, input) {
350
368
  }
351
369
  function createPrimitives(transport) {
352
370
  return {
371
+ background: {
372
+ list: (input) => transport.bridge("background.list", {
373
+ producer: input?.producer,
374
+ running_only: input?.runningOnly
375
+ }),
376
+ stop: (input) => transport.bridge("background.stop", {
377
+ process_id: input.processId
378
+ })
379
+ },
353
380
  rag: {
354
381
  retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
355
382
  embed: (input) => brokerCall(transport, "rag", "embed", input),
@@ -976,8 +1003,30 @@ var WidgetContributionSchema = import_zod.z.object({
976
1003
  /** Default display mode (`inline` | `fullscreen` | `pip`). */
977
1004
  default_display_mode: import_zod.z.string().default(DEFAULT_WIDGET_DISPLAY_MODE)
978
1005
  });
1006
+ var ChatWidgetTemplateSchema = import_zod.z.object({
1007
+ id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
1008
+ title: import_zod.z.string().min(1),
1009
+ description: import_zod.z.string().optional(),
1010
+ triggers: import_zod.z.array(import_zod.z.string()).default([]),
1011
+ examples: import_zod.z.array(import_zod.z.string()).default([]),
1012
+ backing: import_zod.z.object({
1013
+ tool_id: import_zod.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
1014
+ view_id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional()
1015
+ }),
1016
+ display_mode: import_zod.z.string().min(1),
1017
+ safe_action_ids: import_zod.z.array(import_zod.z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
1018
+ availability: import_zod.z.string().default("available")
1019
+ }).superRefine((value, ctx) => {
1020
+ const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
1021
+ if (count !== 1 && value.availability === "available") {
1022
+ ctx.addIssue({ code: "custom", path: ["backing"], message: "available templates need exactly one backing tool_id or view_id" });
1023
+ }
1024
+ if (count > 1) {
1025
+ ctx.addIssue({ code: "custom", path: ["backing"], message: "backing must declare at most one of tool_id or view_id" });
1026
+ }
1027
+ });
979
1028
  var ToolAppConfigSchema = import_zod.z.object({
980
- /** MCP tool slug this runnable wraps — the fully-qualified `<server>__<name>` id. */
1029
+ /** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
981
1030
  slug: import_zod.z.string().min(1),
982
1031
  /** The tool description the model reads when choosing it. Carried here because a
983
1032
  * packed app's manifest is the only channel (there is no `generated.rs`); Core's
@@ -1004,6 +1053,11 @@ var ContributesSchema = import_zod.z.object({
1004
1053
  * signing, leaving an app that emits events nothing is allowed to subscribe to. */
1005
1054
  hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
1006
1055
  composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1056
+ /** Chat feature descriptors whose behavior is implemented by the host shell.
1057
+ * Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
1058
+ * authoring schema prevents `ryu pack` from silently deleting a plugin's chat
1059
+ * feature declaration before signing. */
1060
+ chat_features: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1007
1061
  settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1008
1062
  slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1009
1063
  /** App widgets (Ryu Apps). Each binds a render tool id to its
@@ -1011,6 +1065,8 @@ var ContributesSchema = import_zod.z.object({
1011
1065
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
1012
1066
  * every widget an app authored here declares. */
1013
1067
  widgets: import_zod.z.array(WidgetContributionSchema).default([]),
1068
+ /** Metadata-only chat widget templates. */
1069
+ chat_widget_templates: import_zod.z.array(ChatWidgetTemplateSchema).optional(),
1014
1070
  /** App-registered sidebar sections (header + live list) and buttons (single nav
1015
1071
  * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
1016
1072
  * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
@@ -1023,6 +1079,13 @@ var ContributesSchema = import_zod.z.object({
1023
1079
  * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
1024
1080
  * strip the dock panel an app declares here. */
1025
1081
  dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1082
+ /** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
1083
+ * typed for the same reason as the surfaces above — the shell owns the
1084
+ * `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
1085
+ * without it the CLI's zod parse would strip every live activity an app
1086
+ * declares here, so a packed bundle would ship a dock that silently stays
1087
+ * empty. */
1088
+ live_activities: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
1026
1089
  /** Deletable data categories the app owns — one "Delete all X" row in Settings
1027
1090
  * → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
1028
1091
  * it the CLI's zod parse would strip the declaration before signing, and the
@@ -1060,7 +1123,10 @@ var ContributesSchema = import_zod.z.object({
1060
1123
  * bug: the styles' `.md` files are not carried by the bundle either, so there
1061
1124
  * would be no residue to notice — the plugin would install clean and contribute
1062
1125
  * nothing. */
1063
- output_styles: import_zod.z.array(OutputStyleContributionSchema).default([])
1126
+ output_styles: import_zod.z.array(OutputStyleContributionSchema).default([]),
1127
+ /** Per-message actions contributed by an enabled plugin. Kept as loose records
1128
+ * so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
1129
+ message_actions: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([])
1064
1130
  });
1065
1131
  var SetupStepSchema = import_zod.z.object({
1066
1132
  /** Card heading (e.g. the companion app name). */
@@ -1141,6 +1207,61 @@ var EnginesReqSchema = import_zod.z.object({
1141
1207
  /** Floor for the Next.js web app. */
1142
1208
  web: import_zod.z.string().optional()
1143
1209
  });
1210
+ var McpServerAuthSchema = import_zod.z.object({
1211
+ client_id: import_zod.z.string().min(1).optional(),
1212
+ type: import_zod.z.literal("oauth")
1213
+ }).strict();
1214
+ var McpServerDeclSchema = import_zod.z.object({
1215
+ args: import_zod.z.array(import_zod.z.string()).default([]),
1216
+ auth: McpServerAuthSchema.optional(),
1217
+ command: import_zod.z.string().optional(),
1218
+ command_env: import_zod.z.string().optional(),
1219
+ description: import_zod.z.string().optional(),
1220
+ enabled: import_zod.z.boolean().default(true),
1221
+ env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
1222
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
1223
+ type: import_zod.z.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"]).optional(),
1224
+ url: import_zod.z.url().optional()
1225
+ }).superRefine((server, context) => {
1226
+ if (!(server.command || server.url)) {
1227
+ context.addIssue({
1228
+ code: "custom",
1229
+ message: "an MCP server requires command or url"
1230
+ });
1231
+ }
1232
+ if (!server.auth) {
1233
+ return;
1234
+ }
1235
+ if (server.command || server.type === "stdio" || !server.url) {
1236
+ context.addIssue({
1237
+ code: "custom",
1238
+ message: "OAuth is supported only for remote HTTP MCP servers"
1239
+ });
1240
+ return;
1241
+ }
1242
+ if (Object.keys(server.headers).some(
1243
+ (name) => name.toLowerCase() === "authorization"
1244
+ )) {
1245
+ context.addIssue({
1246
+ code: "custom",
1247
+ message: "OAuth cannot be combined with a static Authorization header"
1248
+ });
1249
+ }
1250
+ const url = new URL(server.url);
1251
+ const loopback = url.hostname === "localhost" || url.hostname.startsWith("127.") || url.hostname === "[::1]" || url.hostname === "::1";
1252
+ if (url.username || url.password || url.hash) {
1253
+ context.addIssue({
1254
+ code: "custom",
1255
+ message: "OAuth MCP URLs cannot contain credentials or fragments"
1256
+ });
1257
+ }
1258
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
1259
+ context.addIssue({
1260
+ code: "custom",
1261
+ message: "OAuth MCP URLs must use HTTPS except on loopback"
1262
+ });
1263
+ }
1264
+ });
1144
1265
  var PluginManifestSchema = import_zod.z.object({
1145
1266
  /** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
1146
1267
  id: import_zod.z.string().min(1, "id is required"),
@@ -1172,6 +1293,8 @@ var PluginManifestSchema = import_zod.z.object({
1172
1293
  * Declarations only — grant enforcement is the Gateway's responsibility.
1173
1294
  */
1174
1295
  permission_grants: import_zod.z.array(import_zod.z.string()).default([]),
1296
+ /** Remote or stdio MCP servers registered by this plugin. */
1297
+ mcp_servers: import_zod.z.record(import_zod.z.string(), McpServerDeclSchema).optional(),
1175
1298
  /**
1176
1299
  * Optional Companion surface (an in-desktop overlay or sidebar panel).
1177
1300
  * Absent when the plugin has no Companion surface.
@@ -1265,10 +1388,16 @@ var PluginManifestSchema = import_zod.z.object({
1265
1388
  url: import_zod.z.string().optional()
1266
1389
  })
1267
1390
  ]).optional(),
1391
+ /** Public source repository URL (Claude/Codex `repository`). */
1392
+ repository: import_zod.z.string().url().optional(),
1393
+ /** True when the provider operates outside the local Ryu runtime. */
1394
+ external: import_zod.z.boolean().optional(),
1268
1395
  /** Project/marketing homepage — maps to the listing `website` (Claude field). */
1269
1396
  homepage: import_zod.z.string().optional(),
1270
1397
  /** Free-text search keywords (Claude field). */
1271
1398
  keywords: import_zod.z.array(import_zod.z.string()).optional(),
1399
+ /** Stable Marketplace filter labels (Ryu extension). */
1400
+ tags: import_zod.z.array(import_zod.z.string()).optional(),
1272
1401
  /** Taxonomy category beyond the runnable kinds (Claude field). */
1273
1402
  category: import_zod.z.string().optional(),
1274
1403
  /** SPDX-ish license identifier (Claude field). */
@@ -1282,6 +1411,8 @@ var PluginManifestSchema = import_zod.z.object({
1282
1411
  * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
1283
1412
  */
1284
1413
  icon: import_zod.z.string().optional(),
1414
+ /** Optional detail-page hero banner metadata forwarded to the marketplace. */
1415
+ banner: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
1285
1416
  /**
1286
1417
  * Dithered-gradient background for the card's icon square (Ryu extension),
1287
1418
  * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
@@ -1313,6 +1444,22 @@ var PluginManifestSchema = import_zod.z.object({
1313
1444
  * array of steps guiding the user through post-install configuration.
1314
1445
  */
1315
1446
  setup: import_zod.z.union([SetupStepSchema, import_zod.z.array(SetupStepSchema)]).optional()
1447
+ }).superRefine((manifest, context) => {
1448
+ const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
1449
+ (server) => server.auth?.type === "oauth"
1450
+ );
1451
+ if (!hasOAuthServer) {
1452
+ return;
1453
+ }
1454
+ for (const grant of ["mcp:server", "identity.read"]) {
1455
+ if (!manifest.permission_grants.includes(grant)) {
1456
+ context.addIssue({
1457
+ code: "custom",
1458
+ message: `OAuth MCP servers require the ${grant} permission grant`,
1459
+ path: ["permission_grants"]
1460
+ });
1461
+ }
1462
+ }
1316
1463
  });
1317
1464
  function validatePluginId(id) {
1318
1465
  loadNative().validatePluginId(id);
@@ -1336,7 +1483,7 @@ function withWidgetRenderGrant(grants, widgets) {
1336
1483
  return out;
1337
1484
  }
1338
1485
  function appToolId(server, name) {
1339
- return `${server}__${name}`;
1486
+ return `${server}.${name}`;
1340
1487
  }
1341
1488
  function defineApp(options) {
1342
1489
  const server = options.server ?? options.slug;
@@ -1376,6 +1523,7 @@ function defineApp(options) {
1376
1523
  }
1377
1524
  const contributes = {
1378
1525
  turn_hooks: [],
1526
+ chat_features: [],
1379
1527
  // This builder synthesises an app from its runnables; an app that emits
1380
1528
  // events declares them in a hand-authored `manifest.json`, same as
1381
1529
  // `lsp_servers` below.
@@ -1386,6 +1534,7 @@ function defineApp(options) {
1386
1534
  sidebar_sections: [],
1387
1535
  sidebar_buttons: [],
1388
1536
  dock_panels: [],
1537
+ live_activities: [],
1389
1538
  // Empty for the same reason as every sibling family above: this builder
1390
1539
  // synthesises `widgets` from the app's own runnables and nothing else, and
1391
1540
  // takes no `contributes` passthrough. An app that wants to declare language
@@ -1398,6 +1547,7 @@ function defineApp(options) {
1398
1547
  data_categories: [],
1399
1548
  pi_extensions: [],
1400
1549
  output_styles: [],
1550
+ message_actions: [],
1401
1551
  widgets
1402
1552
  };
1403
1553
  const raw = {
@@ -1781,6 +1931,359 @@ var AppBuilder = class {
1781
1931
  };
1782
1932
  var app = () => new AppBuilder();
1783
1933
 
1934
+ // src/mcp/client.ts
1935
+ var import_node_child_process = require("child_process");
1936
+ var import_node_readline = require("readline");
1937
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
1938
+ var RPC_TIMEOUT_MS = 6e4;
1939
+ var McpConnection = class _McpConnection {
1940
+ proc;
1941
+ nextId = 1;
1942
+ closed = false;
1943
+ waiters = [];
1944
+ constructor(proc) {
1945
+ this.proc = proc;
1946
+ }
1947
+ /** Spawn the server and complete the MCP `initialize` handshake. */
1948
+ static async connect(cmd) {
1949
+ const env2 = { ...process.env, ...cmd.env ?? {} };
1950
+ const proc = (0, import_node_child_process.spawn)(cmd.command, cmd.args ?? [], {
1951
+ stdio: ["pipe", "pipe", "pipe"],
1952
+ env: env2
1953
+ });
1954
+ if (!(proc.stdin && proc.stdout)) {
1955
+ proc.kill();
1956
+ throw new Error(`MCP server '${cmd.command}' stdin/stdout unavailable`);
1957
+ }
1958
+ if (proc.stderr) {
1959
+ proc.stderr.on("data", (chunk) => {
1960
+ process.stderr.write(`[mcp-server] ${chunk.toString()}`);
1961
+ });
1962
+ }
1963
+ const conn = new _McpConnection(proc);
1964
+ const rl = (0, import_node_readline.createInterface)({
1965
+ input: proc.stdout,
1966
+ crlfDelay: Number.POSITIVE_INFINITY
1967
+ });
1968
+ rl.on("line", (rawLine) => {
1969
+ const trimmed = rawLine.trim();
1970
+ if (!trimmed) {
1971
+ return;
1972
+ }
1973
+ let parsed;
1974
+ try {
1975
+ parsed = JSON.parse(trimmed);
1976
+ } catch {
1977
+ return;
1978
+ }
1979
+ if (parsed.id === void 0 || parsed.id === null) {
1980
+ return;
1981
+ }
1982
+ const idx = conn.waiters.findIndex((w) => w.id === parsed.id);
1983
+ if (idx === -1) {
1984
+ return;
1985
+ }
1986
+ const [waiter] = conn.waiters.splice(idx, 1);
1987
+ if (!waiter) {
1988
+ return;
1989
+ }
1990
+ clearTimeout(waiter.timer);
1991
+ if (parsed.error) {
1992
+ waiter.reject(new Error(`MCP error: ${JSON.stringify(parsed.error)}`));
1993
+ } else {
1994
+ waiter.resolve(parsed.result ?? null);
1995
+ }
1996
+ });
1997
+ rl.on("close", () => {
1998
+ for (const waiter of conn.waiters.splice(0)) {
1999
+ clearTimeout(waiter.timer);
2000
+ waiter.reject(new Error("MCP server closed the connection"));
2001
+ }
2002
+ });
2003
+ await conn.request("initialize", {
2004
+ protocolVersion: MCP_PROTOCOL_VERSION,
2005
+ capabilities: {},
2006
+ clientInfo: { name: "ryu-sdk", version: "0.0.1" }
2007
+ });
2008
+ conn.notify("notifications/initialized", {});
2009
+ return conn;
2010
+ }
2011
+ /** Send a JSON-RPC request and return the `result` field. */
2012
+ request(method, params) {
2013
+ const id = this.nextId++;
2014
+ const frame = JSON.stringify({
2015
+ jsonrpc: "2.0",
2016
+ id,
2017
+ method,
2018
+ params
2019
+ });
2020
+ this.write(frame);
2021
+ return new Promise((resolve, reject) => {
2022
+ const timer = setTimeout(
2023
+ () => reject(new Error(`MCP request '${method}' timed out`)),
2024
+ RPC_TIMEOUT_MS
2025
+ );
2026
+ this.waiters.push({ id, resolve, reject, timer });
2027
+ });
2028
+ }
2029
+ /** Send a JSON-RPC notification (no response expected). */
2030
+ notify(method, params) {
2031
+ const frame = JSON.stringify({ jsonrpc: "2.0", method, params });
2032
+ this.write(frame);
2033
+ }
2034
+ write(frame) {
2035
+ if (this.closed) {
2036
+ return;
2037
+ }
2038
+ this.proc.stdin?.write(`${frame}
2039
+ `);
2040
+ }
2041
+ /** Graceful shutdown: close stdin, then kill. */
2042
+ async shutdown() {
2043
+ this.closed = true;
2044
+ this.proc.stdin?.end();
2045
+ await new Promise((resolve) => {
2046
+ this.proc.once("exit", () => resolve());
2047
+ this.proc.kill();
2048
+ setTimeout(resolve, 500);
2049
+ });
2050
+ }
2051
+ };
2052
+ async function listTools(cmd) {
2053
+ const conn = await McpConnection.connect(cmd);
2054
+ let result;
2055
+ try {
2056
+ result = await conn.request("tools/list", {});
2057
+ } finally {
2058
+ await conn.shutdown();
2059
+ }
2060
+ const tools = result?.tools ?? [];
2061
+ return tools.map((t) => {
2062
+ const tool2 = t;
2063
+ const name = tool2.name;
2064
+ if (typeof name !== "string") {
2065
+ return null;
2066
+ }
2067
+ return {
2068
+ name,
2069
+ description: typeof tool2.description === "string" ? tool2.description : void 0,
2070
+ inputSchema: tool2.inputSchema
2071
+ };
2072
+ }).filter((t) => t !== null);
2073
+ }
2074
+ async function callTool(cmd, tool2, args) {
2075
+ const conn = await McpConnection.connect(cmd);
2076
+ try {
2077
+ return await conn.request("tools/call", { name: tool2, arguments: args });
2078
+ } finally {
2079
+ await conn.shutdown();
2080
+ }
2081
+ }
2082
+
2083
+ // src/mcp/server.ts
2084
+ var import_node_readline2 = require("readline");
2085
+ function respond(id, result) {
2086
+ return { jsonrpc: "2.0", id: id ?? null, result };
2087
+ }
2088
+ function respondError(id, code, message) {
2089
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
2090
+ }
2091
+ function wrapContent(value) {
2092
+ return {
2093
+ content: [
2094
+ {
2095
+ type: "text",
2096
+ text: typeof value === "string" ? value : JSON.stringify(value)
2097
+ }
2098
+ ]
2099
+ };
2100
+ }
2101
+ function unwrapContent(raw) {
2102
+ const r = raw;
2103
+ const text = r?.content?.[0]?.text;
2104
+ if (text === void 0) {
2105
+ return raw;
2106
+ }
2107
+ try {
2108
+ return JSON.parse(text);
2109
+ } catch {
2110
+ return text;
2111
+ }
2112
+ }
2113
+ var McpServer = class {
2114
+ runnables = /* @__PURE__ */ new Map();
2115
+ passthroughs = [];
2116
+ /**
2117
+ * Register an SDK Runnable as an MCP tool.
2118
+ * Returns `this` for chaining.
2119
+ */
2120
+ register(runnable) {
2121
+ this.runnables.set(runnable.name, runnable);
2122
+ return this;
2123
+ }
2124
+ /**
2125
+ * Register a passthrough to an external MCP stdio server (e.g. Ghost or
2126
+ * Shadow). Tools from that server are fetched lazily and re-advertised.
2127
+ * Returns `this` for chaining.
2128
+ */
2129
+ passthrough(registration) {
2130
+ this.passthroughs.push(registration);
2131
+ return this;
2132
+ }
2133
+ /** Fetch all tools: local Runnables + passthrough tools. */
2134
+ async allTools() {
2135
+ const local = [...this.runnables.values()].map((r) => ({
2136
+ name: r.name,
2137
+ description: r.description,
2138
+ inputSchema: r.inputSchema
2139
+ }));
2140
+ const remote = [];
2141
+ for (const pt of this.passthroughs) {
2142
+ try {
2143
+ const tools = await listTools(pt.command);
2144
+ remote.push(...tools);
2145
+ } catch (err) {
2146
+ process.stderr.write(
2147
+ `[mcp-server] passthrough '${pt.label}' list_tools failed: ${err}
2148
+ `
2149
+ );
2150
+ }
2151
+ }
2152
+ return [...local, ...remote];
2153
+ }
2154
+ /** Handle a `tools/call` request. */
2155
+ async handleCallTool(name, args) {
2156
+ const local = this.runnables.get(name);
2157
+ if (local) {
2158
+ const result = await local.run(args);
2159
+ return wrapContent(result);
2160
+ }
2161
+ for (const pt of this.passthroughs) {
2162
+ try {
2163
+ const tools = await listTools(pt.command);
2164
+ if (tools.some((t) => t.name === name)) {
2165
+ return await callTool(pt.command, name, args);
2166
+ }
2167
+ } catch {
2168
+ }
2169
+ }
2170
+ throw new Error(`Unknown tool: ${name}`);
2171
+ }
2172
+ /**
2173
+ * Handle a single parsed JSON-RPC request line. Returns the response to write
2174
+ * (or null for notifications that require no response).
2175
+ */
2176
+ async handleRequest(req, initialized, write) {
2177
+ const { id, method, params } = req;
2178
+ if (method === "initialize") {
2179
+ initialized.value = true;
2180
+ write(
2181
+ respond(id, {
2182
+ protocolVersion: MCP_PROTOCOL_VERSION,
2183
+ capabilities: { tools: {} },
2184
+ serverInfo: { name: "ryu-sdk-server", version: "0.0.1" }
2185
+ })
2186
+ );
2187
+ return;
2188
+ }
2189
+ if (method === "notifications/initialized") {
2190
+ return;
2191
+ }
2192
+ if (!initialized.value) {
2193
+ write(respondError(id, -32002, "Server not initialized"));
2194
+ return;
2195
+ }
2196
+ if (method === "tools/list") {
2197
+ try {
2198
+ const tools = await this.allTools();
2199
+ write(respond(id, { tools }));
2200
+ } catch (err) {
2201
+ write(respondError(id, -32603, String(err)));
2202
+ }
2203
+ return;
2204
+ }
2205
+ if (method === "tools/call") {
2206
+ await this.handleToolsCall(id, params, write);
2207
+ return;
2208
+ }
2209
+ write(respondError(id, -32601, `Method not found: ${method}`));
2210
+ }
2211
+ /** Handle a `tools/call` JSON-RPC request. */
2212
+ async handleToolsCall(id, params, write) {
2213
+ const p = params;
2214
+ const toolName = p?.name;
2215
+ const toolArgs = p?.arguments ?? {};
2216
+ if (typeof toolName !== "string") {
2217
+ write(respondError(id, -32602, "tools/call requires 'name'"));
2218
+ return;
2219
+ }
2220
+ try {
2221
+ const result = await this.handleCallTool(toolName, toolArgs);
2222
+ write(respond(id, result));
2223
+ } catch (err) {
2224
+ write(
2225
+ respond(id, {
2226
+ content: [{ type: "text", text: String(err) }],
2227
+ isError: true
2228
+ })
2229
+ );
2230
+ }
2231
+ }
2232
+ /**
2233
+ * Start reading JSON-RPC requests from the given readable stream and writing
2234
+ * responses to the given writable stream.
2235
+ *
2236
+ * Defaults to `process.stdin` / `process.stdout`. Passing explicit streams
2237
+ * lets tests inject a pair of in-process pipes.
2238
+ *
2239
+ * Resolves when the input stream ends (EOF).
2240
+ */
2241
+ serve(input = process.stdin, output = process.stdout) {
2242
+ const write = (obj) => {
2243
+ output.write(`${JSON.stringify(obj)}
2244
+ `);
2245
+ };
2246
+ const initialized = { value: false };
2247
+ return new Promise((resolve) => {
2248
+ const rl = (0, import_node_readline2.createInterface)({
2249
+ input,
2250
+ crlfDelay: Number.POSITIVE_INFINITY
2251
+ });
2252
+ rl.on("line", (rawLine) => {
2253
+ const line = rawLine.trim();
2254
+ if (!line) {
2255
+ return;
2256
+ }
2257
+ let req;
2258
+ try {
2259
+ req = JSON.parse(line);
2260
+ } catch {
2261
+ write(respondError(null, -32700, "Parse error"));
2262
+ return;
2263
+ }
2264
+ this.handleRequest(req, initialized, write).catch((err) => {
2265
+ write(respondError(req.id, -32603, String(err)));
2266
+ });
2267
+ });
2268
+ rl.on("close", resolve);
2269
+ });
2270
+ }
2271
+ };
2272
+
2273
+ // src/plugin/ryu-plugin.ts
2274
+ function toDisposable(teardown) {
2275
+ let done = false;
2276
+ return {
2277
+ dispose() {
2278
+ if (done) {
2279
+ return;
2280
+ }
2281
+ done = true;
2282
+ teardown();
2283
+ }
2284
+ };
2285
+ }
2286
+
1784
2287
  // src/runnable/agent.ts
1785
2288
  var CAPABILITY_SLOTS = ["rag", "memory", "tts", "stt"];
1786
2289
  function lowerChatSlot(chat) {
@@ -2014,6 +2517,7 @@ function definePlugin(options) {
2014
2517
  turn_hooks: options.turnHooks ?? [],
2015
2518
  hook_events: options.hookEvents ?? [],
2016
2519
  composer_controls: options.composerControls ?? [],
2520
+ chat_features: [],
2017
2521
  settings_tabs: options.settingsTabs ?? [],
2018
2522
  slash_commands: options.slashCommands ?? [],
2019
2523
  lsp_servers: options.lspServers ?? {},
@@ -2025,9 +2529,11 @@ function definePlugin(options) {
2025
2529
  sidebar_sections: [],
2026
2530
  sidebar_buttons: [],
2027
2531
  dock_panels: [],
2532
+ live_activities: [],
2028
2533
  data_categories: [],
2029
2534
  pi_extensions: [],
2030
- output_styles: []
2535
+ output_styles: [],
2536
+ message_actions: []
2031
2537
  };
2032
2538
  const tools = options.tools ?? [];
2033
2539
  const runnables = tools.map((t) => inlineToolRunnable(t));
@@ -2076,6 +2582,10 @@ function defineWorkflow(options) {
2076
2582
  CapabilityReqSchema,
2077
2583
  CompanionSurfaceSchema,
2078
2584
  DEFAULT_GATEWAY_URL,
2585
+ MCP_PROTOCOL_VERSION,
2586
+ McpServer,
2587
+ McpServerAuthSchema,
2588
+ McpServerDeclSchema,
2079
2589
  ModelClient,
2080
2590
  PRIMITIVE_BINDINGS,
2081
2591
  PluginBuilder,
@@ -2093,6 +2603,7 @@ function defineWorkflow(options) {
2093
2603
  app,
2094
2604
  appToolId,
2095
2605
  assertAllowedEgressUrl,
2606
+ callTool,
2096
2607
  coreManifestJsonSchema,
2097
2608
  createAgent,
2098
2609
  createPrimitives,
@@ -2106,12 +2617,15 @@ function defineWorkflow(options) {
2106
2617
  defineWorkflow,
2107
2618
  httpPrimitiveTransport,
2108
2619
  inlineToolRunnable,
2620
+ listTools,
2109
2621
  query,
2110
2622
  resolveGatewayToken,
2111
2623
  resolveGatewayUrl,
2112
2624
  ryuTool,
2113
2625
  skill,
2626
+ toDisposable,
2114
2627
  tool,
2628
+ unwrapContent,
2115
2629
  validateManifestStrict,
2116
2630
  validatePluginId,
2117
2631
  workflow