@ryuhq/sdk 0.1.14 → 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
@@ -32,6 +32,17 @@ export type SettingsFieldOption =
32
32
  */
33
33
  value: string;
34
34
  };
35
+ /**
36
+ * Authentication Ryu performs on behalf of the user for a remote MCP server.
37
+ *
38
+ * `deny_unknown_fields` is a security boundary: a publisher cannot smuggle a
39
+ * client secret, token endpoint, redirect URI, token or scope list into a signed
40
+ * manifest and have an older Core silently ignore it.
41
+ */
42
+ export type McpServerAuthDecl = {
43
+ client_id?: string | null;
44
+ type: "oauth";
45
+ };
35
46
  /**
36
47
  * What a capability provider acts on — see [`ProvidesEntry::target`].
37
48
  *
@@ -192,6 +203,13 @@ export interface PluginManifest {
192
203
  * Prompt-chip examples (contract key `examplePrompts`; Ryu extension).
193
204
  */
194
205
  examplePrompts?: string[];
206
+ /**
207
+ * Whether the provider operates outside the local Ryu runtime, for example a
208
+ * hosted browser or remote MCP service. This is a presentation/provenance
209
+ * flag, not a permission grant; the actual remote endpoint remains declared
210
+ * under `mcp_servers` and is governed by the Gateway.
211
+ */
212
+ external?: boolean;
195
213
  /**
196
214
  * Hide this listing from the Store without uninstalling or disabling it.
197
215
  *
@@ -403,6 +421,12 @@ export interface PluginManifest {
403
421
  * `sidecar`/`route` exists (like `contributes`).
404
422
  */
405
423
  provides?: ProvidesEntry[];
424
+ /**
425
+ * Public source repository URL (Claude/Codex `repository`). This is listing
426
+ * metadata only; install and signature resolution still use the catalog's
427
+ * authoritative source fields.
428
+ */
429
+ repository?: string | null;
406
430
  /**
407
431
  * **Plugin-to-plugin dependencies** — the other plugins this one needs (the
408
432
  * npm-shaped edge that lets the app decompose into a kernel + features).
@@ -483,6 +507,14 @@ export interface PluginManifest {
483
507
  * Short one-line tagline shown under the name (Ryu extension).
484
508
  */
485
509
  tagline?: string | null;
510
+ /**
511
+ * Curated store-filter tags (Ryu extension). Unlike `keywords`, which is
512
+ * publisher search vocabulary, these stable labels are the values the
513
+ * Marketplace filter exposes. Keeping both fields lets Claude/Codex
514
+ * manifests round-trip their native `keywords` while Ryu authors opt into
515
+ * a deliberately bounded taxonomy.
516
+ */
517
+ tags?: string[];
486
518
  /**
487
519
  * Host surfaces this plugin runs on (desktop / island / mobile / …).
488
520
  *
@@ -545,8 +577,8 @@ export interface CompanionSurface {
545
577
  * Most surfaces added since are **self-contained**: they carry their own payload
546
578
  * and reference no runnable at all (`widgets`, `views`, `dock_panels`,
547
579
  * `sidebar_sections`, `sidebar_buttons`, `settings_tabs`, `composer_controls`,
548
- * `slash_commands`, `turn_hooks`, `tool_filters`, `lsp_servers`,
549
- * `message_actions`, `context_menu_items`).
580
+ * `chat_features`, `slash_commands`, `turn_hooks`, `tool_filters`, `lsp_servers`,
581
+ * `message_actions`, `context_menu_items`, `agent_edit_panels`).
550
582
  *
551
583
  * # Extending
552
584
  *
@@ -563,7 +595,8 @@ export interface CompanionSurface {
563
595
  * (`tool_filters`, `turn_hooks`, `widgets`, `lsp_servers`) it gets a fully typed
564
596
  * struct, because a key Core does not know is by construction a key Core cannot
565
597
  * act on. If a client shell renders it (`views`, `dock_panels`,
566
- * `sidebar_sections`, `settings_tabs`, `composer_controls`) it stays opaque
598
+ * `sidebar_sections`, `settings_tabs`, `composer_controls`,
599
+ * `agent_edit_panels`) it stays opaque
567
600
  * JSON, because deserializing into a struct here would DROP any key this Core
568
601
  * build does not know about and a newer desktop would lose exactly the fields it
569
602
  * was shipped to render.
@@ -573,10 +606,34 @@ export interface CompanionSurface {
573
606
  * they are gathered at their own consumption site instead.
574
607
  */
575
608
  export interface Contributes {
609
+ /**
610
+ * Client-rendered panels for the agent edit page. Entries are deliberately
611
+ * opaque and self-contained: the desktop owns the panel vocabulary, while
612
+ * Core only stores, tags, and forwards the declaration through the plugin
613
+ * contributions endpoint. This lets a newer desktop add an agent-edit
614
+ * panel type without requiring every Core node to learn that type first.
615
+ * These entries name no runnable ids and therefore are intentionally absent
616
+ * from [`Contributes::referenced_ids`].
617
+ */
618
+ agent_edit_panels?: unknown[];
576
619
  /**
577
620
  * Agents the plugin contributes (referenced by runnable id).
578
621
  */
579
622
  agents?: ContributionId[];
623
+ /**
624
+ * Declarative chat feature descriptors. These are opaque, client-rendered
625
+ * declarations used to feature-detect chat behaviors whose implementation
626
+ * remains in the host (for example side chats or temporary chats). The
627
+ * owning plugin id is stamped by Core when the contribution endpoint serves
628
+ * them, so a disabled plugin removes both the descriptor and its UI affordance.
629
+ */
630
+ chat_features?: unknown[];
631
+ /**
632
+ * Metadata-only chat widget templates. Unlike [`Contributes::widgets`], this
633
+ * catalog is safe to show before a turn runs: it names a host-owned prompt
634
+ * affordance and a tool/view binding, never HTML, React, or capabilities.
635
+ */
636
+ chat_widget_templates?: ChatWidgetTemplateContribution[] | null;
580
637
  /**
581
638
  * Command-palette commands the plugin contributes (referenced by runnable id).
582
639
  */
@@ -737,6 +794,27 @@ export interface Contributes {
737
794
  * **self-contained** and stays out of [`Contributes::referenced_ids`].
738
795
  */
739
796
  hook_events?: HookEventContribution[];
797
+ /**
798
+ * **Live activities** the plugin contributes — small, always-live status cards
799
+ * the desktop shell's "Dynamic Island" dock (empty-shell launchpad + sidebar)
800
+ * renders for something in progress: an agent run, a download, a pending
801
+ * approval, a recording. The desktop half of the same status vocabulary the
802
+ * mobile `AgentActivity` uses, so one mental model spans devices.
803
+ *
804
+ * Each entry is a [`LiveActivityContribution`]: a typed envelope (`id`/`title`/
805
+ * `icon`/`accent`/`order`) around an **opaque** `spec` payload the desktop
806
+ * renderer interprets. Like a [`Contributes::sidebar_sections`] entry it carries
807
+ * a `ViewSource` (a Core `/api/` path the shell polls) and a field-map; unlike a
808
+ * section it maps response ROWS to live-activity cards (status/progress/target)
809
+ * instead of nav rows. The app returns DATA — never code — so a live activity
810
+ * cannot be made ugly and needs zero sidecar code.
811
+ *
812
+ * Self-contained (it names no runnable), so it stays out of
813
+ * [`Contributes::referenced_ids`]; the `spec` stays opaque to Core so a new
814
+ * activity capability is a renderer change, not a Core change. Served + tagged
815
+ * with the owning `plugin` id at `GET /api/plugins/contributions`.
816
+ */
817
+ live_activities?: LiveActivityContribution[];
740
818
  /**
741
819
  * **Language servers** the plugin declares, keyed by server name — the
742
820
  * agent-neutral mirror of Claude Code's `.lsp.json` / `lspServers`, so a config
@@ -915,7 +993,7 @@ export interface Contributes {
915
993
  * ([`Contributes::sidebar_sections`]) and "what nav rows exist"
916
994
  * ([`Contributes::sidebar_buttons`]): **how the sidebar as a whole is
917
995
  * arranged**. The shell ships three modes of its own (every section stacked;
918
- * every section as a tab; Agent mode, which is the pair Sessions ⇄ Agents), and
996
+ * every section as a tab; Bot mode, which is the pair Sessions ⇄ Agents), and
919
997
  * before this member an app could add a section to that list but could not
920
998
  * propose an arrangement — so a plugin wanting the Grok/Hermes bot-mode posture
921
999
  * had to ask for a shell change. See [`SidebarModeContribution`].
@@ -1058,6 +1136,36 @@ export interface ContributionId {
1058
1136
  */
1059
1137
  title?: string | null;
1060
1138
  }
1139
+ /**
1140
+ * A metadata-only entry the host may offer as a compact chat affordance.
1141
+ *
1142
+ * `backing` selects exactly one existing tool or view by id. The host owns the
1143
+ * eventual rendering and action dispatch; `safe_action_ids` are identifiers only
1144
+ * and are never executable payloads.
1145
+ */
1146
+ export interface ChatWidgetTemplateContribution {
1147
+ /**
1148
+ * `available`, `coming-soon`, or `unavailable`; unknown values are forwarded
1149
+ * for forward compatibility and are not offered by older shells.
1150
+ */
1151
+ availability?: string;
1152
+ backing: ChatWidgetTemplateBacking;
1153
+ description?: string | null;
1154
+ /**
1155
+ * Open vocabulary so newer shells can add display modes without breaking
1156
+ * older Core nodes; the desktop simply ignores modes it does not know.
1157
+ */
1158
+ display_mode: string;
1159
+ examples?: string[];
1160
+ id: string;
1161
+ safe_action_ids?: string[];
1162
+ title: string;
1163
+ triggers?: string[];
1164
+ }
1165
+ export interface ChatWidgetTemplateBacking {
1166
+ tool_id?: string | null;
1167
+ view_id?: string | null;
1168
+ }
1061
1169
  /**
1062
1170
  * One context-menu row a plugin contributes (see
1063
1171
  * [`Contributes::context_menu_items`]).
@@ -1302,6 +1410,50 @@ export interface HookEventContribution {
1302
1410
  */
1303
1411
  title: string;
1304
1412
  }
1413
+ /**
1414
+ * One app-registered **live activity** — a small, always-live status card the
1415
+ * desktop's "Dynamic Island" dock (empty-shell launchpad + sidebar) renders for
1416
+ * something in progress: an agent run, a download, a pending approval, a
1417
+ * recording. The desktop half of the status vocabulary the mobile `AgentActivity`
1418
+ * uses (`running` / `waiting` / `review` / `done` / `error`), so one mental model
1419
+ * spans devices.
1420
+ *
1421
+ * A typed envelope around an opaque `spec` (the `LiveActivitySpec` in
1422
+ * `@ryu/app-host/live-activity`: a `ViewSource` for the live rows, a field-map
1423
+ * from rows to card fields, and a `target` route template). Core stores it
1424
+ * verbatim and tags it with the owning `plugin` id; the `spec` stays opaque so a
1425
+ * new activity capability is a renderer change, not a Core change.
1426
+ */
1427
+ export interface LiveActivityContribution {
1428
+ /**
1429
+ * Optional accent colour hint (any CSS color) tinting the card.
1430
+ */
1431
+ accent?: string | null;
1432
+ /**
1433
+ * Optional glyph id resolved by the shell's Icon primitive (Iconify/Hugeicons).
1434
+ */
1435
+ icon?: string | null;
1436
+ /**
1437
+ * Stable id for this activity within the plugin (namespaced into the shell's
1438
+ * dock identity as `plugin:<pluginId>:<id>:<rowId>`).
1439
+ */
1440
+ id: string;
1441
+ /**
1442
+ * Optional placement hint among the dock's activities (lower = first).
1443
+ */
1444
+ order?: number | null;
1445
+ /**
1446
+ * The opaque activity spec (source/map/target). Interpreted by the desktop
1447
+ * renderer, never by Core. Absent = a header-only activity (renders nothing).
1448
+ */
1449
+ spec?: {
1450
+ [k: string]: unknown;
1451
+ };
1452
+ /**
1453
+ * Human-facing title shown on the dock card (falls back to the row title).
1454
+ */
1455
+ title: string;
1456
+ }
1305
1457
  /**
1306
1458
  * One **language server** a plugin declares (see [`Contributes::lsp_servers`]).
1307
1459
  *
@@ -1746,7 +1898,7 @@ export interface SidebarModeContribution {
1746
1898
  /**
1747
1899
  * Which of `sections` the mode opens on. Absent (or naming a section not in
1748
1900
  * `sections`) = the first one. This is the field that makes a mode an opinion
1749
- * rather than a filter: the shell's own Agent mode lists Sessions first but
1901
+ * rather than a filter: the shell's own Bot mode lists Sessions first but
1750
1902
  * opens on Agents, because the roster is what the mode is for.
1751
1903
  */
1752
1904
  default_section?: string | null;
@@ -1926,10 +2078,10 @@ export interface ThemePreview {
1926
2078
  * One **tool filter**: a fully-qualified tool id a plugin wants withheld from the
1927
2079
  * model's offered tool list.
1928
2080
  *
1929
- * Tools are namespaced `<server>__<tool>` (e.g. `browser__navigate`), so `tool`
2081
+ * Tools are namespaced `<server>.<tool>` (e.g. `browser.navigate`), so `tool`
1930
2082
  * must carry the namespace — a bare `navigate` would be ambiguous across servers
1931
2083
  * and is rejected at load. A **trailing** `*` is a prefix wildcard, which is how a
1932
- * plugin withholds a whole server (`shadow__*`); it is the only wildcard position
2084
+ * plugin withholds a whole server (`shadow.*`); it is the only wildcard position
1933
2085
  * allowed, because an interior or leading `*` invites a pattern that silently
1934
2086
  * matches far more than the author pictured.
1935
2087
  *
@@ -1947,7 +2099,7 @@ export interface ToolFilterContribution {
1947
2099
  */
1948
2100
  reason?: string | null;
1949
2101
  /**
1950
- * Fully-qualified tool id (`<server>__<tool>`), optionally ending in `*` to
2102
+ * Fully-qualified tool id (`<server>.<tool>`), optionally ending in `*` to
1951
2103
  * hide every tool whose id starts with the preceding prefix.
1952
2104
  */
1953
2105
  tool: string;
@@ -1999,6 +2151,11 @@ export interface TurnHookContribution {
1999
2151
  * The turn boundary this hook fires on. Today only `"post_assistant_turn"`.
2000
2152
  */
2001
2153
  on: string;
2154
+ /**
2155
+ * Higher-priority hooks run first within a phase. Ties are resolved by
2156
+ * plugin id and hook id, which makes first-writer-wins directives stable.
2157
+ */
2158
+ priority?: number;
2002
2159
  }
2003
2160
  /**
2004
2161
  * A declarative pre-gate for a [`TurnHookContribution`]. The conditions are
@@ -2169,26 +2326,41 @@ export interface EnginesReq {
2169
2326
  web?: string | null;
2170
2327
  }
2171
2328
  /**
2172
- * One declarative **stdio MCP server** a plugin registers (see
2173
- * [`PluginManifest::mcp_servers`]).
2329
+ * One declarative **MCP server** a plugin registers (see
2330
+ * [`PluginManifest::mcp_servers`]) — either a stdio command to spawn or a remote
2331
+ * HTTP endpoint to call.
2174
2332
  *
2175
2333
  * This is the manifest-side, dependency-free mirror of Core's runtime
2176
2334
  * `McpServerConfig`: pure data (schemars/serde only) so it can live in
2177
2335
  * kernel-contracts, with Core lowering it into its registry type on enable. A
2178
- * server is spawned per request as `command args…` (stdio); `command_env` lets
2336
+ * stdio server is spawned per request as `command args…`; `command_env` lets
2179
2337
  * the manifest name an env var Core resolves to an absolute binary path
2180
2338
  * (e.g. `RYU_GHOST_BIN`) so a downloaded `~/.ryu/bin` binary can override the
2181
- * bare `command`.
2339
+ * bare `command`. An HTTP server names a [`url`](McpServerDecl::url) instead and
2340
+ * spawns nothing at all.
2341
+ *
2342
+ * The field names mirror the `mcp.json` dialect users already paste from Cursor
2343
+ * and Claude Desktop (`type` / `url` / `headers`) precisely so a manifest and a
2344
+ * hand-written config entry are the same shape. Static API-key auth may live in
2345
+ * [`headers`](McpServerDecl::headers). User-delegated OAuth is declared through
2346
+ * [`auth`](McpServerDecl::auth), and Core owns the resulting token lifecycle.
2182
2347
  */
2183
2348
  export interface McpServerDecl {
2184
2349
  /**
2185
2350
  * Arguments passed to the command.
2186
2351
  */
2187
2352
  args?: string[];
2353
+ /**
2354
+ * Core-owned OAuth for this remote MCP server. The manifest may name only an
2355
+ * optional public client id; discovery, PKCE, tokens and redirect URIs are
2356
+ * intentionally outside the publisher-controlled manifest.
2357
+ */
2358
+ auth?: McpServerAuthDecl | null;
2188
2359
  /**
2189
2360
  * Executable to spawn (e.g. `npx`, an absolute path, or a `~/.ryu/bin` name).
2361
+ * Absent for a remote (`url`) server.
2190
2362
  */
2191
- command: string;
2363
+ command?: string | null;
2192
2364
  /**
2193
2365
  * Optional env var whose value, when set, OVERRIDES [`command`] with an
2194
2366
  * absolute binary path. Lets a plugin ship a bare `command` that Core repoints
@@ -2212,6 +2384,22 @@ export interface McpServerDecl {
2212
2384
  env?: {
2213
2385
  [k: string]: string;
2214
2386
  };
2387
+ /**
2388
+ * Request headers sent with every call to a remote server (auth lives here).
2389
+ */
2390
+ headers?: {
2391
+ [k: string]: string;
2392
+ };
2393
+ /**
2394
+ * Transport: `stdio`, `http`, `streamable-http`, or `sse`. Absent ⇒ inferred
2395
+ * from whichever of `command`/`url` is present. `http` and `streamable-http`
2396
+ * select Streamable HTTP; `sse` selects the legacy HTTP+SSE transport.
2397
+ */
2398
+ type?: string | null;
2399
+ /**
2400
+ * Endpoint URL for a remote (HTTP) server. Absent for a stdio server.
2401
+ */
2402
+ url?: string | null;
2215
2403
  }
2216
2404
  /**
2217
2405
  * One entry in an app's **user-facing permission vocabulary** — a level an admin
@@ -2413,7 +2601,7 @@ export interface ProvidesEntry {
2413
2601
  * model-visible tool surface stable across a swap.
2414
2602
  *
2415
2603
  * The key is a canonical verb from the host's capability verb table (e.g.
2416
- * `"web__search"`); the value names the provider's own registered tool plus the
2604
+ * `"web.search"`); the value names the provider's own registered tool plus the
2417
2605
  * argument/response mapping into the canonical shape. A provider that omits a
2418
2606
  * verb simply does not serve it — the facade reports the verb unavailable
2419
2607
  * rather than guessing.
@@ -2431,7 +2619,7 @@ export interface ProvidesEntry {
2431
2619
  /**
2432
2620
  * How one capability **verb** maps onto a concrete provider tool.
2433
2621
  *
2434
- * The facade tool (`web__search`, `browser__navigate`, …) is registered by the host
2622
+ * The facade tool (`web.search`, `browser.navigate`, …) is registered by the host
2435
2623
  * from its canonical verb table; at call time it resolves the capability's bound
2436
2624
  * provider, reads this binding, renames the arguments, re-enters tool dispatch on
2437
2625
  * [`Self::tool`], and maps the response back. Swapping the provider therefore
@@ -2464,7 +2652,7 @@ export interface CapabilityToolBinding {
2464
2652
  * **canonical** argument name (before any rename).
2465
2653
  *
2466
2654
  * Exists because canonical schemas describe what agents may ask for, while
2467
- * providers differ in what they accept: `web__search.limit` allows up to 100,
2655
+ * providers differ in what they accept: `web.search.limit` allows up to 100,
2468
2656
  * but Brave's `count` maxes at 20. Without this, selecting Brave turns a
2469
2657
  * perfectly valid `limit: 50` into an upstream 4xx — the swap stops being
2470
2658
  * transparent, which is the entire point of the facade. Clamping is the right
@@ -2514,8 +2702,8 @@ export interface CapabilityToolBinding {
2514
2702
  */
2515
2703
  response?: CapabilityResponseMap | null;
2516
2704
  /**
2517
- * The provider's own fully-qualified tool id (e.g. `"exa__search"`,
2518
- * `"app__firecrawl_scrape"`) that implements this verb.
2705
+ * The provider's own fully-qualified tool id (e.g. `"exa.search"`,
2706
+ * `"app.firecrawl_scrape"`) that implements this verb.
2519
2707
  */
2520
2708
  tool: string;
2521
2709
  }
@@ -2801,17 +2989,17 @@ export interface ExternalRuntimeConfig {
2801
2989
  }
2802
2990
  /**
2803
2991
  * A single asset an external runtime needs, fetched before first run. Either a
2804
- * direct https URL or an `hf:<owner>/<repo>/<path>` reference; `dest_under_ryu`
2805
- * is the relative directory beneath `~/.ryu` where it lands (Core-owned) — the
2806
- * filename is derived from the source's last path segment.
2992
+ * direct https URL or an `hf:<owner>/<repo>/<path>` reference; the destination
2993
+ * is a relative directory beneath the plugin's dedicated runtime `assets/`
2994
+ * directory — the filename is derived from the source's last path segment.
2807
2995
  */
2808
2996
  export interface AssetSpec {
2809
2997
  /**
2810
2998
  * Destination directory relative to `~/.ryu` (e.g. `"models/hf"`); the
2811
- * fetched file lands at `~/.ryu/<dest_under_ryu>/<filename>`. Must be a
2812
- * traversal-safe relative path (no `..`, not absolute).
2999
+ * fetched file lands at `<runtime>/assets/<dest_under_runtime>/<filename>`. Must
3000
+ * be a traversal-safe relative path (no `..`, not absolute).
2813
3001
  */
2814
- dest_under_ryu: string;
3002
+ dest_under_runtime: string;
2815
3003
  /**
2816
3004
  * Optional SHA-256 for checksum verification (direct-URL assets).
2817
3005
  */
@@ -2908,6 +3096,16 @@ export interface SidecarSpec {
2908
3096
  * until the first proxy/broker hit wakes it on demand; a bounded health-wait
2909
3097
  * warms it before the request is forwarded. `false` (the default) keeps the
2910
3098
  * eager behaviour every existing manifest has: started at enable. Additive.
3099
+ *
3100
+ * **Ignored when [`provides_provider`] is set.** Such a sidecar is always started
3101
+ * eagerly, because the two declarations are mutually exclusive by construction: a
3102
+ * lazy sidecar's only wake trigger is a proxy/broker hit, while a provider's only
3103
+ * client is Pi, which dials the registered `baseUrl` directly and never traverses
3104
+ * the proxy. Nothing could ever wake it, and Core's boot purge of stale
3105
+ * sidecar-owned provider entries would then leave the provider permanently dead.
3106
+ * The coercion is logged at `info`; the manifest is NOT rejected.
3107
+ *
3108
+ * [`provides_provider`]: SidecarSpec::provides_provider
2911
3109
  */
2912
3110
  lazy?: boolean;
2913
3111
  /**
@@ -2918,9 +3116,16 @@ export interface SidecarSpec {
2918
3116
  name: string;
2919
3117
  /**
2920
3118
  * TCP port the process's HTTP server binds to, used to build the health-check
2921
- * URL. The plugin is responsible for choosing a free port there is **no port
2922
- * registry in v1**, so a collision with a built-in (e.g. llama.cpp on 8080) is
2923
- * the plugin author's responsibility to avoid.
3119
+ * URL. The plugin is responsible for choosing a free port: there is **no
3120
+ * allocator** this number is what Core tries (after the profile offset), and a
3121
+ * collision is not repaired by moving anyone.
3122
+ *
3123
+ * There *is* a gate. `SidecarManager::claim_port` checks a live-sidecar registry
3124
+ * and bind-probes the port, and **refuses to start** the sidecar if either fails,
3125
+ * so a collision with a built-in (e.g. llama.cpp on 8080) surfaces as an app that
3126
+ * does not come up rather than as silent breakage. Detection, not resolution:
3127
+ * picking a free port is still the author's job. See `docs/port-allocation.md`
3128
+ * for the band map.
2924
3129
  */
2925
3130
  port: number;
2926
3131
  /**
@@ -2939,6 +3144,17 @@ export interface SidecarSpec {
2939
3144
  * ext-proxy hop and `/api/host/*`), and the host-RPC vocabulary has no
2940
3145
  * provider-registration method. Registration is therefore Core-side, driven by
2941
3146
  * this declaration.
3147
+ *
3148
+ * **Declaring this forces eager start and disables idle-stop**, overriding
3149
+ * [`lazy`] and [`idle_stop_secs`]. Pi bypasses the ext proxy and dials the
3150
+ * registered `baseUrl` directly, so no request can ever wake this sidecar on
3151
+ * demand; a sidecar that is never started never reaches the Healthy edge that
3152
+ * registers it, and one that is scaled to zero drops out of Pi's model list until
3153
+ * a wake that will never come. Both coercions are logged at `info` rather than
3154
+ * rejected by the validator, so existing manifests keep loading.
3155
+ *
3156
+ * [`lazy`]: SidecarSpec::lazy
3157
+ * [`idle_stop_secs`]: SidecarSpec::idle_stop_secs
2942
3158
  */
2943
3159
  provides_provider?: ProviderRegistrationSpec | null;
2944
3160
  }
package/src/index.ts CHANGED
@@ -37,6 +37,8 @@ export type {
37
37
  CapabilityReq,
38
38
  CompanionSurface,
39
39
  Contributes,
40
+ McpServerAuth,
41
+ McpServerDecl,
40
42
  PluginManifest,
41
43
  Requires,
42
44
  RunnableKind,
@@ -51,6 +53,8 @@ export {
51
53
  CapabilityReqSchema,
52
54
  CompanionSurfaceSchema,
53
55
  coreManifestJsonSchema,
56
+ McpServerAuthSchema,
57
+ McpServerDeclSchema,
54
58
  PluginManifestSchema,
55
59
  RequiresSchema,
56
60
  RunnableKindSchema,
@@ -61,6 +65,20 @@ export {
61
65
  validatePluginId,
62
66
  WidgetContributionSchema,
63
67
  } from "./manifest.ts";
68
+ export type {
69
+ JsonSchema,
70
+ McpStdioCommand,
71
+ McpTool,
72
+ PassthroughRegistration,
73
+ SdkRunnable,
74
+ } from "./mcp/index.ts";
75
+ export {
76
+ callTool,
77
+ listTools,
78
+ MCP_PROTOCOL_VERSION,
79
+ McpServer,
80
+ unwrapContent,
81
+ } from "./mcp/index.ts";
64
82
  export type {
65
83
  ChatDelta,
66
84
  ChatMessage,
@@ -74,6 +92,21 @@ export {
74
92
  resolveGatewayToken,
75
93
  resolveGatewayUrl,
76
94
  } from "./model/gateway.ts";
95
+ export type {
96
+ CommandContribution,
97
+ Disposable,
98
+ PanelContribution,
99
+ PanelRegion,
100
+ PluginContext,
101
+ RouteContribution,
102
+ RyuHostServices,
103
+ RyuPlugin,
104
+ RyuPluginModule,
105
+ SettingsSectionContribution,
106
+ StoreSectionContribution,
107
+ ThemeContribution,
108
+ } from "./plugin/ryu-plugin.ts";
109
+ export { toDisposable } from "./plugin/ryu-plugin.ts";
77
110
  export type {
78
111
  AgentCard,
79
112
  AgentManifestOptions,
@@ -91,6 +124,8 @@ export type {
91
124
  } from "./runnable/app.ts";
92
125
  export { appToolId, defineApp } from "./runnable/app.ts";
93
126
  export type {
127
+ BackgroundClient,
128
+ BackgroundProcess,
94
129
  DurableClient,
95
130
  EnginesClient,
96
131
  HttpPrimitiveTransportOptions,
@@ -7,6 +7,7 @@
7
7
  import { describe, expect, test } from "bun:test";
8
8
  import {
9
9
  CompanionSurfaceSchema,
10
+ ChatWidgetTemplateSchema,
10
11
  labelImpersonatesSystemChrome,
11
12
  PluginManifestSchema,
12
13
  RunnableMetaSchema,
@@ -22,6 +23,34 @@ function baseManifest(overrides: Record<string, unknown> = {}) {
22
23
  };
23
24
  }
24
25
 
26
+ test("chat widget templates keep host rendering data-only and forward compatible", () => {
27
+ const available = ChatWidgetTemplateSchema.safeParse({
28
+ id: "meetings.chat",
29
+ title: "Meetings widget",
30
+ triggers: ["meetings widget"],
31
+ examples: ["Show my meetings"],
32
+ backing: { view_id: "surface:meetings" },
33
+ display_mode: "inline",
34
+ safe_action_ids: ["refresh"],
35
+ }).success;
36
+ const comingSoon = ChatWidgetTemplateSchema.safeParse({
37
+ id: "future.chat",
38
+ title: "Future widget",
39
+ backing: {},
40
+ display_mode: "compact-future-mode",
41
+ availability: "coming-soon",
42
+ }).success;
43
+ const unsafe = ChatWidgetTemplateSchema.safeParse({
44
+ id: "future/chat",
45
+ title: "Unsafe",
46
+ backing: { tool_id: "future.render" },
47
+ display_mode: "inline",
48
+ }).success;
49
+ expect(available).toBe(true);
50
+ expect(comingSoon).toBe(true);
51
+ expect(unsafe).toBe(false);
52
+ });
53
+
25
54
  // ── semver regex ──────────────────────────────────────────────────────────────
26
55
 
27
56
  describe("PluginManifest version (semver regex)", () => {
@@ -171,7 +200,7 @@ describe("RunnableMetaSchema", () => {
171
200
  id: "x",
172
201
  name: "X",
173
202
  kind: "tool",
174
- config: { widget: true, slug: "x__render" },
203
+ config: { widget: true, slug: "x.render" },
175
204
  });
176
205
  expect(parsed.success).toBe(true);
177
206
  if (!parsed.success) {
@@ -127,18 +127,28 @@ const SDK_KNOWN_KINDS = new Set([
127
127
  const VALID_HOOK_PHASES = new Set([
128
128
  "post_assistant_turn",
129
129
  "pre_user_turn",
130
+ "pre_model_select",
131
+ "action",
130
132
  "session_start",
131
133
  "stop",
132
134
  "pre_tool_use",
133
135
  "post_tool_use",
136
+ "tool_result",
134
137
  "subagent_stop",
135
138
  "session_end",
136
139
  "notification",
140
+ "context",
141
+ "message_end",
142
+ "session_before_compact",
143
+ "session_compact",
144
+ "model_select",
145
+ "session_tree",
137
146
  // Kernel lifecycle phases fired by Core's own subsystems (the workflow
138
147
  // executor), not by any app.
139
148
  "workflow_run_started",
140
149
  "workflow_run_finished",
141
150
  "workflow_run_failed",
151
+ "delegation_lifecycle",
142
152
  ]);
143
153
 
144
154
  /** An **app event** — a phase some app declares in `contributes.hook_events`,
@@ -303,15 +313,15 @@ describe("glob_match oracle (ported from Core)", () => {
303
313
  expect(globMatch("bash", "bashx")).toBe(false);
304
314
  });
305
315
  it("trailing star is a prefix match", () => {
306
- expect(globMatch("bash*", "bash__run")).toBe(true);
316
+ expect(globMatch("bash*", "bash.run")).toBe(true);
307
317
  expect(globMatch("bash*", "sh")).toBe(false);
308
318
  });
309
319
  it("leading star is a suffix match", () => {
310
- expect(globMatch("*write", "fs__write")).toBe(true);
320
+ expect(globMatch("*write", "fs.write")).toBe(true);
311
321
  expect(globMatch("*write", "writer")).toBe(false);
312
322
  });
313
323
  it("double star is a substring match", () => {
314
- expect(globMatch("*edit*", "editor__do_edit")).toBe(true);
324
+ expect(globMatch("*edit*", "editor.do_edit")).toBe(true);
315
325
  expect(globMatch("*edit*", "read_only")).toBe(false);
316
326
  });
317
327
  it("an interior star degrades to an exact-literal match (Core has no compile step)", () => {