aimeat 1.7.0 → 1.9.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 (261) hide show
  1. package/dist/.env.example +4 -1
  2. package/dist/locales/en.json +573 -10
  3. package/dist/locales/fi.json +572 -9
  4. package/dist/public/css/views/admin-agent-integration.css +304 -0
  5. package/dist/public/css/views/agents-detail.css +1697 -120
  6. package/dist/public/css/views/profile.css +22 -2
  7. package/dist/public/css/views/public-knowledge-viewer.css +479 -0
  8. package/dist/public/js/i18n.js +9 -3
  9. package/dist/public/js/services/admin-agent-integration.js +52 -0
  10. package/dist/public/js/services/agent-integration.js +80 -0
  11. package/dist/public/js/services/agent-messages.js +3 -1
  12. package/dist/public/js/services/generator-spec-validate.js +23 -0
  13. package/dist/public/js/services/memory.js +39 -0
  14. package/dist/public/lib/live-updates.js +9 -4
  15. package/dist/public/llms-template.txt +106 -0
  16. package/dist/public/spa.html +7 -2
  17. package/dist/public/views/admin/agent-integration-tab.js +390 -0
  18. package/dist/public/views/admin/federation-tab.js +5 -5
  19. package/dist/public/views/admin/prompts-tab.js +9 -10
  20. package/dist/public/views/admin.js +2 -0
  21. package/dist/public/views/profile/access-tab.js +18 -0
  22. package/dist/public/views/profile/agents/agent-card.js +396 -0
  23. package/dist/public/views/profile/agents/shared-board.js +97 -0
  24. package/dist/public/views/profile/agents/state-detector.js +62 -0
  25. package/dist/public/views/profile/agents/tab-activity.js +232 -0
  26. package/dist/public/views/profile/agents/tab-agent-config.js +199 -0
  27. package/dist/public/views/profile/agents/tab-data-access.js +304 -0
  28. package/dist/public/views/profile/agents/tab-directives.js +178 -0
  29. package/dist/public/views/profile/agents/tab-integration.js +505 -0
  30. package/dist/public/views/profile/agents/tab-messages.js +278 -0
  31. package/dist/public/views/profile/agents/tab-services.js +17 -0
  32. package/dist/public/views/profile/agents/tab-tasks.js +17 -0
  33. package/dist/public/views/profile/agents-messages-subtab.js +12 -10
  34. package/dist/public/views/profile/agents-services-subtab.js +17 -11
  35. package/dist/public/views/profile/agents-tab.js +159 -255
  36. package/dist/public/views/profile/agents-task-builder.js +403 -0
  37. package/dist/public/views/profile/agents-tasks-subtab.js +186 -140
  38. package/dist/public/views/profile/generator-detail.js +8 -1
  39. package/dist/public/views/profile/memory-tab.js +371 -7
  40. package/dist/public/views/profile/wallet-tab.js +14 -0
  41. package/dist/public/views/public-knowledge-viewer.js +650 -0
  42. package/dist/src/auth/middleware.d.ts.map +1 -1
  43. package/dist/src/auth/middleware.js +17 -0
  44. package/dist/src/auth/middleware.js.map +1 -1
  45. package/dist/src/config.d.ts +1 -0
  46. package/dist/src/config.d.ts.map +1 -1
  47. package/dist/src/config.js +1 -0
  48. package/dist/src/config.js.map +1 -1
  49. package/dist/src/middleware/readiness-gate.d.ts +18 -0
  50. package/dist/src/middleware/readiness-gate.d.ts.map +1 -0
  51. package/dist/src/middleware/readiness-gate.js +50 -0
  52. package/dist/src/middleware/readiness-gate.js.map +1 -0
  53. package/dist/src/models/agent-capabilities-schemas.d.ts +3 -0
  54. package/dist/src/models/agent-capabilities-schemas.d.ts.map +1 -1
  55. package/dist/src/models/agent-capabilities-schemas.js +3 -0
  56. package/dist/src/models/agent-capabilities-schemas.js.map +1 -1
  57. package/dist/src/models/agent-directives-schemas.d.ts +12 -0
  58. package/dist/src/models/agent-directives-schemas.d.ts.map +1 -1
  59. package/dist/src/models/agent-directives-schemas.js +7 -0
  60. package/dist/src/models/agent-directives-schemas.js.map +1 -1
  61. package/dist/src/models/agent-onboarding-schemas.d.ts +45 -0
  62. package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -0
  63. package/dist/src/models/agent-onboarding-schemas.js +91 -0
  64. package/dist/src/models/agent-onboarding-schemas.js.map +1 -0
  65. package/dist/src/models/webhook-schemas.d.ts +266 -0
  66. package/dist/src/models/webhook-schemas.d.ts.map +1 -0
  67. package/dist/src/models/webhook-schemas.js +137 -0
  68. package/dist/src/models/webhook-schemas.js.map +1 -0
  69. package/dist/src/routes/admin-agent-integration.d.ts +24 -0
  70. package/dist/src/routes/admin-agent-integration.d.ts.map +1 -0
  71. package/dist/src/routes/admin-agent-integration.js +186 -0
  72. package/dist/src/routes/admin-agent-integration.js.map +1 -0
  73. package/dist/src/routes/admin-monitoring.js +12 -7
  74. package/dist/src/routes/admin-monitoring.js.map +1 -1
  75. package/dist/src/routes/agent-activity.d.ts.map +1 -1
  76. package/dist/src/routes/agent-activity.js +17 -0
  77. package/dist/src/routes/agent-activity.js.map +1 -1
  78. package/dist/src/routes/agent-capabilities.d.ts +1 -0
  79. package/dist/src/routes/agent-capabilities.d.ts.map +1 -1
  80. package/dist/src/routes/agent-capabilities.js +9 -0
  81. package/dist/src/routes/agent-capabilities.js.map +1 -1
  82. package/dist/src/routes/agent-directives.d.ts +5 -1
  83. package/dist/src/routes/agent-directives.d.ts.map +1 -1
  84. package/dist/src/routes/agent-directives.js +74 -20
  85. package/dist/src/routes/agent-directives.js.map +1 -1
  86. package/dist/src/routes/agent-integration.d.ts +1 -0
  87. package/dist/src/routes/agent-integration.d.ts.map +1 -1
  88. package/dist/src/routes/agent-integration.js +128 -11
  89. package/dist/src/routes/agent-integration.js.map +1 -1
  90. package/dist/src/routes/agent-messages.d.ts +5 -1
  91. package/dist/src/routes/agent-messages.d.ts.map +1 -1
  92. package/dist/src/routes/agent-messages.js +20 -1
  93. package/dist/src/routes/agent-messages.js.map +1 -1
  94. package/dist/src/routes/agent-onboarding.d.ts +23 -0
  95. package/dist/src/routes/agent-onboarding.d.ts.map +1 -0
  96. package/dist/src/routes/agent-onboarding.js +374 -0
  97. package/dist/src/routes/agent-onboarding.js.map +1 -0
  98. package/dist/src/routes/agent-skill-bundle.d.ts +16 -0
  99. package/dist/src/routes/agent-skill-bundle.d.ts.map +1 -0
  100. package/dist/src/routes/agent-skill-bundle.js +133 -0
  101. package/dist/src/routes/agent-skill-bundle.js.map +1 -0
  102. package/dist/src/routes/agent-tasks.d.ts +8 -1
  103. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  104. package/dist/src/routes/agent-tasks.js +192 -14
  105. package/dist/src/routes/agent-tasks.js.map +1 -1
  106. package/dist/src/routes/agent-telemetry.d.ts +15 -0
  107. package/dist/src/routes/agent-telemetry.d.ts.map +1 -0
  108. package/dist/src/routes/agent-telemetry.js +99 -0
  109. package/dist/src/routes/agent-telemetry.js.map +1 -0
  110. package/dist/src/routes/agent-webhook.d.ts +18 -0
  111. package/dist/src/routes/agent-webhook.d.ts.map +1 -0
  112. package/dist/src/routes/agent-webhook.js +243 -0
  113. package/dist/src/routes/agent-webhook.js.map +1 -0
  114. package/dist/src/routes/agents.d.ts.map +1 -1
  115. package/dist/src/routes/agents.js +203 -4
  116. package/dist/src/routes/agents.js.map +1 -1
  117. package/dist/src/routes/auth.d.ts.map +1 -1
  118. package/dist/src/routes/auth.js +45 -2
  119. package/dist/src/routes/auth.js.map +1 -1
  120. package/dist/src/routes/federation-auth.d.ts.map +1 -1
  121. package/dist/src/routes/federation-auth.js +3 -1
  122. package/dist/src/routes/federation-auth.js.map +1 -1
  123. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  124. package/dist/src/routes/federation-peer.js +11 -2
  125. package/dist/src/routes/federation-peer.js.map +1 -1
  126. package/dist/src/routes/federation-sync.d.ts +1 -0
  127. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  128. package/dist/src/routes/federation-sync.js +29 -0
  129. package/dist/src/routes/federation-sync.js.map +1 -1
  130. package/dist/src/routes/ghii.d.ts.map +1 -1
  131. package/dist/src/routes/ghii.js +42 -5
  132. package/dist/src/routes/ghii.js.map +1 -1
  133. package/dist/src/routes/knowledge.d.ts.map +1 -1
  134. package/dist/src/routes/knowledge.js +19 -7
  135. package/dist/src/routes/knowledge.js.map +1 -1
  136. package/dist/src/routes/memory.d.ts +9 -0
  137. package/dist/src/routes/memory.d.ts.map +1 -1
  138. package/dist/src/routes/memory.js +307 -12
  139. package/dist/src/routes/memory.js.map +1 -1
  140. package/dist/src/routes/portal.d.ts.map +1 -1
  141. package/dist/src/routes/portal.js +1 -0
  142. package/dist/src/routes/portal.js.map +1 -1
  143. package/dist/src/routes/prompts.d.ts +1 -0
  144. package/dist/src/routes/prompts.d.ts.map +1 -1
  145. package/dist/src/routes/prompts.js +40 -1
  146. package/dist/src/routes/prompts.js.map +1 -1
  147. package/dist/src/routes/sharing-groups.d.ts.map +1 -1
  148. package/dist/src/routes/sharing-groups.js +8 -2
  149. package/dist/src/routes/sharing-groups.js.map +1 -1
  150. package/dist/src/routes/wallet.d.ts.map +1 -1
  151. package/dist/src/routes/wallet.js +2 -0
  152. package/dist/src/routes/wallet.js.map +1 -1
  153. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  154. package/dist/src/server-bootstrap/routes-loader.js +47 -3
  155. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  156. package/dist/src/services/config-schema.d.ts.map +1 -1
  157. package/dist/src/services/config-schema.js +2 -1
  158. package/dist/src/services/config-schema.js.map +1 -1
  159. package/dist/src/services/core-jobs.d.ts.map +1 -1
  160. package/dist/src/services/core-jobs.js +3 -2
  161. package/dist/src/services/core-jobs.js.map +1 -1
  162. package/dist/src/services/federation-helpers.d.ts +1 -0
  163. package/dist/src/services/federation-helpers.d.ts.map +1 -1
  164. package/dist/src/services/federation-helpers.js +1 -1
  165. package/dist/src/services/federation-helpers.js.map +1 -1
  166. package/dist/src/services/generator-autopilot.d.ts.map +1 -1
  167. package/dist/src/services/generator-autopilot.js +125 -26
  168. package/dist/src/services/generator-autopilot.js.map +1 -1
  169. package/dist/src/services/generator-prompts/index.d.ts +1 -1
  170. package/dist/src/services/generator-prompts/index.d.ts.map +1 -1
  171. package/dist/src/services/generator-prompts/index.js +1 -1
  172. package/dist/src/services/generator-prompts/index.js.map +1 -1
  173. package/dist/src/services/generator-prompts/spec-validate.d.ts +1 -0
  174. package/dist/src/services/generator-prompts/spec-validate.d.ts.map +1 -1
  175. package/dist/src/services/generator-prompts/spec-validate.js +20 -0
  176. package/dist/src/services/generator-prompts/spec-validate.js.map +1 -1
  177. package/dist/src/services/generator-prompts/validate.d.ts +7 -1
  178. package/dist/src/services/generator-prompts/validate.d.ts.map +1 -1
  179. package/dist/src/services/generator-prompts/validate.js +70 -0
  180. package/dist/src/services/generator-prompts/validate.js.map +1 -1
  181. package/dist/src/services/onboarding-validator.d.ts +22 -0
  182. package/dist/src/services/onboarding-validator.d.ts.map +1 -0
  183. package/dist/src/services/onboarding-validator.js +202 -0
  184. package/dist/src/services/onboarding-validator.js.map +1 -0
  185. package/dist/src/services/platform-detector.d.ts +27 -0
  186. package/dist/src/services/platform-detector.d.ts.map +1 -0
  187. package/dist/src/services/platform-detector.js +73 -0
  188. package/dist/src/services/platform-detector.js.map +1 -0
  189. package/dist/src/services/prompt-defaults.d.ts +1 -0
  190. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  191. package/dist/src/services/prompt-defaults.js +875 -47
  192. package/dist/src/services/prompt-defaults.js.map +1 -1
  193. package/dist/src/services/prompt-seeder.js +1 -1
  194. package/dist/src/services/prompt-seeder.js.map +1 -1
  195. package/dist/src/services/readiness-scorer.d.ts +34 -0
  196. package/dist/src/services/readiness-scorer.d.ts.map +1 -0
  197. package/dist/src/services/readiness-scorer.js +87 -0
  198. package/dist/src/services/readiness-scorer.js.map +1 -0
  199. package/dist/src/services/skill-bundle/generator.d.ts +17 -0
  200. package/dist/src/services/skill-bundle/generator.d.ts.map +1 -0
  201. package/dist/src/services/skill-bundle/generator.js +387 -0
  202. package/dist/src/services/skill-bundle/generator.js.map +1 -0
  203. package/dist/src/services/skill-bundle/generic-adapter.d.ts +16 -0
  204. package/dist/src/services/skill-bundle/generic-adapter.d.ts.map +1 -0
  205. package/dist/src/services/skill-bundle/generic-adapter.js +96 -0
  206. package/dist/src/services/skill-bundle/generic-adapter.js.map +1 -0
  207. package/dist/src/services/skill-bundle/hermes-adapter.d.ts +25 -0
  208. package/dist/src/services/skill-bundle/hermes-adapter.d.ts.map +1 -0
  209. package/dist/src/services/skill-bundle/hermes-adapter.js +302 -0
  210. package/dist/src/services/skill-bundle/hermes-adapter.js.map +1 -0
  211. package/dist/src/services/skill-bundle/types.d.ts +54 -0
  212. package/dist/src/services/skill-bundle/types.d.ts.map +1 -0
  213. package/dist/src/services/skill-bundle/types.js +8 -0
  214. package/dist/src/services/skill-bundle/types.js.map +1 -0
  215. package/dist/src/services/task-stall-detector.d.ts +10 -2
  216. package/dist/src/services/task-stall-detector.d.ts.map +1 -1
  217. package/dist/src/services/task-stall-detector.js +34 -5
  218. package/dist/src/services/task-stall-detector.js.map +1 -1
  219. package/dist/src/services/webhook-dispatcher.d.ts +71 -0
  220. package/dist/src/services/webhook-dispatcher.d.ts.map +1 -0
  221. package/dist/src/services/webhook-dispatcher.js +259 -0
  222. package/dist/src/services/webhook-dispatcher.js.map +1 -0
  223. package/dist/src/storage/interface.d.ts +82 -2
  224. package/dist/src/storage/interface.d.ts.map +1 -1
  225. package/dist/src/storage/providers/mongodb/index.d.ts +20 -1
  226. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  227. package/dist/src/storage/providers/mongodb/index.js +254 -3
  228. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  229. package/dist/src/storage/providers/sqlite/index.d.ts +18 -1
  230. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  231. package/dist/src/storage/providers/sqlite/index.js +173 -4
  232. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  233. package/dist/src/storage/providers/sqlite/repos/agent-directives.d.ts.map +1 -1
  234. package/dist/src/storage/providers/sqlite/repos/agent-directives.js +8 -4
  235. package/dist/src/storage/providers/sqlite/repos/agent-directives.js.map +1 -1
  236. package/dist/src/storage/providers/sqlite/repos/agent.d.ts.map +1 -1
  237. package/dist/src/storage/providers/sqlite/repos/agent.js +25 -4
  238. package/dist/src/storage/providers/sqlite/repos/agent.js.map +1 -1
  239. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  240. package/dist/src/storage/providers/sqlite/schema.js +73 -1
  241. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  242. package/dist/src/storage/repositories/agent-onboarding.repository.d.ts +16 -0
  243. package/dist/src/storage/repositories/agent-onboarding.repository.d.ts.map +1 -0
  244. package/dist/src/storage/repositories/agent-onboarding.repository.js +8 -0
  245. package/dist/src/storage/repositories/agent-onboarding.repository.js.map +1 -0
  246. package/dist/src/storage/repositories/agent-webhook.repository.d.ts +21 -0
  247. package/dist/src/storage/repositories/agent-webhook.repository.d.ts.map +1 -0
  248. package/dist/src/storage/repositories/agent-webhook.repository.js +2 -0
  249. package/dist/src/storage/repositories/agent-webhook.repository.js.map +1 -0
  250. package/dist/src/storage/repositories/agent.repository.d.ts +1 -0
  251. package/dist/src/storage/repositories/agent.repository.d.ts.map +1 -1
  252. package/dist/src/storage/repositories/index.d.ts +2 -0
  253. package/dist/src/storage/repositories/index.d.ts.map +1 -1
  254. package/dist/src/utils/env-config.d.ts.map +1 -1
  255. package/dist/src/utils/env-config.js +7 -1
  256. package/dist/src/utils/env-config.js.map +1 -1
  257. package/dist/src/utils/url-validator.d.ts.map +1 -1
  258. package/dist/src/utils/url-validator.js +3 -0
  259. package/dist/src/utils/url-validator.js.map +1 -1
  260. package/package.json +1 -1
  261. package/prisma/schema.prisma +67 -7
@@ -8,6 +8,7 @@
8
8
  * - validateDataApiSpec: structural validation of data API spec JSON
9
9
  * - validateComponentSpec: structural validation of component spec JSON
10
10
  * - validateAppDomainSpec: structural validation of app-domain spec JSON
11
+ * - validateAppSpec: structural validation of app (HTML page) spec JSON
11
12
  * - validateSpecAgainstProbe: compare spec declarations against probe results
12
13
  * @usage
13
14
  * import { validateExtensionSpec, validateSpecAgainstProbe } from '/js/services/generator-spec-validate.js';
@@ -122,6 +123,28 @@ export function validateAppDomainSpec(spec) {
122
123
  return { valid: errors.length === 0, errors };
123
124
  }
124
125
 
126
+ /**
127
+ * Validate that an app (HTML page) spec has required structure.
128
+ * @param {object} spec - Parsed app spec JSON
129
+ * @returns {{ valid: boolean, errors: string[] }}
130
+ */
131
+ export function validateAppSpec(spec) {
132
+ const errors = [];
133
+ if (!spec) return { valid: false, errors: ['Spec is null or undefined'] };
134
+
135
+ if (!spec.name) errors.push('Missing spec.name');
136
+ if (spec.name && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(spec.name)) {
137
+ errors.push(`spec.name "${spec.name}" must be ASCII kebab-case`);
138
+ }
139
+ if (!spec.title) errors.push('Missing spec.title');
140
+ if (!spec.appDomainLib) errors.push('Missing spec.appDomainLib — must reference the app-domain cortex libName');
141
+ if (!Array.isArray(spec.cortexDependencies) || spec.cortexDependencies.length === 0) {
142
+ errors.push('Missing or empty spec.cortexDependencies — must list all cortex registeredAs names');
143
+ }
144
+
145
+ return { valid: errors.length === 0, errors };
146
+ }
147
+
125
148
  /**
126
149
  * Compare extension spec against actual probe results.
127
150
  * SPEC IS KING: if probe response doesn't match spec, the CODE is wrong.
@@ -123,3 +123,42 @@ export async function pullFromHome(key) {
123
123
  export async function pushToHome(key) {
124
124
  return apiPost('/v1/memory/push-home', { key });
125
125
  }
126
+
127
+ /** List memory entries on the home node (federated sessions). */
128
+ export async function listHomeMemories() {
129
+ const resp = await apiPost('/v1/memory/list-home', {});
130
+ return resp?.data?.entries || [];
131
+ }
132
+
133
+ /** List memory entries on a remote peer node (home sessions). */
134
+ export async function listRemoteMemories(peerNodeId) {
135
+ const resp = await apiPost('/v1/memory/list-remote', { peer_node_id: peerNodeId });
136
+ return resp?.data?.entries || [];
137
+ }
138
+
139
+ /** Pull a memory entry from a remote peer node (home sessions). */
140
+ export async function pullFromRemote(peerNodeId, key) {
141
+ return apiPost('/v1/memory/pull-remote', { peer_node_id: peerNodeId, key });
142
+ }
143
+
144
+ /** Discover public memory entries from other users on this node. */
145
+ export async function discoverPublicMemories(opts = {}) {
146
+ const params = new URLSearchParams();
147
+ if (opts.prefix) params.set('prefix', opts.prefix);
148
+ if (opts.owner) params.set('owner', opts.owner);
149
+ if (opts.q) params.set('q', opts.q);
150
+ if (opts.limit) params.set('limit', String(opts.limit));
151
+ if (opts.offset) params.set('offset', String(opts.offset));
152
+ const qs = params.toString();
153
+ const data = await apiGet('/v1/memory/discover' + (qs ? '?' + qs : ''));
154
+ return data?.data || { items: [], total: 0 };
155
+ }
156
+
157
+ /** Copy a public memory entry from another user to your own memory. */
158
+ export async function copyPublicMemory(sourceGaii, key, visibility) {
159
+ return apiPost('/v1/memory/copy', {
160
+ source_gaii: sourceGaii,
161
+ key,
162
+ visibility: visibility || 'private',
163
+ });
164
+ }
@@ -29,7 +29,12 @@ async function _open() {
29
29
  method: 'POST',
30
30
  headers: { 'Authorization': `Bearer ${jwt}` },
31
31
  });
32
- if (!resp.ok) return;
32
+ if (!resp.ok) {
33
+ console.warn('[SSE] Ticket request failed:', resp.status);
34
+ reconnectTimer = setTimeout(() => { if (refCount > 0) _open(); }, reconnectDelay);
35
+ reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
36
+ return;
37
+ }
33
38
  const body = await resp.json();
34
39
  const ticket = body.data.ticket;
35
40
 
@@ -39,12 +44,12 @@ async function _open() {
39
44
  reconnectDelay = 5000; // reset on successful connection
40
45
  };
41
46
 
42
- es.onmessage = () => {
43
- reconnectDelay = 5000; // reset on successful message
47
+ es.onmessage = (event) => {
48
+ reconnectDelay = 5000;
44
49
  clearTimeout(debounceTimer);
45
50
  debounceTimer = setTimeout(() => {
46
51
  listeners.forEach(fn => fn());
47
- }, 2000);
52
+ }, 500);
48
53
  };
49
54
 
50
55
  es.onerror = () => {
@@ -801,6 +801,112 @@ On approval (HTTP 200):
801
801
  - `gaii` — your identity on this node
802
802
  - `token` — use for all API calls: `Authorization: Bearer <token>`
803
803
 
804
+ ## Agent API Quick Reference
805
+
806
+ All agent endpoints use `/v1/agents/me/` which resolves to your agent name. Header: `Authorization: Bearer <token>`
807
+
808
+ ### Capabilities
809
+
810
+ ```
811
+ PUT /v1/agents/me/capabilities
812
+ {
813
+ "technical": [
814
+ { "name": "memory", "type": "skill" },
815
+ { "name": "tasks", "type": "skill" },
816
+ { "name": "web_scraping", "type": "tool" }
817
+ ],
818
+ "domain": ["grocery_monitoring", "data_analysis"],
819
+ "languages": ["en", "fi"],
820
+ "modules_loaded": ["tier1", "tier1/tasks", "tier1/messages"],
821
+ "limitations": ["session-scoped runtime"]
822
+ }
823
+ ```
824
+
825
+ ### Tasks — Propose todos
826
+
827
+ ```
828
+ PATCH /v1/agents/me/tasks/{id}
829
+ {
830
+ "todos": [
831
+ { "title": "Check connectivity", "description": "Verify API access", "order": 1, "environment": "aimeat" },
832
+ { "title": "Write report", "description": "Generate analysis", "order": 2, "environment": "agent" }
833
+ ]
834
+ }
835
+ ```
836
+ Environment: `aimeat` (runs against AIMEAT API) or `agent` (runs in your local environment).
837
+
838
+ ### Tasks — Update a todo
839
+
840
+ ```
841
+ PATCH /v1/agents/me/tasks/{id}/todos/{todoId}
842
+ { "status": "done" }
843
+ ```
844
+ Valid statuses: `pending`, `active`, `done`, `failed`, `skipped`
845
+
846
+ ### Tasks — Complete
847
+
848
+ ```
849
+ POST /v1/agents/me/tasks/{id}/complete
850
+ { "summary": "All steps executed successfully" }
851
+ ```
852
+
853
+ ### Telemetry
854
+
855
+ ```
856
+ POST /v1/agents/me/telemetry
857
+ {
858
+ "type": "llm_call",
859
+ "tokens_in": 1523,
860
+ "tokens_out": 847,
861
+ "model": "qwen/qwen3.6-plus",
862
+ "duration_ms": 3200
863
+ }
864
+ ```
865
+ Types: `llm_call`, `tool_call`, `agent_report`
866
+
867
+ ### Messages — Send
868
+
869
+ ```
870
+ POST /v1/agents/me/messages
871
+ {
872
+ "thread_id": "optional-thread-id",
873
+ "content": "Hello from my agent",
874
+ "direction": "outbound"
875
+ }
876
+ ```
877
+
878
+ ### Onboarding — Confirm a step
879
+
880
+ ```
881
+ POST /v1/agents/me/onboarding/step/{stepId}
882
+ ```
883
+ Step IDs: `authenticate`, `identify_platform`, `install_skill`, `report_capabilities`, `read_directives`, `send_test_message`, `configure_delivery`, `report_telemetry`, `accept_test_task`, `complete_test_task`, `declare_services`
884
+
885
+ Some steps auto-validate when you GET /v1/agents/me/onboarding. The test task auto-starts after you propose todos.
886
+
887
+ ### Memory — Write
888
+
889
+ ```
890
+ POST /v1/memory
891
+ {
892
+ "key": "agents.my-agent.commands",
893
+ "value": [
894
+ { "name": "/status", "description": "Show agent status", "category": "general" },
895
+ { "name": "/inbox", "description": "Check inbox", "category": "tasks" }
896
+ ],
897
+ "visibility": "owner"
898
+ }
899
+ ```
900
+ The value for commands MUST be a flat array of `{ name, description, category }`. Each name starts with `/`.
901
+ Visibility: `private` (only you), `owner` (you + owner), `public` (everyone)
902
+
903
+ ### Inbox — Poll
904
+
905
+ ```
906
+ GET /v1/agents/me/inbox
907
+ ```
908
+ Returns: `{ "queued_tasks": [...], "active_tasks": [...], "pending_messages": [...] }`
909
+
804
910
  ## Connecting: MCP (OAuth 2.1)
805
911
 
806
912
  For MCP-capable clients (Claude, Cursor, etc.) that support the Model Context Protocol.
@@ -66,6 +66,8 @@
66
66
  <link rel="stylesheet" href="/css/views/capabilities.css">
67
67
  <link rel="stylesheet" href="/css/views/agents-detail.css">
68
68
  <link rel="stylesheet" href="/css/views/admin.css">
69
+ <link rel="stylesheet" href="/css/views/admin-agent-integration.css">
70
+ <link rel="stylesheet" href="/css/views/public-knowledge-viewer.css">
69
71
  <script type="importmap">
70
72
  {
71
73
  "imports": {
@@ -143,7 +145,7 @@
143
145
  "/lib/minidenticons.min.js": "/lib/minidenticons.min.js",
144
146
  "/lib/yaml.mjs": "/lib/yaml.mjs",
145
147
  "/views/profile/agents-tasks-subtab.js": "/views/profile/agents-tasks-subtab.js",
146
- "/views/profile/agents-directives-subtab.js": "/views/profile/agents-directives-subtab.js",
148
+ "/views/profile/agents-task-builder.js": "/views/profile/agents-task-builder.js",
147
149
  "/js/services/agent-tasks.js": "/js/services/agent-tasks.js",
148
150
  "/js/services/agent-directives.js": "/js/services/agent-directives.js",
149
151
  "/js/services/agent-capabilities.js": "/js/services/agent-capabilities.js",
@@ -154,7 +156,9 @@
154
156
  "/views/profile/agents-services-subtab.js": "/views/profile/agents-services-subtab.js",
155
157
  "/views/profile/agents-messages-subtab.js": "/views/profile/agents-messages-subtab.js",
156
158
  "/js/services/agent-services.js": "/js/services/agent-services.js",
157
- "/js/services/agent-messages.js": "/js/services/agent-messages.js"
159
+ "/js/services/agent-messages.js": "/js/services/agent-messages.js",
160
+ "/js/services/agent-integration.js": "/js/services/agent-integration.js",
161
+ "/js/services/admin-agent-integration.js": "/js/services/admin-agent-integration.js"
158
162
  }
159
163
  }
160
164
  </script>
@@ -225,6 +229,7 @@
225
229
  '/v1/portfolio': () => import('/views/portfolio.js' + B),
226
230
  '/v1/admin': () => import('/views/admin.js' + B),
227
231
  '/v1/help': () => import('/views/help.js' + B),
232
+ '/v1/publicknowledgeviewer': () => import('/views/public-knowledge-viewer.js' + B),
228
233
  };
229
234
 
230
235
  // Match route, handling query params for special views
@@ -0,0 +1,390 @@
1
+ /**
2
+ * @file agent-integration-tab.js
3
+ * @description Admin dashboard Agent Integration tab. Four sections:
4
+ * Platform Registry, Onboarding Overview (with readiness distribution),
5
+ * Skill Bundle Management, Bundle Templates.
6
+ * @version-history
7
+ * v1.0.0 -- 2026-05-24 -- Initial creation for Governance Phase C
8
+ * v1.1.0 -- 2026-05-24 -- Fix M8 M9 F19 F20 F21 audit findings
9
+ * v1.2.0 -- 2026-05-24 -- Move readiness into onboarding, add bundle templates, add notify button
10
+ */
11
+ import { h } from 'preact';
12
+ import { useState, useEffect, useCallback } from 'preact/hooks';
13
+ import htm from 'htm';
14
+ const html = htm.bind(h);
15
+ import { t } from '/js/i18n.js';
16
+ import { useViewCSS } from '/components/useViewCSS.js';
17
+ import { num, dt, Empty } from './shared.js';
18
+ import * as api from '/js/services/admin-agent-integration.js';
19
+
20
+
21
+ export default function AgentIntegrationTab({ data, session }) {
22
+ useViewCSS('/css/views/admin-agent-integration.css');
23
+ const [platforms, setPlatforms] = useState([]);
24
+ const [onboarding, setOnboarding] = useState(null);
25
+ const [readiness, setReadiness] = useState(null);
26
+ const [bundles, setBundles] = useState(null);
27
+ const [loading, setLoading] = useState(true);
28
+ const [regenerating, setRegenerating] = useState(false);
29
+
30
+ const loadData = useCallback(async () => {
31
+ if (!session) return;
32
+ try {
33
+ const [platRes, onbRes, readRes, bundRes] = await Promise.all([
34
+ api.getPlatforms(session),
35
+ api.getOnboardingOverview(session),
36
+ api.getReadinessDistribution(session),
37
+ api.getSkillBundles(session),
38
+ ]);
39
+ setPlatforms(platRes.data?.platforms || []);
40
+ setOnboarding(onbRes.data || null);
41
+ setReadiness(readRes.data || null);
42
+ setBundles(bundRes.data?.bundles || null);
43
+ } catch { /* API unavailable */ }
44
+ setLoading(false);
45
+ }, [session]);
46
+
47
+ useEffect(() => { loadData(); }, [loadData]);
48
+
49
+ useEffect(() => {
50
+ const handler = () => { loadData(); };
51
+ window.addEventListener('aimeat-live-update', handler);
52
+ return () => window.removeEventListener('aimeat-live-update', handler);
53
+ }, [loadData]);
54
+
55
+ if (loading) return html`<div class="adm-card">${t('common.loading')}</div>`;
56
+
57
+ const handleRegenerate = async () => {
58
+ setRegenerating(true);
59
+ try {
60
+ await api.regenerateBundles(session);
61
+ await loadData();
62
+ } catch { /* ignore */ }
63
+ setRegenerating(false);
64
+ };
65
+
66
+ const totalAgents = platforms.reduce((sum, p) => sum + (p.agent_count || 0), 0);
67
+
68
+ return html`
69
+ <div>
70
+ ${renderPlatformRegistry(platforms, totalAgents, session, loadData)}
71
+ ${renderOnboardingOverview(onboarding, readiness, session, loadData)}
72
+ ${renderSkillBundles(bundles, handleRegenerate, regenerating, session)}
73
+ ${renderBundleTemplates()}
74
+ </div>
75
+ `;
76
+ }
77
+
78
+ /* ── M8: Merged Platform column (name + ID as secondary text) ── */
79
+ function renderPlatformRegistry(platforms, totalAgents, session, loadData) {
80
+ const [showForm, setShowForm] = useState(false);
81
+
82
+ return html`
83
+ <div class="adm-agi-section">
84
+ <div class="adm-agi-section-title">${t('admin.agentIntegration.platformRegistry')}</div>
85
+ ${platforms.length === 0
86
+ ? html`<${Empty} text=${t('admin.agentIntegration.noPlatforms')} />`
87
+ : html`
88
+ <table class="adm-agi-table">
89
+ <thead><tr>
90
+ <th>${t('admin.agentIntegration.platform')}</th>
91
+ <th>${t('admin.agentIntegration.agentCount')}</th>
92
+ <th>${t('admin.agentIntegration.adapter')}</th>
93
+ <th>${t('admin.agentIntegration.detectPattern')}</th>
94
+ </tr></thead>
95
+ <tbody>
96
+ ${platforms.map(p => html`
97
+ <tr>
98
+ <td>
99
+ <div>${p.display_name}</div>
100
+ <div class="adm-agi-platform-id">${p.id}</div>
101
+ </td>
102
+ <td>${num(p.agent_count)}</td>
103
+ <td class="adm-agi-mono-sm">${p.bundle_name}</td>
104
+ <td class="adm-agi-mono-sm">${p.detect_pattern || '--'}</td>
105
+ </tr>
106
+ `)}
107
+ </tbody>
108
+ </table>
109
+ <div class="adm-agi-footer">
110
+ ${t('admin.agentIntegration.totalAgents')}: ${num(totalAgents)}
111
+ </div>
112
+ `
113
+ }
114
+ ${showForm
115
+ ? html`<${AddPlatformForm} session=${session} onDone=${() => { setShowForm(false); loadData(); }} onCancel=${() => setShowForm(false)} />`
116
+ : html`<button class="btn-outline adm-agi-add-btn" onClick=${() => setShowForm(true)}>
117
+ ${t('admin.agentIntegration.addPlatform')}
118
+ </button>`
119
+ }
120
+ </div>
121
+ `;
122
+ }
123
+
124
+ /* ── F19: Inline Add Platform form ── */
125
+ function AddPlatformForm({ session, onDone, onCancel }) {
126
+ const [name, setName] = useState('');
127
+ const [id, setId] = useState('');
128
+ const [adapter, setAdapter] = useState('');
129
+ const [pattern, setPattern] = useState('');
130
+ const [submitting, setSubmitting] = useState(false);
131
+
132
+ const handleSubmit = async () => {
133
+ if (!name.trim() || !id.trim()) return;
134
+ setSubmitting(true);
135
+ try {
136
+ await api.registerPlatform(session, {
137
+ id: id.trim(),
138
+ display_name: name.trim(),
139
+ bundle_name: adapter.trim() || undefined,
140
+ detect_pattern: pattern.trim() || undefined,
141
+ });
142
+ onDone();
143
+ } catch { /* ignore */ }
144
+ setSubmitting(false);
145
+ };
146
+
147
+ return html`
148
+ <div class="adm-agi-add-form">
149
+ <div class="adm-agi-add-form-row">
150
+ <label class="adm-agi-add-form-label">${t('admin.agentIntegration.platformName')}</label>
151
+ <input class="adm-agi-add-form-input" value=${name} onInput=${e => setName(e.target.value)} placeholder=${t('admin.agentIntegration.placeholders.name')} />
152
+ </div>
153
+ <div class="adm-agi-add-form-row">
154
+ <label class="adm-agi-add-form-label">${t('admin.agentIntegration.platformId')}</label>
155
+ <input class="adm-agi-add-form-input" value=${id} onInput=${e => setId(e.target.value)} placeholder=${t('admin.agentIntegration.placeholders.id')} />
156
+ </div>
157
+ <div class="adm-agi-add-form-row">
158
+ <label class="adm-agi-add-form-label">${t('admin.agentIntegration.adapterPattern')}</label>
159
+ <input class="adm-agi-add-form-input" value=${adapter} onInput=${e => setAdapter(e.target.value)} placeholder=${t('admin.agentIntegration.placeholders.adapter')} />
160
+ </div>
161
+ <div class="adm-agi-add-form-row">
162
+ <label class="adm-agi-add-form-label">${t('admin.agentIntegration.autoDetectRegex')}</label>
163
+ <input class="adm-agi-add-form-input" value=${pattern} onInput=${e => setPattern(e.target.value)} placeholder=${t('admin.agentIntegration.placeholders.regex')} />
164
+ </div>
165
+ <div class="adm-agi-add-form-actions">
166
+ <button class="btn-primary" onClick=${handleSubmit} disabled=${submitting || !name.trim() || !id.trim()}>
167
+ ${submitting ? t('common.loading') : t('common.add')}
168
+ </button>
169
+ <button class="btn-ghost" onClick=${onCancel}>${t('common.cancel')}</button>
170
+ </div>
171
+ </div>
172
+ `;
173
+ }
174
+
175
+ /* ── M9: Not Started stat card + F20: Stuck section with suggestions + actions ── */
176
+ function renderOnboardingOverview(onboarding, readiness, session, loadData) {
177
+ if (!onboarding) return html`<div class="adm-agi-section"><${Empty} text=${t('common.noData')} /></div>`;
178
+
179
+ const stuck = onboarding.stuck || [];
180
+ const notStarted = onboarding.not_started || 0;
181
+ const readinessTotal = (onboarding.completed || 0) + (onboarding.in_progress || 0) + notStarted;
182
+
183
+ return html`
184
+ <div class="adm-agi-section">
185
+ <div class="adm-agi-section-title">${t('admin.agentIntegration.onboardingOverview')}</div>
186
+ <div class="adm-agi-stats">
187
+ <div class="adm-agi-stat-card">
188
+ <div class="adm-agi-stat-value adm-agi-stat-value--success">${num(onboarding.completed)}</div>
189
+ <div class="adm-agi-stat-label">${t('admin.agentIntegration.completed')}</div>
190
+ </div>
191
+ <div class="adm-agi-stat-card">
192
+ <div class="adm-agi-stat-value adm-agi-stat-value--accent">${num(onboarding.in_progress)}</div>
193
+ <div class="adm-agi-stat-label">${t('admin.agentIntegration.inProgress')}</div>
194
+ </div>
195
+ <div class="adm-agi-stat-card">
196
+ <div class="adm-agi-stat-value">${num(notStarted)}</div>
197
+ <div class="adm-agi-stat-label">${t('admin.agentIntegration.notStarted')}</div>
198
+ </div>
199
+ </div>
200
+ ${stuck.length > 0 ? html`
201
+ <div class="adm-agi-stuck-title">${t('admin.agentIntegration.stuckAgents')}</div>
202
+ <div class="adm-agi-stuck">
203
+ ${stuck.map(s => html`
204
+ <${StuckAgentRow} agent=${s} session=${session} onAction=${loadData} />
205
+ `)}
206
+ </div>
207
+ ` : ''}
208
+ ${renderReadinessDistribution(readiness, readinessTotal)}
209
+ </div>
210
+ `;
211
+ }
212
+
213
+ /* ── F20: Single stuck agent row with suggestion + actions ── */
214
+ function StuckAgentRow({ agent, session, onAction }) {
215
+ const [acting, setActing] = useState(false);
216
+ const suggestion = getSuggestion(agent.current_step);
217
+
218
+ const handleRemind = async () => {
219
+ setActing(true);
220
+ try {
221
+ await api.sendReminder(session, agent.agent_gaii);
222
+ onAction();
223
+ } catch { /* ignore */ }
224
+ setActing(false);
225
+ };
226
+
227
+ const handleSkip = async () => {
228
+ setActing(true);
229
+ try {
230
+ await api.skipOnboardingStep(session, agent.agent_gaii, agent.current_step);
231
+ onAction();
232
+ } catch { /* ignore */ }
233
+ setActing(false);
234
+ };
235
+
236
+ return html`
237
+ <div class="adm-agi-stuck-agent">
238
+ <div class="adm-agi-stuck-info">
239
+ <div>
240
+ <span class="adm-agi-stuck-name">${agent.agent_gaii}</span>
241
+ <div class="adm-agi-stuck-detail">${agent.current_step}</div>
242
+ </div>
243
+ <div class="adm-agi-stuck-detail">
244
+ ${t('admin.agentIntegration.stuckSince')}: ${dt(agent.stuck_since)}
245
+ </div>
246
+ <div class="adm-agi-stuck-suggestion">${suggestion}</div>
247
+ </div>
248
+ <div class="adm-agi-stuck-actions">
249
+ <button class="btn-outline adm-agi-stuck-btn" onClick=${handleRemind} disabled=${acting}>
250
+ ${t('admin.agentIntegration.sendReminder')}
251
+ </button>
252
+ <button class="btn-ghost adm-agi-stuck-btn" onClick=${handleSkip} disabled=${acting}>
253
+ ${t('admin.agentIntegration.skipStep')}
254
+ </button>
255
+ </div>
256
+ </div>
257
+ `;
258
+ }
259
+
260
+ function getSuggestion(stepId) {
261
+ if (stepId?.includes('webhook') || stepId?.includes('delivery'))
262
+ return t('admin.agentIntegration.suggestion.webhook');
263
+ if (stepId?.includes('platform') || stepId?.includes('detect'))
264
+ return t('admin.agentIntegration.suggestion.platform');
265
+ if (stepId?.includes('skill') || stepId?.includes('bundle'))
266
+ return t('admin.agentIntegration.suggestion.skillBundle');
267
+ return t('admin.agentIntegration.suggestion.default');
268
+ }
269
+
270
+ function renderReadinessDistribution(readiness, total) {
271
+ if (!readiness) return '';
272
+ const dist = readiness.distribution || {};
273
+ const levels = ['expert', 'full', 'standard', 'basic'];
274
+
275
+ return html`
276
+ <div class="adm-agi-subsection">
277
+ <div class="adm-agi-section-title">${t('admin.agentIntegration.readinessDistribution')}</div>
278
+ ${total === 0
279
+ ? html`<div class="adm-agi-empty-text">${t('common.noData')}</div>`
280
+ : html`
281
+ ${levels.map(level => {
282
+ const count = dist[level] || 0;
283
+ const pct = total > 0 ? (count / total * 100) : 0;
284
+ return html`
285
+ <div class="adm-agi-readiness-bar">
286
+ <div class="adm-agi-readiness-level">${t(`agentOnboarding.readiness.${level}`)}</div>
287
+ <div class="adm-agi-readiness-track">
288
+ <div class="adm-agi-readiness-fill" style="width:${pct}%" data-level="${level}"></div>
289
+ </div>
290
+ <div class="adm-agi-readiness-count">${count}</div>
291
+ </div>
292
+ `;
293
+ })}
294
+ <div class="adm-agi-footer">
295
+ ${t('admin.agentIntegration.totalAgents')}: ${num(total)}
296
+ </div>
297
+ `
298
+ }
299
+ </div>
300
+ `;
301
+ }
302
+
303
+ /* ── F21: Added "Current version" column to skill bundle table ── */
304
+ function renderSkillBundles(bundles, onRegenerate, regenerating, session) {
305
+ if (!bundles) return '';
306
+ const entries = Object.entries(bundles);
307
+
308
+ const handleNotify = async (platform) => {
309
+ try {
310
+ await api.notifyOutdatedAgents(session, platform);
311
+ window.dispatchEvent(new CustomEvent('aimeat-toast', { detail: { message: t('admin.agentIntegration.notifySuccess') } }));
312
+ } catch { /* ignore */ }
313
+ };
314
+
315
+ return html`
316
+ <div class="adm-agi-section">
317
+ <div class="adm-agi-section-title">${t('admin.agentIntegration.skillBundles')}</div>
318
+ ${entries.length === 0
319
+ ? html`<div class="adm-agi-empty-text">${t('common.noData')}</div>`
320
+ : html`
321
+ <table class="adm-agi-table">
322
+ <thead><tr>
323
+ <th>${t('admin.agentIntegration.platform')}</th>
324
+ <th>${t('admin.agentIntegration.agentCount')}</th>
325
+ <th>${t('admin.agentIntegration.currentVersion')}</th>
326
+ <th>${t('admin.agentIntegration.outdated')}</th>
327
+ <th></th>
328
+ </tr></thead>
329
+ <tbody>
330
+ ${entries.map(([platform, info]) => html`
331
+ <tr>
332
+ <td>${platform}</td>
333
+ <td>${num(info.agents)}</td>
334
+ <td class="adm-agi-mono-sm">${info.version || info.currentVersion || '--'}</td>
335
+ <td>${info.outdated > 0
336
+ ? html`<span class="adm-agi-outdated">${num(info.outdated)}</span>`
337
+ : html`<span class="adm-agi-zero">0</span>`
338
+ }</td>
339
+ <td>${info.outdated > 0 ? html`
340
+ <button class="btn-outline adm-agi-notify-btn" onClick=${() => handleNotify(platform)}>
341
+ ${t('admin.agentIntegration.notifyOutdated')}
342
+ </button>
343
+ ` : ''}</td>
344
+ </tr>
345
+ `)}
346
+ </tbody>
347
+ </table>
348
+ `
349
+ }
350
+ <button class="btn-outline adm-agi-regen-btn" onClick=${onRegenerate} disabled=${regenerating}>
351
+ ${regenerating ? t('common.loading') : t('admin.agentIntegration.regenerateAll')}
352
+ </button>
353
+ </div>
354
+ `;
355
+ }
356
+
357
+ /* ── Bundle Templates (Phase C stub) ── */
358
+ function renderBundleTemplates() {
359
+ const handleComingSoon = () => {
360
+ window.dispatchEvent(new CustomEvent('aimeat-toast', { detail: { message: t('admin.agentIntegration.comingSoon') } }));
361
+ };
362
+
363
+ return html`
364
+ <div class="adm-agi-section">
365
+ <div class="adm-agi-section-title">${t('admin.agentIntegration.bundleTemplates')}</div>
366
+ <div class="adm-agi-stats">
367
+ <div class="adm-agi-stat-card">
368
+ <div class="adm-agi-stat-value">0</div>
369
+ <div class="adm-agi-stat-label">${t('admin.agentIntegration.customReferences')}</div>
370
+ </div>
371
+ <div class="adm-agi-stat-card">
372
+ <div class="adm-agi-stat-value">0</div>
373
+ <div class="adm-agi-stat-label">${t('admin.agentIntegration.customScripts')}</div>
374
+ </div>
375
+ <div class="adm-agi-stat-card">
376
+ <div class="adm-agi-stat-value adm-agi-mono-sm">--</div>
377
+ <div class="adm-agi-stat-label">${t('admin.agentIntegration.lastGenerated')}</div>
378
+ </div>
379
+ </div>
380
+ <div class="adm-agi-btn-row">
381
+ <button class="btn-outline" onClick=${handleComingSoon}>
382
+ ${t('admin.agentIntegration.customizeBundle')}
383
+ </button>
384
+ <button class="btn-outline" onClick=${handleComingSoon}>
385
+ ${t('admin.agentIntegration.previewBundle')}
386
+ </button>
387
+ </div>
388
+ </div>
389
+ `;
390
+ }
@@ -38,10 +38,10 @@ export default function FederationTab({ data, reload }) {
38
38
 
39
39
  useEffect(() => {
40
40
  getConfig().then(res => {
41
- if (res.ok && res.data?.config) {
42
- const c = res.data.config;
43
- setAuthPolicy(c.federation?.auth_policy ?? c.federationAuthPolicy ?? 'disabled');
44
- const scopes = c.federation?.default_scopes ?? c.federationDefaultScopes ?? 'memory:read,catalogue:read';
41
+ if (res.ok && res.data?.schema) {
42
+ const s = res.data.schema;
43
+ setAuthPolicy(s['federation.auth_policy']?.value ?? 'disabled');
44
+ const scopes = s['federation.default_scopes']?.value ?? 'memory:read,catalogue:read';
45
45
  setDefaultScopes(typeof scopes === 'string' ? scopes.split(',').filter(Boolean) : (scopes || []));
46
46
  }
47
47
  setAuthPolicyLoading(false);
@@ -385,7 +385,7 @@ export default function FederationTab({ data, reload }) {
385
385
  ${(p.status === 'active' || p.status === 'degraded') && html`
386
386
  <button class="adm-btn-sm" onClick=${() => doRemove(p.node_id)}>${t('dashboard.fedDepeer')}</button>
387
387
  `}
388
- ${p.status === 'active' && html`
388
+ ${(p.status === 'active' || p.status === 'degraded' || p.status === 'offline' || p.status === 'depeering') && html`
389
389
  <button class="adm-btn-sm adm-text-error" onClick=${() => doEmergencyRemove(p.node_id)}>\u26A0 ${t('dashboard.fedEmergencyDepeer')}</button>
390
390
  `}
391
391
  </td>