@ouro.bot/cli 0.1.0-alpha.851 → 0.1.0-alpha.852

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.852",
6
+ "changes": [
7
+ "An orientation hold no longer blocks read-only tools from the agent's own MCP servers (readOnlyHint); the Butler's media lookups are marked read-only. The media tools treat a 200 with an empty body as success, so clearing a stuck download goes on to re-search"
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.851",
6
12
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.851",
2
+ "runtimeVersion": "0.1.0-alpha.852",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -63,7 +63,9 @@ async function req(base, path, { key, header = "X-Api-Key", method = "GET", body
63
63
  throw new ServiceError(`${serviceOf(base)}_http_${res.status}`, `${serviceOf(base)} returned ${res.status}${text ? `: ${text.slice(0, 200)}` : ""}`)
64
64
  }
65
65
  if (res.status === 204) return null
66
- return res.json()
66
+ // Sonarr/Radarr answer some DELETEs with 200 and an empty body; that is success, not a parse failure.
67
+ const raw = await res.text()
68
+ return raw.trim() ? JSON.parse(raw) : null
67
69
  }
68
70
 
69
71
  function serviceOf(base) {
@@ -1135,9 +1137,10 @@ export async function mediaManualImport(a) {
1135
1137
 
1136
1138
  // ------------------------------------------------------------------- schema
1137
1139
 
1138
- const TOOLS = [
1140
+ export const TOOLS = [
1139
1141
  {
1140
1142
  name: "media_search",
1143
+ annotations: { readOnlyHint: true },
1141
1144
  description: "Search or browse the household media catalogue. Use `query` for a title lookup: partial and misspelled titles are matched against the library first (Sonarr, Radarr, Jellyfin) and returned as `library_matches` with their kind and service id, and when nothing in the library matches, `lookup_candidates` (TVDB/TMDB) cover titles not in the library. Never say a title does not exist without reading both; use `genres`/`keywords`/`year_range` to browse by vibe (e.g. keywords ['autumn','thanksgiving'] for cozy fall films). `on_shelf: true` restricts to what is already downloaded, `false` to what is not, omit for both. Every item says whether it is on the shelf and carries its tmdb_id so a follow-up media_request needs no second lookup.",
1142
1145
  inputSchema: { type: "object", properties: {
1143
1146
  query: { type: "string", description: "Title or free text. Omit to browse by filters." },
@@ -1163,6 +1166,7 @@ const TOOLS = [
1163
1166
  },
1164
1167
  {
1165
1168
  name: "media_request_status",
1169
+ annotations: { readOnlyHint: true },
1166
1170
  description: "One call, every stage: request, indexer search, download, file on disk, plus acquisition-chain health and a deterministic `diagnosis` block naming what is stuck, why, and the fix. Use this for any 'is it here yet?' or 'why hasn't X downloaded?' question. A queue row is not proof of progress: a release sitting at zero bytes comes back as stalled, not as downloading. Never infer the cause yourself — read diagnosis.summary.",
1167
1171
  inputSchema: { type: "object", properties: {
1168
1172
  request_id: { type: "string", description: "e.g. jellyseerr:42" },
@@ -1181,6 +1185,7 @@ const TOOLS = [
1181
1185
  },
1182
1186
  {
1183
1187
  name: "media_chain_health",
1188
+ annotations: { readOnlyHint: true },
1184
1189
  description: "Health of the whole acquisition chain: Prowlarr indexers, their failure state, and whether Prowlarr is syncing to Sonarr/Radarr. Call this when several things are stuck at once, or before telling anyone a specific title is the problem — a dead chain blocks everything and is the single most likely cause.",
1185
1190
  inputSchema: { type: "object", properties: {} },
1186
1191
  },
@@ -1213,6 +1218,7 @@ const TOOLS = [
1213
1218
  },
1214
1219
  {
1215
1220
  name: "media_episodes",
1221
+ annotations: { readOnlyHint: true },
1216
1222
  description: "List a Sonarr series' episodes (optionally one season): episode_id, SxxEyy code, scene_code (the numbering release names use, when Sonarr has a mapping), title, air date, has_file. Use it to see what the TVDB numbering calls each episode before searching for releases by hand.",
1217
1223
  inputSchema: { type: "object", properties: {
1218
1224
  service_id: { type: "number", description: "Sonarr series id." },
@@ -1221,6 +1227,7 @@ const TOOLS = [
1221
1227
  },
1222
1228
  {
1223
1229
  name: "media_indexer_search",
1230
+ annotations: { readOnlyHint: true },
1224
1231
  description: "Free-text search across every indexer (Prowlarr), sorted by seeders. Discovery only, it grabs nothing. Use it when Sonarr/Radarr's own search finds nothing: try the scene code from media_episodes, 'Season N', the year, or an episode title, to learn whether any release exists and how it is named. Zero seeders means it cannot download right now.",
1225
1232
  inputSchema: { type: "object", properties: {
1226
1233
  query: { type: "string" },
@@ -1229,6 +1236,7 @@ const TOOLS = [
1229
1236
  },
1230
1237
  {
1231
1238
  name: "media_release_search",
1239
+ annotations: { readOnlyHint: true },
1232
1240
  description: "Interactive release search for a series or movie already in the library: what the indexers offer and how Sonarr/Radarr parsed each release (full_season, parsed_season, parsed_episodes, mapped_episodes), whether it is rejected and why, and whether it is blocklisted. Series: give season_number or episode_ids. Can take a minute or more. `query` filters returned titles. Use it when the automatic search found nothing, then grab with media_release_grab.",
1233
1241
  inputSchema: { type: "object", properties: {
1234
1242
  kind: { type: "string", enum: ["series", "movie"] },
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.851</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.852</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -67,11 +67,15 @@ function mcpToolsAsDefinitions(view) {
67
67
  });
68
68
  return {
69
69
  tool: schema,
70
- riskProfile: {
71
- mutates: "external_side_effect",
72
- risk: "high",
73
- reason: "MCP tools may mutate external systems",
74
- },
70
+ // A read-only hint is trusted only from the agent's own bundle (agent.json mcpServers),
71
+ // so an orientation hold still lets the agent inspect through those tools.
72
+ riskProfile: entry.source === "builtin" && tool.annotations?.readOnlyHint === true
73
+ ? { mutates: "none", risk: "low", reason: "read-only tool from the agent's own MCP server" }
74
+ : {
75
+ mutates: "external_side_effect",
76
+ risk: "high",
77
+ reason: "MCP tools may mutate external systems",
78
+ },
75
79
  handler: async (args, ctx) => {
76
80
  if (ctx?.agentName !== binding.agentName || ctx.agentRoot !== binding.agentRoot) {
77
81
  (0, runtime_1.emitNervesEvent)({
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.851",
3
+ "version": "0.1.0-alpha.852",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@ouro.bot/cli",
9
- "version": "0.1.0-alpha.851",
9
+ "version": "0.1.0-alpha.852",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.851",
3
+ "version": "0.1.0-alpha.852",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },