@tai42/api-client 25.0.0 → 26.0.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 (157) hide show
  1. package/dist/agents.d.ts +1 -0
  2. package/dist/agents.d.ts.map +1 -1
  3. package/dist/agents.js.map +1 -1
  4. package/dist/client/auth-client.d.ts +1 -0
  5. package/dist/client/auth-client.d.ts.map +1 -1
  6. package/dist/client/backup-client.d.ts +1 -0
  7. package/dist/client/backup-client.d.ts.map +1 -1
  8. package/dist/client/capabilities-client.d.ts +1 -0
  9. package/dist/client/capabilities-client.d.ts.map +1 -1
  10. package/dist/client/channels-client.d.ts +4 -1
  11. package/dist/client/channels-client.d.ts.map +1 -1
  12. package/dist/client/config-client.d.ts +1 -0
  13. package/dist/client/config-client.d.ts.map +1 -1
  14. package/dist/client/connectors-client.d.ts +7 -0
  15. package/dist/client/connectors-client.d.ts.map +1 -1
  16. package/dist/client/conversations-client.d.ts +9 -9
  17. package/dist/client/hooks-client.d.ts +1 -1
  18. package/dist/client/index.d.ts +529 -198
  19. package/dist/client/index.d.ts.map +1 -1
  20. package/dist/client/index.js +9 -1
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/interactions-client.d.ts +1 -1
  23. package/dist/client/manifest-client.d.ts +161 -30
  24. package/dist/client/manifest-client.d.ts.map +1 -1
  25. package/dist/client/manifest-client.js +16 -5
  26. package/dist/client/manifest-client.js.map +1 -1
  27. package/dist/client/marketplace-client.d.ts +132 -83
  28. package/dist/client/marketplace-client.d.ts.map +1 -1
  29. package/dist/client/observability-client.d.ts +2 -2
  30. package/dist/client/presets-client.d.ts +113 -11
  31. package/dist/client/presets-client.d.ts.map +1 -1
  32. package/dist/client/presets-client.js +4 -3
  33. package/dist/client/presets-client.js.map +1 -1
  34. package/dist/client/profiles-client.d.ts +1 -0
  35. package/dist/client/profiles-client.d.ts.map +1 -1
  36. package/dist/client/states-client.d.ts +30 -21
  37. package/dist/client/states-client.d.ts.map +1 -1
  38. package/dist/client/states-client.js +5 -0
  39. package/dist/client/states-client.js.map +1 -1
  40. package/dist/client/storage-client.d.ts +19 -0
  41. package/dist/client/storage-client.d.ts.map +1 -1
  42. package/dist/client/storage-client.js +14 -17
  43. package/dist/client/storage-client.js.map +1 -1
  44. package/dist/client/templates-client.d.ts +20 -2
  45. package/dist/client/templates-client.d.ts.map +1 -1
  46. package/dist/client/templates-client.js +1 -1
  47. package/dist/client/templates-client.js.map +1 -1
  48. package/dist/client/tool-extensions-client.d.ts +3 -0
  49. package/dist/client/tool-extensions-client.d.ts.map +1 -1
  50. package/dist/client/transport.d.ts +2 -1
  51. package/dist/client/transport.d.ts.map +1 -1
  52. package/dist/client/transport.js.map +1 -1
  53. package/dist/errors.d.ts +13 -0
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +31 -0
  56. package/dist/errors.js.map +1 -1
  57. package/dist/fleet-report.d.ts +14 -2
  58. package/dist/fleet-report.d.ts.map +1 -1
  59. package/dist/fleet-report.js +41 -10
  60. package/dist/fleet-report.js.map +1 -1
  61. package/dist/generated/api-routes.d.ts +17 -1
  62. package/dist/generated/api-routes.d.ts.map +1 -1
  63. package/dist/generated/api-routes.js +7 -2
  64. package/dist/generated/api-routes.js.map +1 -1
  65. package/dist/generated/openapi-schemas.d.ts +737 -160
  66. package/dist/generated/openapi-schemas.d.ts.map +1 -1
  67. package/dist/generated/openapi-schemas.js +46 -27
  68. package/dist/generated/openapi-schemas.js.map +1 -1
  69. package/dist/generated/served-schemas.d.ts +80 -16
  70. package/dist/generated/served-schemas.d.ts.map +1 -1
  71. package/dist/generated/served-schemas.js +64 -4
  72. package/dist/generated/served-schemas.js.map +1 -1
  73. package/dist/http.d.ts +6 -0
  74. package/dist/http.d.ts.map +1 -1
  75. package/dist/http.js +11 -0
  76. package/dist/http.js.map +1 -1
  77. package/dist/index.d.ts +3 -3
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/schemas/auth-routes.d.ts +34 -8
  81. package/dist/schemas/auth-routes.d.ts.map +1 -1
  82. package/dist/schemas/auth-routes.js +16 -8
  83. package/dist/schemas/auth-routes.js.map +1 -1
  84. package/dist/schemas/backup.d.ts +13 -0
  85. package/dist/schemas/backup.d.ts.map +1 -1
  86. package/dist/schemas/backup.js +5 -0
  87. package/dist/schemas/backup.js.map +1 -1
  88. package/dist/schemas/capabilities.d.ts +11 -6
  89. package/dist/schemas/capabilities.d.ts.map +1 -1
  90. package/dist/schemas/capabilities.js +10 -6
  91. package/dist/schemas/capabilities.js.map +1 -1
  92. package/dist/schemas/channels.d.ts +7 -1
  93. package/dist/schemas/channels.d.ts.map +1 -1
  94. package/dist/schemas/connectors.d.ts +6 -0
  95. package/dist/schemas/connectors.d.ts.map +1 -1
  96. package/dist/schemas/conversations.d.ts +11 -11
  97. package/dist/schemas/extensions.d.ts +8 -4
  98. package/dist/schemas/extensions.d.ts.map +1 -1
  99. package/dist/schemas/extensions.js +5 -4
  100. package/dist/schemas/extensions.js.map +1 -1
  101. package/dist/schemas/hooks.d.ts +1 -1
  102. package/dist/schemas/interactions.d.ts +3 -3
  103. package/dist/schemas/manifest-mcp.d.ts +79 -0
  104. package/dist/schemas/manifest-mcp.d.ts.map +1 -1
  105. package/dist/schemas/manifest-mcp.js +19 -0
  106. package/dist/schemas/manifest-mcp.js.map +1 -1
  107. package/dist/schemas/marketplace.d.ts +155 -84
  108. package/dist/schemas/marketplace.d.ts.map +1 -1
  109. package/dist/schemas/marketplace.js +7 -2
  110. package/dist/schemas/marketplace.js.map +1 -1
  111. package/dist/schemas/observability.d.ts +3 -3
  112. package/dist/schemas/presets.d.ts +107 -14
  113. package/dist/schemas/presets.d.ts.map +1 -1
  114. package/dist/schemas/presets.js +7 -5
  115. package/dist/schemas/presets.js.map +1 -1
  116. package/dist/schemas/profiles.d.ts +1 -0
  117. package/dist/schemas/profiles.d.ts.map +1 -1
  118. package/dist/schemas/served.d.ts +67 -13
  119. package/dist/schemas/served.d.ts.map +1 -1
  120. package/dist/schemas/states.d.ts +22 -22
  121. package/dist/schemas/storage.d.ts +30 -1
  122. package/dist/schemas/storage.d.ts.map +1 -1
  123. package/dist/schemas/storage.js +8 -1
  124. package/dist/schemas/storage.js.map +1 -1
  125. package/dist/schemas/system-kinds.d.ts +1 -0
  126. package/dist/schemas/system-kinds.d.ts.map +1 -1
  127. package/dist/schemas/templates.d.ts +110 -83
  128. package/dist/schemas/templates.d.ts.map +1 -1
  129. package/dist/schemas/templates.js +14 -2
  130. package/dist/schemas/templates.js.map +1 -1
  131. package/dist/schemas/tools.d.ts +1 -1
  132. package/package.json +3 -3
  133. package/src/agents.ts +1 -0
  134. package/src/client/index.ts +9 -2
  135. package/src/client/manifest-client.ts +39 -12
  136. package/src/client/presets-client.ts +20 -6
  137. package/src/client/states-client.ts +10 -0
  138. package/src/client/storage-client.ts +21 -17
  139. package/src/client/templates-client.ts +1 -1
  140. package/src/client/transport.ts +3 -1
  141. package/src/errors.ts +34 -0
  142. package/src/fleet-report.ts +46 -7
  143. package/src/generated/api-routes.ts +7 -2
  144. package/src/generated/openapi-schemas.ts +80 -31
  145. package/src/generated/served-schemas.ts +64 -4
  146. package/src/http.ts +12 -0
  147. package/src/index.ts +19 -2
  148. package/src/schemas/auth-routes.ts +18 -8
  149. package/src/schemas/backup.ts +7 -0
  150. package/src/schemas/capabilities.ts +10 -6
  151. package/src/schemas/channels.ts +2 -0
  152. package/src/schemas/extensions.ts +5 -4
  153. package/src/schemas/manifest-mcp.ts +25 -0
  154. package/src/schemas/marketplace.ts +7 -2
  155. package/src/schemas/presets.ts +13 -6
  156. package/src/schemas/storage.ts +10 -1
  157. package/src/schemas/templates.ts +17 -1
@@ -1,4 +1,4 @@
1
- /** Manifest / MCP section, env-ref and config-reload sub-clients. */
1
+ /** Manifest / MCP section, per-server MCP ops, env-ref and config-reload sub-clients. */
2
2
  import { apiRoutes, buildPath } from '../generated/api-routes';
3
3
  import * as s from '../schemas';
4
4
  import type { Transport } from './transport';
@@ -28,6 +28,12 @@ export interface SetMcpSecretEnvBody {
28
28
  readonly manifest_pointer: string;
29
29
  }
30
30
 
31
+ /** The include/exclude tool filter of one MCP entry (`PUT .../tool-filter`). */
32
+ export interface McpToolFilter {
33
+ readonly include: readonly string[];
34
+ readonly exclude: readonly string[];
35
+ }
36
+
31
37
  export function manifestClient(t: Transport) {
32
38
  const { req } = t;
33
39
  return {
@@ -38,17 +44,6 @@ export function manifestClient(t: Transport) {
38
44
  method: 'POST',
39
45
  body: { mcp },
40
46
  }),
41
- getMcpStatus: (signal?: AbortSignal) =>
42
- req(apiRoutes.get_api_mcp_status.path, s.mcpStatus, { signal }),
43
- reloadMcp: (title: string) =>
44
- req(
45
- buildPath(apiRoutes.post_api_mcp_status_title_reload.path, { title }),
46
- s.mcpReloadResult,
47
- {
48
- method: 'POST',
49
- },
50
- ),
51
-
52
47
  // Granular manifest-section entry edits — add/replace or remove ONE entry by
53
48
  // title, leaving the rest of the section untouched (the surgical counterpart to
54
49
  // the whole-section `setMcpConfig`). Each crosses the config pipeline and returns
@@ -93,6 +88,38 @@ export function manifestClient(t: Transport) {
93
88
  exclude_remove: body.exclude_remove ?? [],
94
89
  },
95
90
  }),
91
+ };
92
+ }
93
+
94
+ /** Per-server MCP status reads and live ops: status, advertised tools, tool filter, reloads. */
95
+ export function mcpServersClient(t: Transport) {
96
+ const { req } = t;
97
+ return {
98
+ getMcpStatus: (signal?: AbortSignal) =>
99
+ req(apiRoutes.get_api_mcp_status.path, s.mcpStatus, { signal }),
100
+ // Every tool the server advertises, before the entry's tool filter. A POST: the
101
+ // read opens a session to the server.
102
+ listMcpAdvertisedTools: (title: string, signal?: AbortSignal) =>
103
+ req(
104
+ buildPath(apiRoutes.post_api_mcp_status_title_advertised_tools.path, { title }),
105
+ s.mcpAdvertisedTools,
106
+ { method: 'POST', signal },
107
+ ),
108
+ // Replace the entry's include/exclude tool filter; crosses the config pipeline.
109
+ setMcpToolFilter: (title: string, filter: McpToolFilter) =>
110
+ req(
111
+ buildPath(apiRoutes.put_api_mcp_config_entries_title_tool_filter.path, { title }),
112
+ s.reloadConfigResult,
113
+ { method: 'PUT', body: filter },
114
+ ),
115
+ reloadMcp: (title: string) =>
116
+ req(
117
+ buildPath(apiRoutes.post_api_mcp_status_title_reload.path, { title }),
118
+ s.mcpReloadResult,
119
+ {
120
+ method: 'POST',
121
+ },
122
+ ),
96
123
 
97
124
  // The MCP servers skipped by the viability check, plus their remediation ops. The
98
125
  // LIST rides the fleet fan-out primitive (each worker's failed roster arrives as
@@ -1,5 +1,6 @@
1
1
  /** Preset CRUD, versioning and validation sub-client. */
2
2
  import { apiRoutes, buildPath } from '../generated/api-routes';
3
+ import type { PresetHolderAction } from '../schemas';
3
4
  import * as s from '../schemas';
4
5
  import type { Transport } from './transport';
5
6
 
@@ -10,7 +11,11 @@ import type { Transport } from './transport';
10
11
  * `extensions` is the combos list — the create flow OMITS it when there are no
11
12
  * combos (the route rejects an explicit `extensions: []`). `output_schema` is the
12
13
  * optional author-set output JSON Schema; `input_schema` is the optional author-set
13
- * input JSON Schema, read by the router as a top-level version field. Each combo
14
+ * input JSON Schema, read by the router as a top-level version field. `input_map` is
15
+ * the optional jq program that turns the caller's input into the base tool's
16
+ * arguments (it needs an `input_schema`); `output_map` is the optional jq program that
17
+ * reshapes the base tool's result before the output schema is checked. Each map is
18
+ * inline `content` or a stored template `id` (a plain string is inline jq). Each combo
14
19
  * element is a bare extension name or a `{ name, config }` mapping. Categorization
15
20
  * tags live in the tool_meta overlay, written after a successful create — never in
16
21
  * this body.
@@ -23,6 +28,8 @@ export interface CreatePresetBody {
23
28
  readonly extensions?: readonly s.PresetExtensionElement[][];
24
29
  readonly output_schema?: Record<string, unknown> | null;
25
30
  readonly input_schema?: Record<string, unknown> | null;
31
+ readonly input_map?: s.TemplatedText | string | null;
32
+ readonly output_map?: s.TemplatedText | string | null;
26
33
  // The optional door-layer state binding applied around every run of this preset.
27
34
  readonly state_binding?: s.StateBinding | null;
28
35
  }
@@ -35,7 +42,9 @@ export interface CreatePresetBody {
35
42
  * or `input_schema`. `input_schema` is the optional author-set input JSON Schema and
36
43
  * follows the same carry-forward sentinel as `output_schema`. `state_binding` is the
37
44
  * optional door-layer binding and follows the same sentinel: omitted carries the active
38
- * one forward, an explicit `null` clears it. `description` carries forward when omitted;
45
+ * one forward, an explicit `null` clears it. `input_map` and `output_map` follow the same
46
+ * sentinel: omitted carries the active map forward, an explicit `null` clears it.
47
+ * `description` carries forward when omitted;
39
48
  * an explicit non-empty string sets it (the API rejects an explicit empty one).
40
49
  * Categorization tags are not a version field — they live in the tool_meta overlay.
41
50
  */
@@ -45,6 +54,8 @@ export interface SavePresetVersionBody {
45
54
  readonly extensions?: readonly s.PresetExtensionElement[][];
46
55
  readonly output_schema?: Record<string, unknown> | null;
47
56
  readonly input_schema?: Record<string, unknown> | null;
57
+ readonly input_map?: s.TemplatedText | string | null;
58
+ readonly output_map?: s.TemplatedText | string | null;
48
59
  readonly state_binding?: s.StateBinding | null;
49
60
  }
50
61
 
@@ -63,6 +74,8 @@ export interface ValidatePresetBody {
63
74
  readonly extensions?: readonly s.PresetExtensionElement[][];
64
75
  readonly output_schema?: Record<string, unknown> | null;
65
76
  readonly input_schema?: Record<string, unknown> | null;
77
+ readonly input_map?: s.TemplatedText | string | null;
78
+ readonly output_map?: s.TemplatedText | string | null;
66
79
  // The optional door-layer state binding to dry-run — validated (never mounted) by the
67
80
  // same checks the write would run. In VERSION mode it follows the save-version sentinel:
68
81
  // omitted carries the active binding forward, an explicit `null` clears it.
@@ -112,10 +125,11 @@ export function presetsClient(t: Transport) {
112
125
  method: 'POST',
113
126
  body: { new_name: newName },
114
127
  }),
115
- // The presets whose active composition names this one as a tool — the referees
116
- // a rename would strand. A rename preflight reads this to warn before the 409.
117
- getPresetReferees: (name: string, signal?: AbortSignal) =>
118
- req(buildPath(apiRoutes.get_api_presets_name_referees.path, { name }), s.presetReferees, {
128
+ // Every holder that would refuse this preset's rename or delete. A dialog
129
+ // preflights it to list the holders before the server's 409 backstop.
130
+ getPresetReferees: (name: string, action: PresetHolderAction, signal?: AbortSignal) =>
131
+ req(buildPath(apiRoutes.get_api_presets_name_referees.path, { name }), s.holdersPreview, {
132
+ query: { action },
119
133
  signal,
120
134
  }),
121
135
  // The dry-run verdict for a draft (create OR version, mode-resolved server-side):
@@ -165,6 +165,16 @@ function stateAttachmentMethods(t: Transport) {
165
165
  s.stateAttachmentUpdated,
166
166
  { method: 'PATCH', body },
167
167
  ),
168
+ // Every holder that would refuse detaching `template` from the state.
169
+ getStateTemplateReferees: (name: string, template: string, signal?: AbortSignal) =>
170
+ req(
171
+ buildPath(apiRoutes.get_api_states_name_attachments_template_referees.path, {
172
+ name,
173
+ template,
174
+ }),
175
+ s.holdersPreview,
176
+ { signal },
177
+ ),
168
178
  detachStateTemplate: (name: string, template: string) =>
169
179
  req(
170
180
  buildPath(apiRoutes.delete_api_states_name_attachments_template.path, { name, template }),
@@ -1,6 +1,6 @@
1
1
  /** Storage resource CRUD sub-client. */
2
2
  import { apiRoutes, buildPath } from '../generated/api-routes';
3
- import { apiDownload, isUnsafePathSegment } from '../http';
3
+ import { apiDownload } from '../http';
4
4
  import * as s from '../schemas';
5
5
  import type { Transport } from './transport';
6
6
 
@@ -17,22 +17,17 @@ export interface StorageUploadBody {
17
17
  }
18
18
 
19
19
  /**
20
- * Encode a possibly-nested storage id / directory path for use in a URL path,
21
- * percent-encoding each segment while preserving the `/` separators. A storage id
22
- * can be a nested path (e.g. `a/b c.txt`); encoding each segment independently keeps
23
- * the slashes structural and escapes everything else (spaces, `#`, `?`, …).
24
- *
25
- * Dot segments (`.`, `..`), empty segments (a leading/trailing/doubled `/`, or an
26
- * empty id) are REJECTED loudly rather than encoded: the browser's WHATWG URL parser
27
- * removes dot-segments from a relative request path before it is sent — collapsing
28
- * `..` even when percent-encoded — so such an id can never be faithfully transmitted.
29
- * Left to encode, it would silently retarget the request at an unrelated same-origin
30
- * route. The server enforces the same relative-path rule; failing fast here keeps a
31
- * malformed id from ever leaving the client.
20
+ * A possibly-nested storage id for a `{resource_id:path}` route parameter. Only a `.`
21
+ * or `..` segment is refused here: the browser's URL parser removes those before the
22
+ * request is sent, so `./x` would silently reach `x`. Every other spelling (`a//b`,
23
+ * `x/`) is sent as typed, and the server refuses every non-canonical key, these
24
+ * included, with the same words.
32
25
  */
33
26
  function storagePath(id: string): string {
34
- if (id.split('/').some(isUnsafePathSegment)) {
35
- throw new Error("storage path must be a relative path with no empty, '.', or '..' segment");
27
+ if (id.split('/').some((segment) => segment === '.' || segment === '..')) {
28
+ throw new Error(
29
+ `non-canonical store key '${id}': a key is '/'-separated names with no empty, '.' or '..' segment`,
30
+ );
36
31
  }
37
32
  return id;
38
33
  }
@@ -43,12 +38,21 @@ export function storageClient(t: Transport) {
43
38
  // The content store a storage-provider plugin exposes. Dead by default:
44
39
  // `getStorageInfo` reports `present: false` (a 200 empty state) and every other
45
40
  // door answers a loud 501 when no provider is installed. Ids may be nested
46
- // paths: `storagePath` refuses a dot or empty segment, and `buildPath` encodes each
47
- // segment of the route's `{…:path}` parameter with the `/` separators preserved.
41
+ // paths: `storagePath` refuses a dot segment the browser would remove, and
42
+ // `buildPath` encodes each part of the route's `{…:path}` parameter with the `/`
43
+ // separators preserved; the server refuses every other non-canonical key.
48
44
  getStorageInfo: (signal?: AbortSignal) =>
49
45
  req(apiRoutes.get_api_storage.path, s.storageInfo, { signal }),
50
46
  listStorageResources: (signal?: AbortSignal) =>
51
47
  req(apiRoutes.get_api_storage_resources.path, s.storageResourceList, { signal }),
48
+ readStorageResource: (id: string, signal?: AbortSignal) =>
49
+ req(
50
+ buildPath(apiRoutes.get_api_storage_entries_resource_id.path, {
51
+ resource_id: storagePath(id),
52
+ }),
53
+ s.storedEntry,
54
+ { signal },
55
+ ),
52
56
  statStorageResource: (id: string, signal?: AbortSignal) =>
53
57
  req(
54
58
  buildPath(apiRoutes.get_api_storage_resources_resource_id_stat.path, {
@@ -9,7 +9,7 @@ export function templatesClient(t: Transport) {
9
9
  listTemplates: (signal?: AbortSignal) =>
10
10
  req(apiRoutes.get_api_templates.path, s.templateNames, { signal }),
11
11
  getTemplate: (templateId: string) =>
12
- req(apiRoutes.post_api_template.path, s.templateDetail, {
12
+ req(apiRoutes.post_api_template.path, s.templateEntry, {
13
13
  method: 'POST',
14
14
  body: { template_id: templateId },
15
15
  }),
@@ -1,4 +1,6 @@
1
1
  /** The shared transport seam every sub-client method flows through. */
2
+ import type { z } from 'zod';
3
+
2
4
  import type { ApiConfig } from '../http';
3
5
  import { apiRequest } from '../http';
4
6
 
@@ -11,7 +13,7 @@ export function makeTransport(config: ApiConfig) {
11
13
  path: string,
12
14
  schema: S,
13
15
  options?: Parameters<typeof apiRequest>[3],
14
- ) => apiRequest(config, path, schema, options);
16
+ ): Promise<z.output<S>> => apiRequest(config, path, schema, options);
15
17
  return { config, req };
16
18
  }
17
19
 
package/src/errors.ts CHANGED
@@ -4,6 +4,10 @@
4
4
  * empty render.
5
5
  */
6
6
 
7
+ import { z } from 'zod';
8
+
9
+ import { type ReferenceHolder, referenceHolder } from './generated/openapi-schemas';
10
+
7
11
  /** The skeleton returned `{ "error": "<message>" }` (or a non-2xx status). */
8
12
  export class ApiError extends Error {
9
13
  readonly status: number;
@@ -102,3 +106,33 @@ export class ApiSchemaError extends Error {
102
106
  this.issues = issues;
103
107
  }
104
108
  }
109
+
110
+ /**
111
+ * The holders a refused rename or delete names: an `ApiError` whose body carries a
112
+ * `holders` list answers it, parsed (a malformed list throws). Any other error, or a
113
+ * body without `holders`, answers `null`.
114
+ */
115
+ export function holdersFromError(error: unknown): ReferenceHolder[] | null {
116
+ if (!(error instanceof ApiError)) return null;
117
+ const body = error.body;
118
+ if (typeof body !== 'object' || body === null || !('holders' in body)) return null;
119
+ return z.array(referenceHolder).parse(body.holders);
120
+ }
121
+
122
+ const STORED_PRESET_UNREADABLE = 'stored-preset-unreadable';
123
+
124
+ /**
125
+ * The stored preset a read could not walk: an `ApiError` with code
126
+ * `stored-preset-unreadable` answers the row's name from `body.preset`. That code without a
127
+ * non-empty string `preset` throws; any other error answers `null`.
128
+ */
129
+ export function storedPresetFaultFromError(error: unknown): string | null {
130
+ if (!(error instanceof ApiError) || error.code !== STORED_PRESET_UNREADABLE) return null;
131
+ const body = error.body;
132
+ const preset =
133
+ typeof body === 'object' && body !== null && 'preset' in body ? body.preset : undefined;
134
+ if (typeof preset !== 'string' || preset === '') {
135
+ throw new Error(`a '${STORED_PRESET_UNREADABLE}' error carried no stored preset name`);
136
+ }
137
+ return preset;
138
+ }
@@ -58,6 +58,12 @@ export interface FleetReportSummary {
58
58
  readonly error: string | null;
59
59
  /** The workers whose reload named recycle-class keys awaiting a respawn. */
60
60
  readonly awaiting: readonly FleetWorkerAwaitingRespawn[];
61
+ /**
62
+ * The config store's error releasing its exclusive write span after the persist, or
63
+ * `null` when it released. Non-null means the change landed but the store may hold
64
+ * the span until the writer process exits, so a later write can block or be refused.
65
+ */
66
+ readonly spanReleaseError: string | null;
61
67
  }
62
68
 
63
69
  /**
@@ -72,9 +78,12 @@ function awaitingKeys(payload: unknown): string[] {
72
78
  return keys.filter((key): key is string => typeof key === 'string');
73
79
  }
74
80
 
75
- /** True when a summary describes a propagation the operator must be shown. */
81
+ /**
82
+ * True when a summary describes a failure the operator must be shown: a propagation
83
+ * that did not converge, or a write span the store did not release.
84
+ */
76
85
  export function isFleetReportFailure(summary: FleetReportSummary | null): boolean {
77
- return summary !== null && summary.status !== 'converged';
86
+ return summary !== null && (summary.status !== 'converged' || summary.spanReleaseError !== null);
78
87
  }
79
88
 
80
89
  /**
@@ -88,15 +97,23 @@ export function summarizeFleetResult(result: Omit<FleetResult, 'ok'>): FleetRepo
88
97
  const awaiting: FleetWorkerAwaitingRespawn[] = result.results
89
98
  .map((entry) => ({ name: entry.name, keys: awaitingKeys(entry.payload) }))
90
99
  .filter((entry) => entry.keys.length > 0);
91
- return summarizeWith(result, awaiting);
100
+ return summarizeWith(result, awaiting, null);
92
101
  }
93
102
 
94
103
  function summarizeWith(
95
104
  result: Omit<FleetResult, 'ok'>,
96
105
  awaiting: readonly FleetWorkerAwaitingRespawn[],
106
+ spanReleaseError: string | null,
97
107
  ): FleetReportSummary {
98
108
  if (!result.reachable) {
99
- return { status: 'unreachable', note: null, failures: [], error: result.error, awaiting };
109
+ return {
110
+ status: 'unreachable',
111
+ note: null,
112
+ failures: [],
113
+ error: result.error,
114
+ awaiting,
115
+ spanReleaseError,
116
+ };
100
117
  }
101
118
  const failures: FleetWorkerFailure[] = result.results
102
119
  .filter((entry) => entry.outcome !== 'applied')
@@ -111,6 +128,7 @@ function summarizeWith(
111
128
  failures,
112
129
  error: null,
113
130
  awaiting,
131
+ spanReleaseError,
114
132
  };
115
133
  }
116
134
 
@@ -122,16 +140,37 @@ function summarizeWith(
122
140
  * `awaiting` is the mutation's own verdict (its response's `awaiting_respawn`, or `[]`
123
141
  * for a mutation that states none): the operation knows whether its recycle roll
124
142
  * carried a worker's moved env, so its per-worker rows are never re-read for it.
143
+ * `spanReleaseError` is the response's `span_release_error`: a non-null value makes the
144
+ * summary a failure ({@link isFleetReportFailure}) whatever the fan-out reports, and a
145
+ * response that mutated no manifest still yields a summary carrying it.
125
146
  */
126
147
  export function summarizeFleetFanout(
127
148
  fanout: FleetReportFanout | null | undefined,
128
149
  awaiting: readonly FleetWorkerAwaitingRespawn[],
150
+ spanReleaseError: string | null,
129
151
  ): FleetReportSummary | null {
130
- if (fanout === null || fanout === undefined) return null;
152
+ if (fanout === null || fanout === undefined) {
153
+ if (spanReleaseError === null) return null;
154
+ return {
155
+ status: 'converged',
156
+ note: null,
157
+ failures: [],
158
+ error: null,
159
+ awaiting,
160
+ spanReleaseError,
161
+ };
162
+ }
131
163
  if (fanout.mode === 'local-only') {
132
- return { status: 'converged', note: fanout.note, failures: [], error: null, awaiting };
164
+ return {
165
+ status: 'converged',
166
+ note: fanout.note,
167
+ failures: [],
168
+ error: null,
169
+ awaiting,
170
+ spanReleaseError,
171
+ };
133
172
  }
134
- return summarizeWith(fanout, awaiting);
173
+ return summarizeWith(fanout, awaiting, spanReleaseError);
135
174
  }
136
175
 
137
176
  /** One MCP server skipped by the viability check — its title, a coarse status, and the
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // The platform's core routes by operationId. A placeholder written `{name:path}` matches
6
6
  // across `/` separators (the server names its converter on the parameter).
7
- import { encodeSegment } from '../http';
7
+ import { encodePathPart, encodeSegment } from '../http';
8
8
 
9
9
  export const apiRoutes = {
10
10
  delete_api_agents_config_entries_title: { method: "DELETE", path: "/api/agents-config/entries/{title}" },
@@ -129,6 +129,7 @@ export const apiRoutes = {
129
129
  get_api_states_name: { method: "GET", path: "/api/states/{name}" },
130
130
  get_api_states_name_attachments: { method: "GET", path: "/api/states/{name}/attachments" },
131
131
  get_api_states_name_attachments_template: { method: "GET", path: "/api/states/{name}/attachments/{template}" },
132
+ get_api_states_name_attachments_template_referees: { method: "GET", path: "/api/states/{name}/attachments/{template}/referees" },
132
133
  get_api_states_name_consumers: { method: "GET", path: "/api/states/{name}/consumers" },
133
134
  get_api_states_name_records_check: { method: "GET", path: "/api/states/{name}/records/check" },
134
135
  get_api_states_name_records_target_kind_target_name_kind_key: { method: "GET", path: "/api/states/{name}/records/{target_kind}/{target_name}/{kind}/{key}" },
@@ -137,6 +138,7 @@ export const apiRoutes = {
137
138
  get_api_states_name_stats: { method: "GET", path: "/api/states/{name}/stats" },
138
139
  get_api_states_name_subjects: { method: "GET", path: "/api/states/{name}/subjects" },
139
140
  get_api_storage: { method: "GET", path: "/api/storage" },
141
+ get_api_storage_entries_resource_id: { method: "GET", path: "/api/storage/entries/{resource_id:path}" },
140
142
  get_api_storage_resources: { method: "GET", path: "/api/storage/resources" },
141
143
  get_api_storage_resources_resource_id_content: { method: "GET", path: "/api/storage/resources/{resource_id:path}/content" },
142
144
  get_api_storage_resources_resource_id_stat: { method: "GET", path: "/api/storage/resources/{resource_id:path}/stat" },
@@ -214,6 +216,7 @@ export const apiRoutes = {
214
216
  post_api_mcp_config_entries: { method: "POST", path: "/api/mcp-config/entries" },
215
217
  post_api_mcp_config_secret_env: { method: "POST", path: "/api/mcp-config/secret-env" },
216
218
  post_api_mcp_status_reload_failed: { method: "POST", path: "/api/mcp-status/reload-failed" },
219
+ post_api_mcp_status_title_advertised_tools: { method: "POST", path: "/api/mcp-status/{title}/advertised-tools" },
217
220
  post_api_mcp_status_title_deregister: { method: "POST", path: "/api/mcp-status/{title}/deregister" },
218
221
  post_api_mcp_status_title_reload: { method: "POST", path: "/api/mcp-status/{title}/reload" },
219
222
  post_api_notifications: { method: "POST", path: "/api/notifications" },
@@ -257,6 +260,7 @@ export const apiRoutes = {
257
260
  put_api_conversations_persons_person_id_locale: { method: "PUT", path: "/api/conversations/persons/{person_id}/locale" },
258
261
  put_api_conversations_route_name_thread_mode: { method: "PUT", path: "/api/conversations/{route_name}/thread/mode" },
259
262
  put_api_hooks_topics_topic_verifier: { method: "PUT", path: "/api/hooks/topics/{topic}/verifier" },
263
+ put_api_mcp_config_entries_title_tool_filter: { method: "PUT", path: "/api/mcp-config/entries/{title}/tool-filter" },
260
264
  put_api_presets_name_versions_version_tags: { method: "PUT", path: "/api/presets/{name}/versions/{version}/tags" },
261
265
  put_api_state_templates_name: { method: "PUT", path: "/api/state-templates/{name}" },
262
266
  put_api_states_name: { method: "PUT", path: "/api/states/{name}" },
@@ -310,6 +314,7 @@ export const apiPathPrefixes = [
310
314
  "/api/states/{name}/records/{target_kind}/{target_name}/{kind}",
311
315
  "/api/states/{name}/records/{target_kind}/{target_name}/{kind}/{key}/template-jq",
312
316
  "/api/storage/dirs",
317
+ "/api/storage/entries",
313
318
  "/api/system",
314
319
  "/api/tool-meta/tools",
315
320
  "/api/tools-config",
@@ -327,6 +332,6 @@ export function buildPath(template: ApiPathTemplate, params: Record<string, stri
327
332
  return template.replace(/\{([^}:]+)(:path)?\}/g, (_match, name: string, multi?: string) => {
328
333
  const value = params[name];
329
334
  if (value === undefined) throw new Error(`buildPath: missing param ${name} for ${template}`);
330
- return multi ? value.split('/').map(encodeSegment).join('/') : encodeSegment(value);
335
+ return multi ? value.split('/').map(encodePathPart).join('/') : encodeSegment(value);
331
336
  });
332
337
  }