aimeat 1.8.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 (199) hide show
  1. package/dist/.env.example +4 -1
  2. package/dist/bin/aimeat.js +0 -0
  3. package/dist/locales/en.json +527 -7
  4. package/dist/locales/fi.json +526 -6
  5. package/dist/public/css/views/admin-agent-integration.css +304 -0
  6. package/dist/public/css/views/agents-detail.css +1573 -146
  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/lib/live-updates.js +9 -4
  13. package/dist/public/llms-template.txt +106 -0
  14. package/dist/public/spa.html +7 -2
  15. package/dist/public/views/admin/agent-integration-tab.js +390 -0
  16. package/dist/public/views/admin.js +2 -0
  17. package/dist/public/views/profile/agents/agent-card.js +396 -0
  18. package/dist/public/views/profile/agents/shared-board.js +97 -0
  19. package/dist/public/views/profile/agents/state-detector.js +62 -0
  20. package/dist/public/views/profile/agents/tab-activity.js +232 -0
  21. package/dist/public/views/profile/agents/tab-agent-config.js +199 -0
  22. package/dist/public/views/profile/agents/tab-data-access.js +304 -0
  23. package/dist/public/views/profile/agents/tab-directives.js +178 -0
  24. package/dist/public/views/profile/agents/tab-integration.js +505 -0
  25. package/dist/public/views/profile/agents/tab-messages.js +278 -0
  26. package/dist/public/views/profile/agents/tab-services.js +17 -0
  27. package/dist/public/views/profile/agents/tab-tasks.js +17 -0
  28. package/dist/public/views/profile/agents-messages-subtab.js +11 -10
  29. package/dist/public/views/profile/agents-services-subtab.js +17 -11
  30. package/dist/public/views/profile/agents-tab.js +159 -259
  31. package/dist/public/views/profile/agents-task-builder.js +403 -0
  32. package/dist/public/views/profile/agents-tasks-subtab.js +114 -127
  33. package/dist/public/views/public-knowledge-viewer.js +650 -0
  34. package/dist/src/auth/middleware.d.ts.map +1 -1
  35. package/dist/src/auth/middleware.js +17 -0
  36. package/dist/src/auth/middleware.js.map +1 -1
  37. package/dist/src/config.d.ts +1 -0
  38. package/dist/src/config.d.ts.map +1 -1
  39. package/dist/src/config.js +1 -0
  40. package/dist/src/config.js.map +1 -1
  41. package/dist/src/middleware/readiness-gate.d.ts +18 -0
  42. package/dist/src/middleware/readiness-gate.d.ts.map +1 -0
  43. package/dist/src/middleware/readiness-gate.js +50 -0
  44. package/dist/src/middleware/readiness-gate.js.map +1 -0
  45. package/dist/src/models/agent-directives-schemas.d.ts +12 -0
  46. package/dist/src/models/agent-directives-schemas.d.ts.map +1 -1
  47. package/dist/src/models/agent-directives-schemas.js +7 -0
  48. package/dist/src/models/agent-directives-schemas.js.map +1 -1
  49. package/dist/src/models/agent-onboarding-schemas.d.ts +45 -0
  50. package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -0
  51. package/dist/src/models/agent-onboarding-schemas.js +91 -0
  52. package/dist/src/models/agent-onboarding-schemas.js.map +1 -0
  53. package/dist/src/models/webhook-schemas.d.ts +266 -0
  54. package/dist/src/models/webhook-schemas.d.ts.map +1 -0
  55. package/dist/src/models/webhook-schemas.js +137 -0
  56. package/dist/src/models/webhook-schemas.js.map +1 -0
  57. package/dist/src/routes/admin-agent-integration.d.ts +24 -0
  58. package/dist/src/routes/admin-agent-integration.d.ts.map +1 -0
  59. package/dist/src/routes/admin-agent-integration.js +186 -0
  60. package/dist/src/routes/admin-agent-integration.js.map +1 -0
  61. package/dist/src/routes/agent-activity.d.ts.map +1 -1
  62. package/dist/src/routes/agent-activity.js +17 -0
  63. package/dist/src/routes/agent-activity.js.map +1 -1
  64. package/dist/src/routes/agent-directives.d.ts +5 -1
  65. package/dist/src/routes/agent-directives.d.ts.map +1 -1
  66. package/dist/src/routes/agent-directives.js +74 -20
  67. package/dist/src/routes/agent-directives.js.map +1 -1
  68. package/dist/src/routes/agent-integration.d.ts +1 -0
  69. package/dist/src/routes/agent-integration.d.ts.map +1 -1
  70. package/dist/src/routes/agent-integration.js +128 -11
  71. package/dist/src/routes/agent-integration.js.map +1 -1
  72. package/dist/src/routes/agent-messages.d.ts +5 -1
  73. package/dist/src/routes/agent-messages.d.ts.map +1 -1
  74. package/dist/src/routes/agent-messages.js +20 -1
  75. package/dist/src/routes/agent-messages.js.map +1 -1
  76. package/dist/src/routes/agent-onboarding.d.ts +23 -0
  77. package/dist/src/routes/agent-onboarding.d.ts.map +1 -0
  78. package/dist/src/routes/agent-onboarding.js +374 -0
  79. package/dist/src/routes/agent-onboarding.js.map +1 -0
  80. package/dist/src/routes/agent-skill-bundle.d.ts +16 -0
  81. package/dist/src/routes/agent-skill-bundle.d.ts.map +1 -0
  82. package/dist/src/routes/agent-skill-bundle.js +133 -0
  83. package/dist/src/routes/agent-skill-bundle.js.map +1 -0
  84. package/dist/src/routes/agent-tasks.d.ts +7 -1
  85. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  86. package/dist/src/routes/agent-tasks.js +183 -7
  87. package/dist/src/routes/agent-tasks.js.map +1 -1
  88. package/dist/src/routes/agent-telemetry.d.ts +15 -0
  89. package/dist/src/routes/agent-telemetry.d.ts.map +1 -0
  90. package/dist/src/routes/agent-telemetry.js +99 -0
  91. package/dist/src/routes/agent-telemetry.js.map +1 -0
  92. package/dist/src/routes/agent-webhook.d.ts +18 -0
  93. package/dist/src/routes/agent-webhook.d.ts.map +1 -0
  94. package/dist/src/routes/agent-webhook.js +243 -0
  95. package/dist/src/routes/agent-webhook.js.map +1 -0
  96. package/dist/src/routes/agents.d.ts.map +1 -1
  97. package/dist/src/routes/agents.js +203 -4
  98. package/dist/src/routes/agents.js.map +1 -1
  99. package/dist/src/routes/auth.d.ts.map +1 -1
  100. package/dist/src/routes/auth.js +45 -2
  101. package/dist/src/routes/auth.js.map +1 -1
  102. package/dist/src/routes/ghii.d.ts.map +1 -1
  103. package/dist/src/routes/ghii.js +42 -5
  104. package/dist/src/routes/ghii.js.map +1 -1
  105. package/dist/src/routes/knowledge.d.ts.map +1 -1
  106. package/dist/src/routes/knowledge.js +19 -7
  107. package/dist/src/routes/knowledge.js.map +1 -1
  108. package/dist/src/routes/memory.js +3 -3
  109. package/dist/src/routes/memory.js.map +1 -1
  110. package/dist/src/routes/portal.d.ts.map +1 -1
  111. package/dist/src/routes/portal.js +1 -0
  112. package/dist/src/routes/portal.js.map +1 -1
  113. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  114. package/dist/src/server-bootstrap/routes-loader.js +16 -3
  115. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  116. package/dist/src/services/config-schema.d.ts.map +1 -1
  117. package/dist/src/services/config-schema.js +2 -1
  118. package/dist/src/services/config-schema.js.map +1 -1
  119. package/dist/src/services/core-jobs.d.ts.map +1 -1
  120. package/dist/src/services/core-jobs.js +3 -2
  121. package/dist/src/services/core-jobs.js.map +1 -1
  122. package/dist/src/services/onboarding-validator.d.ts +22 -0
  123. package/dist/src/services/onboarding-validator.d.ts.map +1 -0
  124. package/dist/src/services/onboarding-validator.js +202 -0
  125. package/dist/src/services/onboarding-validator.js.map +1 -0
  126. package/dist/src/services/platform-detector.d.ts +27 -0
  127. package/dist/src/services/platform-detector.d.ts.map +1 -0
  128. package/dist/src/services/platform-detector.js +73 -0
  129. package/dist/src/services/platform-detector.js.map +1 -0
  130. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  131. package/dist/src/services/prompt-defaults.js +129 -123
  132. package/dist/src/services/prompt-defaults.js.map +1 -1
  133. package/dist/src/services/readiness-scorer.d.ts +34 -0
  134. package/dist/src/services/readiness-scorer.d.ts.map +1 -0
  135. package/dist/src/services/readiness-scorer.js +87 -0
  136. package/dist/src/services/readiness-scorer.js.map +1 -0
  137. package/dist/src/services/skill-bundle/generator.d.ts +17 -0
  138. package/dist/src/services/skill-bundle/generator.d.ts.map +1 -0
  139. package/dist/src/services/skill-bundle/generator.js +387 -0
  140. package/dist/src/services/skill-bundle/generator.js.map +1 -0
  141. package/dist/src/services/skill-bundle/generic-adapter.d.ts +16 -0
  142. package/dist/src/services/skill-bundle/generic-adapter.d.ts.map +1 -0
  143. package/dist/src/services/skill-bundle/generic-adapter.js +96 -0
  144. package/dist/src/services/skill-bundle/generic-adapter.js.map +1 -0
  145. package/dist/src/services/skill-bundle/hermes-adapter.d.ts +25 -0
  146. package/dist/src/services/skill-bundle/hermes-adapter.d.ts.map +1 -0
  147. package/dist/src/services/skill-bundle/hermes-adapter.js +302 -0
  148. package/dist/src/services/skill-bundle/hermes-adapter.js.map +1 -0
  149. package/dist/src/services/skill-bundle/types.d.ts +54 -0
  150. package/dist/src/services/skill-bundle/types.d.ts.map +1 -0
  151. package/dist/src/services/skill-bundle/types.js +8 -0
  152. package/dist/src/services/skill-bundle/types.js.map +1 -0
  153. package/dist/src/services/task-stall-detector.d.ts +10 -2
  154. package/dist/src/services/task-stall-detector.d.ts.map +1 -1
  155. package/dist/src/services/task-stall-detector.js +34 -5
  156. package/dist/src/services/task-stall-detector.js.map +1 -1
  157. package/dist/src/services/webhook-dispatcher.d.ts +71 -0
  158. package/dist/src/services/webhook-dispatcher.d.ts.map +1 -0
  159. package/dist/src/services/webhook-dispatcher.js +259 -0
  160. package/dist/src/services/webhook-dispatcher.js.map +1 -0
  161. package/dist/src/storage/interface.d.ts +80 -2
  162. package/dist/src/storage/interface.d.ts.map +1 -1
  163. package/dist/src/storage/providers/mongodb/index.d.ts +20 -1
  164. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  165. package/dist/src/storage/providers/mongodb/index.js +250 -2
  166. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  167. package/dist/src/storage/providers/sqlite/index.d.ts +18 -1
  168. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  169. package/dist/src/storage/providers/sqlite/index.js +168 -4
  170. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  171. package/dist/src/storage/providers/sqlite/repos/agent-directives.d.ts.map +1 -1
  172. package/dist/src/storage/providers/sqlite/repos/agent-directives.js +8 -4
  173. package/dist/src/storage/providers/sqlite/repos/agent-directives.js.map +1 -1
  174. package/dist/src/storage/providers/sqlite/repos/agent.d.ts.map +1 -1
  175. package/dist/src/storage/providers/sqlite/repos/agent.js +25 -4
  176. package/dist/src/storage/providers/sqlite/repos/agent.js.map +1 -1
  177. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  178. package/dist/src/storage/providers/sqlite/schema.js +71 -1
  179. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  180. package/dist/src/storage/repositories/agent-onboarding.repository.d.ts +16 -0
  181. package/dist/src/storage/repositories/agent-onboarding.repository.d.ts.map +1 -0
  182. package/dist/src/storage/repositories/agent-onboarding.repository.js +8 -0
  183. package/dist/src/storage/repositories/agent-onboarding.repository.js.map +1 -0
  184. package/dist/src/storage/repositories/agent-webhook.repository.d.ts +21 -0
  185. package/dist/src/storage/repositories/agent-webhook.repository.d.ts.map +1 -0
  186. package/dist/src/storage/repositories/agent-webhook.repository.js +2 -0
  187. package/dist/src/storage/repositories/agent-webhook.repository.js.map +1 -0
  188. package/dist/src/storage/repositories/agent.repository.d.ts +1 -0
  189. package/dist/src/storage/repositories/agent.repository.d.ts.map +1 -1
  190. package/dist/src/storage/repositories/index.d.ts +2 -0
  191. package/dist/src/storage/repositories/index.d.ts.map +1 -1
  192. package/dist/src/utils/env-config.d.ts.map +1 -1
  193. package/dist/src/utils/env-config.js +7 -1
  194. package/dist/src/utils/env-config.js.map +1 -1
  195. package/dist/src/utils/url-validator.d.ts.map +1 -1
  196. package/dist/src/utils/url-validator.js +3 -0
  197. package/dist/src/utils/url-validator.js.map +1 -1
  198. package/package.json +97 -77
  199. package/prisma/schema.prisma +65 -7
@@ -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
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @file state-detector.js
3
+ * @description Determines agent state from onboarding record and agent data.
4
+ * States: 'new' | 'onboarding' | 'problem' | 'idle' | 'production'
5
+ * Also provides default tab selection and state color mapping.
6
+ * @version-history
7
+ * v1.3.0 -- 2026-05-24 -- Remove unused getStateLabel() export (audit fix #7)
8
+ * v1.2.1 -- 2026-05-24 -- Document idle state in design spec, align with audit findings
9
+ * v1.2.0 -- 2026-05-24 -- Add idle state for inactive production agents
10
+ * v1.1.0 -- 2026-05-24 -- Fix: onboarding color to yellow, add readiness drop detection for problem state
11
+ * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
12
+ */
13
+
14
+ /**
15
+ * Detect agent state from onboarding record and agent data.
16
+ * Checked in priority order: new > onboarding > problem > idle > production.
17
+ */
18
+ export function detectAgentState(agent, onboarding) {
19
+ if (!onboarding || onboarding.status === 'pending' || onboarding.status === 'not_started') return 'new';
20
+ if (onboarding.status === 'in_progress') return 'onboarding';
21
+
22
+ const webhookDown = (agent.webhookFailCount ?? 0) >= 5;
23
+ const noTelemetry = !agent.last_seen || isStale(agent.last_seen, 24 * 60);
24
+ const readinessDrop = onboarding.previousReadinessLevel && onboarding.readinessLevel
25
+ && readinessRank(onboarding.readinessLevel) < readinessRank(onboarding.previousReadinessLevel);
26
+ if (webhookDown || noTelemetry || readinessDrop) return 'problem';
27
+
28
+ if (!agent.last_seen || isStale(agent.last_seen, 60)) return 'idle';
29
+ return 'production';
30
+ }
31
+
32
+ export function getDefaultTab(state) {
33
+ switch (state) {
34
+ case 'new': return 'integration';
35
+ case 'onboarding': return 'integration';
36
+ case 'problem': return 'integration';
37
+ case 'idle': return 'tasks';
38
+ case 'production': return 'tasks';
39
+ default: return 'tasks';
40
+ }
41
+ }
42
+
43
+ export function getStateColor(state) {
44
+ switch (state) {
45
+ case 'new': return 'var(--warning)';
46
+ case 'onboarding': return 'var(--warning)';
47
+ case 'problem': return 'var(--danger)';
48
+ case 'idle': return 'var(--text-muted)';
49
+ case 'production': return 'var(--success)';
50
+ default: return 'var(--text-muted)';
51
+ }
52
+ }
53
+
54
+ const READINESS_RANKS = { none: 0, basic: 1, standard: 2, advanced: 3, full: 4 };
55
+ function readinessRank(level) {
56
+ return READINESS_RANKS[level] ?? 0;
57
+ }
58
+
59
+ function isStale(isoDate, thresholdMinutes) {
60
+ const diff = Date.now() - new Date(isoDate).getTime();
61
+ return diff > thresholdMinutes * 60 * 1000;
62
+ }