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
@@ -3,7 +3,6 @@ import { useState } from 'preact/hooks';
3
3
  import htm from 'htm';
4
4
  const html = htm.bind(h);
5
5
  import { t } from '/js/i18n.js';
6
- import { escHtml } from '/js/utils.js';
7
6
  import { dt, Badge, StatsGrid, Empty, ExpandableHelp, Spinner } from './shared.js';
8
7
  import {
9
8
  getSystemPrompts, getSystemPrompt, updateSystemPrompt,
@@ -141,8 +140,8 @@ export default function PromptsTab({ data, reload }) {
141
140
  return html`
142
141
  <div>
143
142
  <button class="adm-btn-action" onClick=${() => { setEditId(null); setEditData(null); }}>← ${t('dashboard.promptsTab')}</button>
144
- <h3 style="margin:12px 0 4px">${escHtml(editData.name)}</h3>
145
- <p class="adm-text-dim adm-text-base" style="margin:0 0 16px">${escHtml(editData.description)}</p>
143
+ <h3 style="margin:12px 0 4px">${editData.name}</h3>
144
+ <p class="adm-text-dim adm-text-base" style="margin:0 0 16px">${editData.description}</p>
146
145
 
147
146
  <!-- Active toggle -->
148
147
  <label class="adm-flex-center adm-mb-md" style="cursor:pointer">
@@ -172,8 +171,8 @@ export default function PromptsTab({ data, reload }) {
172
171
  <div style="margin:8px 0">
173
172
  <strong class="adm-text-base">${t('dashboard.promptsUsedIn')}:</strong>
174
173
  ${editData.usedIn.map(u => u.startsWith('/')
175
- ? html` <a href="${u}" target="_blank" class="adm-prompt-used-tag" style="text-decoration:none;color:var(--accent,#06b6d4)">${escHtml(u)}</a>`
176
- : html` <span class="adm-prompt-used-tag">${escHtml(u)}</span>`
174
+ ? html` <a href="${u}" target="_blank" class="adm-prompt-used-tag" style="text-decoration:none;color:var(--accent,#06b6d4)">${u}</a>`
175
+ : html` <span class="adm-prompt-used-tag">${u}</span>`
177
176
  )}
178
177
  </div>
179
178
  `}
@@ -202,7 +201,7 @@ export default function PromptsTab({ data, reload }) {
202
201
  : versions.map(v => html`
203
202
  <div class="adm-prompt-version-row">
204
203
  <span style="font-weight:600">v${v.version}</span>
205
- <span class="adm-text-dim" style="flex:1">${dt(v.changedAt)} · ${escHtml(v.changedBy)}${v.changeNote ? ` · ${escHtml(v.changeNote)}` : ''}</span>
204
+ <span class="adm-text-dim" style="flex:1">${dt(v.changedAt)} · ${v.changedBy}${v.changeNote ? ` · ${v.changeNote}` : ''}</span>
206
205
  <button class="adm-btn-sm" onClick=${() => handleRestore(v.version)} disabled=${saving}>
207
206
  ${t('dashboard.promptsRestore')}
208
207
  </button>
@@ -263,14 +262,14 @@ export default function PromptsTab({ data, reload }) {
263
262
  <div class="adm-hrow" style="cursor:pointer" onClick=${() => openEdit(p.id)}>
264
263
  <div style="flex:1">
265
264
  <div class="adm-flex-center" style="gap:6px">
266
- <strong>${escHtml(p.name)}</strong>
265
+ <strong>${p.name}</strong>
267
266
  <${Badge} type=${p.active ? 'healthy' : 'critical'} />
268
267
  </div>
269
- <div class="adm-text-sm adm-text-dim">${escHtml(p.description)}</div>
268
+ <div class="adm-text-sm adm-text-dim">${p.description}</div>
270
269
  ${p.usedIn && p.usedIn.length > 0 && html`
271
270
  <div>${p.usedIn.map(u => u.startsWith('/')
272
- ? html`<a href="${u}" target="_blank" class="adm-prompt-used-tag" style="text-decoration:none;color:var(--accent,#06b6d4)" onClick=${e => e.stopPropagation()}>${escHtml(u)}</a> `
273
- : html`<span class="adm-prompt-used-tag">${escHtml(u)}</span> `
271
+ ? html`<a href="${u}" target="_blank" class="adm-prompt-used-tag" style="text-decoration:none;color:var(--accent,#06b6d4)" onClick=${e => e.stopPropagation()}>${u}</a> `
272
+ : html`<span class="adm-prompt-used-tag">${u}</span> `
274
273
  )}</div>
275
274
  `}
276
275
  </div>
@@ -50,6 +50,7 @@ import GeneratorDebugTab from './admin/generator-debug-tab.js';
50
50
  import CapabilitiesAdminTab from './admin/capabilities-tab.js';
51
51
  import AgentTasksAdminTab from './admin/agent-tasks-tab.js';
52
52
  import SharingGroupsAdminTab from './admin/sharing-groups-tab.js';
53
+ import AgentIntegrationAdminTab from './admin/agent-integration-tab.js';
53
54
 
54
55
  // ── Sidebar nav structure ──
55
56
  const NAV_GROUPS = [
@@ -68,6 +69,7 @@ const NAV_GROUPS = [
68
69
  { id: 'owners', icon: '\u{1F464}', key: 'dashboard.owners', component: OwnersTab, count: 'owners' },
69
70
  { id: 'agents', icon: '\u{1F916}', key: 'dashboard.agents', component: AgentsTab, count: 'agents' },
70
71
  { id: 'ghii', icon: '\u{1F511}', key: 'dashboard.ghii', component: GhiiTab, count: 'ghii' },
72
+ { id: 'agent-integration', icon: '\u{1F50C}', key: 'admin.tabs.agentIntegration', component: AgentIntegrationAdminTab },
71
73
  ]},
72
74
  { key: 'dashboard.navData', items: [
73
75
  { id: 'actions', icon: '\u26A1', key: 'dashboard.actions', component: ActionsTab, count: 'actions' },
@@ -51,6 +51,8 @@ function SharingGroupsSection({ showToast }) {
51
51
  const [editingId, setEditingId] = useState(null);
52
52
  const [editName, setEditName] = useState('');
53
53
  const [editDesc, setEditDesc] = useState('');
54
+ const [editRead, setEditRead] = useState(true);
55
+ const [editWrite, setEditWrite] = useState(false);
54
56
  const [saving, setSaving] = useState(false);
55
57
 
56
58
  const loadGroups = useCallback(async () => {
@@ -119,6 +121,8 @@ function SharingGroupsSection({ showToast }) {
119
121
  setEditingId(group.id);
120
122
  setEditName(group.name);
121
123
  setEditDesc(group.description || '');
124
+ setEditRead(group.defaultPermissions?.read ?? true);
125
+ setEditWrite(group.defaultPermissions?.write ?? false);
122
126
  }, []);
123
127
 
124
128
  const handleUpdate = useCallback(async (id) => {
@@ -131,6 +135,7 @@ function SharingGroupsSection({ showToast }) {
131
135
  await groupsApi.updateGroup(id, {
132
136
  name: editName.trim(),
133
137
  description: editDesc.trim() || undefined,
138
+ default_permissions: { read: editRead, write: editWrite },
134
139
  });
135
140
  showToast(t('profile.access.sgUpdated') || 'Group updated');
136
141
  setEditingId(null);
@@ -297,6 +302,19 @@ function SharingGroupsSection({ showToast }) {
297
302
  <textarea class="input-field input-sm" rows="2"
298
303
  value=${editDesc} onInput=${e => setEditDesc(e.target.value)} />
299
304
  </div>
305
+ <div class="form-row">
306
+ <label>${t('profile.access.sgDefaultPerms') || 'Default permissions'}</label>
307
+ <div class="flex-row-wrap">
308
+ <label class="flex-row">
309
+ <input type="checkbox" checked=${editRead} onChange=${() => setEditRead(!editRead)} />
310
+ ${t('profile.access.sgRead') || 'Read'}
311
+ </label>
312
+ <label class="flex-row">
313
+ <input type="checkbox" checked=${editWrite} onChange=${() => setEditWrite(!editWrite)} />
314
+ ${t('profile.access.sgWrite') || 'Write'}
315
+ </label>
316
+ </div>
317
+ </div>
300
318
  <div class="form-actions">
301
319
  <button class="btn-primary btn-sm" onClick=${() => handleUpdate(group.id)} disabled=${saving}>
302
320
  ${saving ? '...' : (t('profile.access.sgSave') || 'Save')}
@@ -0,0 +1,396 @@
1
+ /**
2
+ * @file agent-card.js
3
+ * @description Agent card component with collapsed/expanded states,
4
+ * Two-Zone Header (identity + state-dependent status), and 8-tab bar.
5
+ * @version-history
6
+ * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
7
+ * v1.1.0 -- 2026-05-24 -- Fix C6: remove GAII from Zone 1, M1: add Next prefix, C1: production stats, C2: problem action buttons
8
+ * v1.3.0 -- 2026-05-24 -- Audit fix: use proper down arrow glyph for collapse icon
9
+ * v1.2.0 -- 2026-05-24 -- Add idle state handling, tokens today display, combined delivery label
10
+ */
11
+
12
+ import { h } from 'preact';
13
+ import { useState, useEffect, useCallback } from 'preact/hooks';
14
+ import htm from 'htm';
15
+ import { t } from '/js/i18n.js';
16
+ import { timeAgo } from '/js/utils.js';
17
+ import { detectAgentState, getDefaultTab, getStateColor } from './state-detector.js';
18
+ import { testWebhook, updateWebhook } from '/js/services/agent-integration.js';
19
+ import TabIntegration from './tab-integration.js';
20
+ import TabTasks from './tab-tasks.js';
21
+ import TabMessages from './tab-messages.js';
22
+ import TabDataAccess from './tab-data-access.js';
23
+ import TabDirectives from './tab-directives.js';
24
+ import TabAgentConfig from './tab-agent-config.js';
25
+ import TabActivity from './tab-activity.js';
26
+ import TabServices from './tab-services.js';
27
+
28
+ const html = htm.bind(h);
29
+
30
+ const TABS = [
31
+ { id: 'integration', key: 'profile.agents.detail.tabs.integration' },
32
+ { id: 'tasks', key: 'profile.agents.detail.tabs.tasks' },
33
+ { id: 'messages', key: 'profile.agents.detail.tabs.messages' },
34
+ { id: 'data-access', key: 'profile.agents.detail.tabs.data_access' },
35
+ { id: 'directives', key: 'profile.agents.detail.tabs.directives' },
36
+ { id: 'agent-config', key: 'profile.agents.detail.tabs.agent_config' },
37
+ { id: 'activity', key: 'profile.agents.detail.tabs.activity' },
38
+ { id: 'services', key: 'profile.agents.detail.tabs.services' },
39
+ ];
40
+
41
+ export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, onScopesClick, onDeleteClick, onFederateToggle }) {
42
+ const state = detectAgentState(agent, onboarding);
43
+ const [activeTab, setActiveTab] = useState(null);
44
+
45
+ useEffect(() => {
46
+ if (expanded && activeTab === null) {
47
+ setActiveTab(getDefaultTab(state));
48
+ }
49
+ }, [expanded]);
50
+
51
+ const handleCollapse = useCallback(() => {
52
+ onToggle(agent.name);
53
+ if (expanded) setActiveTab(null);
54
+ }, [expanded, agent.name, onToggle]);
55
+
56
+ if (!expanded) {
57
+ return html`
58
+ <div class="pf-agd-card">
59
+ <div class="pf-agd-collapsed ${state === 'problem' ? 'pf-agd-collapsed--problem' : ''}"
60
+ onClick=${() => onToggle(agent.name)}>
61
+ <span class="pf-agd-expand-icon">▶</span>
62
+ <span class="pf-agd-collapsed-icon">🤖</span>
63
+ <span class="pf-agd-collapsed-name">${agent.display_name || agent.name}</span>
64
+ <div class="pf-agd-collapsed-badges">
65
+ ${renderPlatformBadge(onboarding)}
66
+ ${renderReadinessBadge(state, onboarding)}
67
+ ${agent.federate && html`<span class="pf-agd-badge pf-agd-badge--federation">${t('profile.federated')}</span>`}
68
+ </div>
69
+ <span class="pf-agd-collapsed-stats">
70
+ ${renderDeliveryIndicator(agent)}
71
+ ${renderCollapsedStats(state, agent, onboarding)}
72
+ </span>
73
+ </div>
74
+ </div>
75
+ `;
76
+ }
77
+
78
+ return html`
79
+ <div class="pf-agd-card">
80
+ <div class="pf-agd-expanded">
81
+ <!-- Zone 1: Identity -->
82
+ <div class="pf-agd-zone1" onClick=${handleCollapse}>
83
+ <span class="pf-agd-expand-icon pf-agd-expand-icon--open">▼</span>
84
+ <div class="pf-agd-zone1-identity">
85
+ <span class="pf-agd-zone1-name">${agent.display_name || agent.name}</span>
86
+ </div>
87
+ <div class="pf-agd-zone1-badges">
88
+ ${renderPlatformBadge(onboarding)}
89
+ ${renderReadinessBadge(state, onboarding)}
90
+ ${agent.federate && html`<span class="pf-agd-badge pf-agd-badge--federation">${t('profile.federated')}</span>`}
91
+ </div>
92
+ <span class="pf-agd-zone1-right">
93
+ ${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''}
94
+ </span>
95
+ </div>
96
+
97
+ <!-- Capabilities -->
98
+ ${(agent.technical_capabilities?.length > 0 || agent.domain_capabilities?.length > 0) && html`
99
+ <div class="pf-agd-capabilities">
100
+ ${(agent.technical_capabilities || []).map(c => html`
101
+ <span key=${c.name || c} class="pf-agd-cap-badge pf-agd-cap-badge--tech">${c.name || c}</span>
102
+ `)}
103
+ ${(agent.domain_capabilities || []).map(c => html`
104
+ <span key=${c} class="pf-agd-cap-badge pf-agd-cap-badge--domain">${c}</span>
105
+ `)}
106
+ </div>
107
+ `}
108
+
109
+ <!-- Zone 2: Status -->
110
+ ${renderZone2(state, agent, onboarding, setActiveTab, showToast)}
111
+
112
+ <!-- Tab Bar -->
113
+ <div class="pf-agd-tabs">
114
+ ${TABS.map(tab => {
115
+ const label = t(tab.key);
116
+ return html`
117
+ <button key=${tab.id}
118
+ class="pf-agd-tab ${activeTab === tab.id ? 'pf-agd-tab--active' : ''}"
119
+ onClick=${(e) => { e.stopPropagation(); setActiveTab(tab.id); }}>
120
+ ${label !== tab.key ? label : tab.id.charAt(0).toUpperCase() + tab.id.slice(1)}
121
+ </button>
122
+ `;
123
+ })}
124
+ </div>
125
+
126
+ <!-- Tab Content -->
127
+ <div class="pf-agd-tab-content" onClick=${(e) => e.stopPropagation()}>
128
+ ${renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents)}
129
+ </div>
130
+
131
+ <!-- Card Footer: Scopes + Delete -->
132
+ <div class="pf-agd-card-footer">
133
+ <div class="pf-agd-card-actions">
134
+ ${onScopesClick && html`
135
+ <button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); onScopesClick(agent); }}>
136
+ ${t('profile.agents.scopeUi.manage')}
137
+ </button>
138
+ `}
139
+ ${onFederateToggle && html`
140
+ <button class="btn-ghost btn-sm" onClick=${(e) => { e.stopPropagation(); onFederateToggle(agent); }}>
141
+ ${agent.federate ? t('profile.federated') : t('profile.notFederated')}
142
+ </button>
143
+ `}
144
+ </div>
145
+ ${onDeleteClick && html`
146
+ <button class="btn-danger btn-sm" onClick=${(e) => { e.stopPropagation(); onDeleteClick(agent.name); }}>
147
+ ${t('profile.agents.deleteAgent')}
148
+ </button>
149
+ `}
150
+ </div>
151
+ </div>
152
+ </div>
153
+ `;
154
+ }
155
+
156
+ function renderDeliveryIndicator(agent) {
157
+ const hasWebhook = agent.webhookUrl || agent.webhook_url;
158
+ const hasMcp = agent.mcpEnabled || agent.mcp_enabled;
159
+ const failCount = agent.webhookFailCount ?? 0;
160
+ const icon = hasWebhook ? (failCount >= 5 ? '⚠' : '✓') : '';
161
+
162
+ let label;
163
+ if (hasMcp && hasWebhook) {
164
+ label = t('profile.agents.detail.deliveryMcpWh');
165
+ } else if (hasWebhook) {
166
+ label = t('profile.agents.detail.deliveryWh');
167
+ } else if (hasMcp) {
168
+ label = t('profile.agents.detail.deliveryMcp');
169
+ } else {
170
+ label = t('profile.agents.detail.deliveryPolling');
171
+ }
172
+
173
+ return html`<span class="pf-agd-delivery-indicator">${label}${icon ? ` ${icon}` : ''} </span>`;
174
+ }
175
+
176
+ function renderPlatformBadge(onboarding) {
177
+ const platform = onboarding?.platformName || onboarding?.detectedPlatform;
178
+ if (!platform) return null;
179
+ const version = onboarding?.platformVersion;
180
+ return html`<span class="pf-agd-badge pf-agd-badge--platform">${platform}${version ? ` v${version}` : ''}</span>`;
181
+ }
182
+
183
+ const READINESS_RANKS = { none: 0, basic: 1, standard: 2, advanced: 3, full: 4 };
184
+
185
+ function renderReadinessBadge(state, onboarding) {
186
+ if (state === 'new') {
187
+ return html`<span class="pf-agd-badge pf-agd-badge--readiness-none">--</span>`;
188
+ }
189
+ if (state === 'onboarding') {
190
+ const passed = onboarding?.steps?.filter(s => s.status === 'passed').length ?? 0;
191
+ const total = onboarding?.steps?.length ?? 11;
192
+ return html`<span class="pf-agd-badge pf-agd-badge--readiness-onboarding">${t('profile.agents.detail.state.onboarding')}: ${passed}/${total}</span>`;
193
+ }
194
+ if (state === 'problem') {
195
+ const level = onboarding?.readinessLevel || 'none';
196
+ const score = onboarding?.readinessScore;
197
+ if (!score && score !== 0) return html`<span class="pf-agd-badge pf-agd-badge--readiness-none">--</span>`;
198
+ const label = t(`agentOnboarding.readiness.${level}`);
199
+ const degraded = onboarding?.previousReadinessLevel && (READINESS_RANKS[level] ?? 0) < (READINESS_RANKS[onboarding.previousReadinessLevel] ?? 0);
200
+ return html`<span class="pf-agd-badge pf-agd-badge--readiness-${level} ${degraded ? 'pf-agd-badge--degraded' : ''}">${degraded ? '↓ ' : ''}${label} (${score})</span>`;
201
+ }
202
+ // idle and production both show level + score
203
+ const level = onboarding?.readinessLevel || 'none';
204
+ const score = onboarding?.readinessScore;
205
+ if (!score && score !== 0) return html`<span class="pf-agd-badge pf-agd-badge--readiness-none">--</span>`;
206
+ const label = t(`agentOnboarding.readiness.${level}`);
207
+ return html`<span class="pf-agd-badge pf-agd-badge--readiness-${level}">${label} (${score})</span>`;
208
+ }
209
+
210
+ function renderCollapsedStats(state, agent, onboarding) {
211
+ switch (state) {
212
+ case 'new':
213
+ return html`${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''} | ${t('profile.agents.detail.state.newSummary')}`;
214
+ case 'onboarding': {
215
+ const nextStep = onboarding?.steps?.find(s => s.status === 'pending');
216
+ return html`${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''} ${nextStep ? `| ${t('profile.agents.detail.state.next')}: ${t('agentOnboarding.steps.' + nextStep.id) || nextStep.title || nextStep.id}` : ''}`;
217
+ }
218
+ case 'problem':
219
+ return html`${t('profile.agents.detail.state.problemSummary')} | ${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''}`;
220
+ case 'idle':
221
+ case 'production':
222
+ default: {
223
+ const parts = [];
224
+ if (agent.last_seen) parts.push(`${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}`);
225
+ if (agent.taskStats) {
226
+ const { done, active: act } = agent.taskStats;
227
+ if (done || act) parts.push(`${t('profile.agents.detail.today')}: ${done || 0} ${t('profile.agents.detail.done')}${act ? `, ${act} ${t('profile.agents.detail.active')}` : ''}`);
228
+ }
229
+ if (agent.tokensUsedToday != null) parts.push(`${t('profile.agents.detail.tokensToday')}: ${agent.tokensUsedToday.toLocaleString()}`);
230
+ return html`${parts.join(' | ')}`;
231
+ }
232
+ }
233
+ }
234
+
235
+ function renderZone2(state, agent, onboarding, setActiveTab, showToast) {
236
+ switch (state) {
237
+ case 'new':
238
+ return html`
239
+ <div class="pf-agd-zone2 pf-agd-zone2--new">
240
+ <div class="pf-agd-zone2-title">${t('profile.agents.detail.zone2.newTitle')}</div>
241
+ <div class="pf-agd-zone2-desc">${t('profile.agents.detail.zone2.newDesc')}</div>
242
+ <div class="pf-agd-zone2-actions">
243
+ <button class="btn-primary btn-sm" onClick=${(e) => { e.stopPropagation(); setActiveTab('integration'); }}>
244
+ ${t('profile.agents.detail.zone2.goToIntegration')}
245
+ </button>
246
+ </div>
247
+ </div>
248
+ `;
249
+ case 'onboarding': {
250
+ const steps = onboarding?.steps || [];
251
+ const passed = steps.filter(s => s.status === 'passed').length;
252
+ const total = steps.length || 11;
253
+ const pct = Math.round((passed / total) * 100);
254
+ const nextStep = steps.find(s => s.status === 'pending');
255
+ return html`
256
+ <div class="pf-agd-zone2 pf-agd-zone2--onboarding">
257
+ <div class="pf-agd-zone2-title">
258
+ ${t('profile.agents.detail.zone2.onboardingTitle')}: ${passed} / ${total}
259
+ ${nextStep ? html`<span class="pf-agd-zone2-desc"> ${t('profile.agents.detail.state.next')}: ${t('agentOnboarding.steps.' + nextStep.id) || nextStep.title || nextStep.id}</span>` : ''}
260
+ </div>
261
+ <div class="pf-agd-progress-bar">
262
+ <div class="pf-agd-progress-fill" style="width: ${pct}%"></div>
263
+ </div>
264
+ <div class="pf-agd-step-pills">
265
+ ${steps.map(s => html`
266
+ <span key=${s.id} class="pf-agd-step-pill pf-agd-step-pill--${s.status}">
267
+ ${s.status === 'passed' ? '✓' : '○'} ${t('agentOnboarding.steps.' + s.id) || s.title || s.id}
268
+ </span>
269
+ `)}
270
+ </div>
271
+ </div>
272
+ `;
273
+ }
274
+ case 'problem':
275
+ return html`<${ProblemZone2} agent=${agent} setActiveTab=${setActiveTab} showToast=${showToast} />`;
276
+
277
+ case 'idle':
278
+ case 'production':
279
+ default: {
280
+ const tags = agent.tags ?? [];
281
+ const hasWebhook = agent.webhookUrl || agent.webhook_url;
282
+ const hasMcp = agent.mcpEnabled || agent.mcp_enabled;
283
+ let deliveryLabel;
284
+ if (hasMcp && hasWebhook) deliveryLabel = t('profile.agents.detail.deliveryMcpWh');
285
+ else if (hasWebhook) deliveryLabel = t('profile.agents.detail.deliveryWh');
286
+ else if (hasMcp) deliveryLabel = t('profile.agents.detail.deliveryMcp');
287
+ else deliveryLabel = t('profile.agents.detail.deliveryPolling');
288
+ const stats = agent.taskStats;
289
+ return html`
290
+ <div class="pf-agd-zone2 pf-agd-zone2--production">
291
+ <div class="pf-agd-zone2-stats">
292
+ <span>${deliveryLabel}</span>
293
+ ${agent.last_seen ? html`<span>${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}</span>` : ''}
294
+ ${stats && (stats.done || stats.active) ? html`<span>${t('profile.agents.detail.today')}: ${stats.done || 0} ${t('profile.agents.detail.done')}${stats.active ? `, ${stats.active} ${t('profile.agents.detail.active')}` : ''}</span>` : ''}
295
+ ${agent.tokensUsedToday != null ? html`<span>${t('profile.agents.detail.tokensToday')}: ${agent.tokensUsedToday.toLocaleString()}</span>` : ''}
296
+ ${tags.length > 0 ? html`<span>${t('profile.agents.detail.sharedTags')}: ${tags.map(tag => `[${tag}]`).join(' ')}</span>` : ''}
297
+ </div>
298
+ </div>
299
+ `;
300
+ }
301
+ }
302
+ }
303
+
304
+ function ProblemZone2({ agent, setActiveTab, showToast }) {
305
+ const [editingUrl, setEditingUrl] = useState(false);
306
+ const [urlValue, setUrlValue] = useState(agent.webhook_url || agent.webhookUrl || '');
307
+ const [testing, setTesting] = useState(false);
308
+
309
+ async function handleTestWebhook(e) {
310
+ e.stopPropagation();
311
+ setTesting(true);
312
+ try {
313
+ const resp = await testWebhook(agent.name);
314
+ if (resp?.ok !== false) {
315
+ showToast(t('profile.agents.detail.zone2.testWebhookSuccess'));
316
+ } else {
317
+ showToast(t('profile.agents.detail.zone2.testWebhookFailed'), true);
318
+ }
319
+ } catch {
320
+ showToast(t('profile.agents.detail.zone2.testWebhookFailed'), true);
321
+ }
322
+ setTesting(false);
323
+ }
324
+
325
+ async function handleSaveUrl(e) {
326
+ e.stopPropagation();
327
+ try {
328
+ const resp = await updateWebhook(agent.name, { url: urlValue });
329
+ if (resp?.ok !== false) {
330
+ showToast(t('profile.agents.detail.zone2.urlUpdated'));
331
+ setEditingUrl(false);
332
+ } else {
333
+ showToast(t('profile.agents.detail.zone2.urlUpdateFailed'), true);
334
+ }
335
+ } catch {
336
+ showToast(t('profile.agents.detail.zone2.urlUpdateFailed'), true);
337
+ }
338
+ }
339
+
340
+ function handleOverrideReadiness(e) {
341
+ e.stopPropagation();
342
+ setActiveTab('integration');
343
+ }
344
+
345
+ const failCount = agent.webhookFailCount ?? 0;
346
+
347
+ return html`
348
+ <div class="pf-agd-zone2 pf-agd-zone2--problem">
349
+ <div class="pf-agd-zone2-title">${t('profile.agents.detail.zone2.problemTitle')}</div>
350
+ <div class="pf-agd-zone2-desc">
351
+ ${failCount >= 5 ? t('profile.agents.detail.zone2.webhookFailed', { count: failCount }) : ''}
352
+ ${!agent.last_seen ? t('profile.agents.detail.zone2.noTelemetry') : ''}
353
+ </div>
354
+ <div class="pf-agd-zone2-actions">
355
+ <button class="btn-outline btn-sm" onClick=${handleTestWebhook} disabled=${testing}>
356
+ ${t('profile.agents.detail.zone2.testWebhook')}
357
+ </button>
358
+ <button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); setEditingUrl(!editingUrl); }}>
359
+ ${t('profile.agents.detail.zone2.updateUrl')}
360
+ </button>
361
+ <button class="btn-outline btn-sm" onClick=${handleOverrideReadiness}>
362
+ ${t('profile.agents.detail.zone2.overrideReadiness')}
363
+ </button>
364
+ </div>
365
+ ${editingUrl && html`
366
+ <div class="pf-agd-zone2-url-form">
367
+ <input type="text" value=${urlValue}
368
+ onInput=${(e) => setUrlValue(e.target.value)}
369
+ onClick=${(e) => e.stopPropagation()}
370
+ placeholder="https://..." />
371
+ <button class="btn-primary btn-sm" onClick=${handleSaveUrl}>
372
+ ${t('profile.agents.detail.zone2.save')}
373
+ </button>
374
+ <button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); setEditingUrl(false); }}>
375
+ ${t('profile.agents.detail.zone2.cancel')}
376
+ </button>
377
+ </div>
378
+ `}
379
+ </div>
380
+ `;
381
+ }
382
+
383
+ function renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents) {
384
+ const props = { agent, onboarding, session, showToast, agentName: agent.name, allAgents };
385
+ switch (activeTab) {
386
+ case 'integration': return html`<${TabIntegration} ...${props} />`;
387
+ case 'tasks': return html`<${TabTasks} ...${props} />`;
388
+ case 'messages': return html`<${TabMessages} ...${props} />`;
389
+ case 'data-access': return html`<${TabDataAccess} ...${props} />`;
390
+ case 'directives': return html`<${TabDirectives} ...${props} />`;
391
+ case 'agent-config': return html`<${TabAgentConfig} ...${props} />`;
392
+ case 'activity': return html`<${TabActivity} ...${props} />`;
393
+ case 'services': return html`<${TabServices} ...${props} />`;
394
+ default: return null;
395
+ }
396
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @file shared-board.js
3
+ * @description Shared Agent Board component. Shows fleet-wide overview grid
4
+ * above agent cards with mini-cards per agent and shared tag summary.
5
+ * @version-history
6
+ * v1.1.0 -- 2026-05-24 -- Fix: board card name colored by state, fix locale prefix
7
+ * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
8
+ */
9
+
10
+ import { h } from 'preact';
11
+ import { useMemo } from 'preact/hooks';
12
+ import htm from 'htm';
13
+ import { t } from '/js/i18n.js';
14
+ import { detectAgentState, getStateColor } from './state-detector.js';
15
+
16
+ const html = htm.bind(h);
17
+
18
+ export default function SharedBoard({ agents, onboardings, onAgentClick }) {
19
+ if (!agents || agents.length === 0) return null;
20
+
21
+ const agentStates = useMemo(() => {
22
+ return agents.map(agent => ({
23
+ agent,
24
+ state: detectAgentState(agent, onboardings?.[agent.name]),
25
+ onboarding: onboardings?.[agent.name],
26
+ }));
27
+ }, [agents, onboardings]);
28
+
29
+ const allTags = useMemo(() => {
30
+ const tagCounts = {};
31
+ for (const { agent } of agentStates) {
32
+ for (const tag of agent.tags ?? []) {
33
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
34
+ }
35
+ }
36
+ return tagCounts;
37
+ }, [agentStates]);
38
+
39
+ return html`
40
+ <div class="pf-agd-board">
41
+ <div class="pf-agd-board-grid">
42
+ ${agentStates.map(({ agent, state, onboarding }) => html`
43
+ <div
44
+ key=${agent.name}
45
+ class="pf-agd-board-card"
46
+ style="border-left-color: ${getStateColor(state)}"
47
+ onClick=${() => onAgentClick?.(agent.name)}
48
+ >
49
+ <div class="pf-agd-board-card-name" style="color: ${getStateColor(state)}">${agent.display_name || agent.name}</div>
50
+ <div class="pf-agd-board-card-activity">
51
+ ${renderActivitySummary(state, agent, onboarding)}
52
+ </div>
53
+ <div class="pf-agd-board-card-tags">
54
+ ${(agent.tags ?? []).length > 0
55
+ ? (agent.tags ?? []).join(', ')
56
+ : '--'}
57
+ </div>
58
+ </div>
59
+ `)}
60
+ </div>
61
+ ${Object.keys(allTags).length > 0 && html`
62
+ <div class="pf-agd-board-tags">
63
+ ${t('profile.agents.detail.sharedTags')}: ${Object.entries(allTags).map(([tag, count]) =>
64
+ html`<span class="pf-agd-tag-pill" key=${tag}>[${tag}] (${count})</span> `
65
+ )}
66
+ </div>
67
+ `}
68
+ </div>
69
+ `;
70
+ }
71
+
72
+ function renderActivitySummary(state, agent, onboarding) {
73
+ switch (state) {
74
+ case 'new':
75
+ return t('profile.agents.detail.state.newSummary');
76
+ case 'onboarding': {
77
+ const passed = onboarding?.steps?.filter(s => s.status === 'passed').length ?? 0;
78
+ const total = onboarding?.steps?.length ?? 11;
79
+ return `${t('profile.agents.detail.state.onboarding')}: ${passed}/${total}`;
80
+ }
81
+ case 'problem':
82
+ return t('profile.agents.detail.state.problemSummary');
83
+ case 'production':
84
+ default:
85
+ return agent.last_seen
86
+ ? `${t('profile.agents.detail.lastSeen')}: ${formatTimeAgo(agent.last_seen)}`
87
+ : t('profile.agents.detail.state.idle');
88
+ }
89
+ }
90
+
91
+ function formatTimeAgo(isoDate) {
92
+ const diff = Math.floor((Date.now() - new Date(isoDate).getTime()) / 1000);
93
+ if (diff < 60) return `${diff}s`;
94
+ if (diff < 3600) return `${Math.floor(diff / 60)}m`;
95
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
96
+ return `${Math.floor(diff / 86400)}d`;
97
+ }