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,304 @@
1
+ /**
2
+ * @file tab-data-access.js
3
+ * @description Data Access tab: shared tags, memory areas, knowledge packages,
4
+ * and effective scope summary.
5
+ * @version-history
6
+ * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
7
+ * v1.1.0 -- 2026-05-24 -- Add area form (F12), link package (F13), doc count (F14), tags help (F15), scope footer (F16)
8
+ */
9
+
10
+ import { h } from 'preact';
11
+ import { useState, useEffect, useRef } from 'preact/hooks';
12
+ import htm from 'htm';
13
+ import { t } from '/js/i18n.js';
14
+ import { apiGet, apiPatch } from '/js/api.js';
15
+ import { getDirectives, upsertDirectives } from '/js/services/agent-directives.js';
16
+
17
+ const html = htm.bind(h);
18
+
19
+ export default function TabDataAccess({ agent, agentName, session, showToast, allAgents }) {
20
+ const [tags, setTags] = useState(agent.tags ?? []);
21
+ const [memoryAreas, setMemoryAreas] = useState([]);
22
+ const [resources, setResources] = useState([]);
23
+ const [addingTag, setAddingTag] = useState(false);
24
+ const [newTag, setNewTag] = useState('');
25
+ const [addingArea, setAddingArea] = useState(false);
26
+ const [newAreaKey, setNewAreaKey] = useState('');
27
+ const [newAreaDesc, setNewAreaDesc] = useState('');
28
+ const [newAreaPerm, setNewAreaPerm] = useState('read+write');
29
+ const [addingPackage, setAddingPackage] = useState(false);
30
+ const [newPkgName, setNewPkgName] = useState('');
31
+ const [newPkgDesc, setNewPkgDesc] = useState('');
32
+ const [memoryKeys, setMemoryKeys] = useState([]);
33
+ const [expandedKey, setExpandedKey] = useState(null);
34
+ const [expandedValue, setExpandedValue] = useState(null);
35
+ const [loading, setLoading] = useState(true);
36
+
37
+ async function loadData() {
38
+ setLoading(true);
39
+ try {
40
+ const [dirResp, memResp] = await Promise.all([
41
+ getDirectives(agentName).catch(() => null),
42
+ apiGet(`/v1/memory?prefix=&per_page=100&agent=${encodeURIComponent(agent.gaii || agentName)}`).catch(() => null),
43
+ ]);
44
+ const data = dirResp?.data || {};
45
+ setMemoryAreas(data.memory_areas || []);
46
+ setResources(data.resources || []);
47
+ const items = memResp?.data?.items || memResp?.data || [];
48
+ const keys = (Array.isArray(items) ? items : [])
49
+ .map(item => ({ key: item.key, visibility: item.visibility, updatedAt: item.updatedAt }));
50
+ setMemoryKeys(keys);
51
+ } catch {
52
+ setMemoryAreas([]);
53
+ setResources([]);
54
+ setMemoryKeys([]);
55
+ }
56
+ setTags(agent.tags ?? []);
57
+ setLoading(false);
58
+ }
59
+
60
+ useEffect(() => { loadData(); }, [agentName]);
61
+
62
+ const loadRef = useRef(loadData);
63
+ loadRef.current = loadData;
64
+ useEffect(() => {
65
+ const handler = () => loadRef.current();
66
+ window.addEventListener('aimeat-live-update', handler);
67
+ return () => window.removeEventListener('aimeat-live-update', handler);
68
+ }, []);
69
+
70
+ async function handleAddTag() {
71
+ const tag = newTag.trim().toLowerCase();
72
+ if (!tag || tags.includes(tag)) return;
73
+ try {
74
+ const updated = [...tags, tag];
75
+ await apiPatch(`/v1/agents/${encodeURIComponent(agentName)}/tags`, { tags: updated });
76
+ setTags(updated);
77
+ setNewTag('');
78
+ setAddingTag(false);
79
+ showToast(t('profile.agents.detail.data_access.tagAdded'));
80
+ } catch (err) {
81
+ showToast(err.message || t('profile.agents.detail.data_access.addTagError'), true);
82
+ }
83
+ }
84
+
85
+ async function handleRemoveTag(tag) {
86
+ try {
87
+ const updated = tags.filter(t => t !== tag);
88
+ await apiPatch(`/v1/agents/${encodeURIComponent(agentName)}/tags`, { tags: updated });
89
+ setTags(updated);
90
+ showToast(t('profile.agents.detail.data_access.tagRemoved'));
91
+ } catch (err) {
92
+ showToast(err.message || t('profile.agents.detail.data_access.removeTagError'), true);
93
+ }
94
+ }
95
+
96
+ async function handleAddArea() {
97
+ const key = newAreaKey.trim();
98
+ if (!key) return;
99
+ try {
100
+ const updated = [...memoryAreas, { key, description: newAreaDesc.trim(), access: newAreaPerm }];
101
+ await upsertDirectives(agentName, { memory_areas: updated });
102
+ setMemoryAreas(updated);
103
+ setNewAreaKey('');
104
+ setNewAreaDesc('');
105
+ setNewAreaPerm('read+write');
106
+ setAddingArea(false);
107
+ showToast(t('profile.agents.detail.data_access.areaAdded'));
108
+ } catch (err) {
109
+ showToast(err.message || t('profile.agents.detail.data_access.addAreaError'), true);
110
+ }
111
+ }
112
+
113
+ async function handleLinkPackage() {
114
+ const name = newPkgName.trim();
115
+ if (!name) return;
116
+ try {
117
+ const updated = [...resources, { name, description: newPkgDesc.trim() }];
118
+ await upsertDirectives(agentName, { resources: updated });
119
+ setResources(updated);
120
+ setNewPkgName('');
121
+ setNewPkgDesc('');
122
+ setAddingPackage(false);
123
+ showToast(t('profile.agents.detail.data_access.packageLinked'));
124
+ } catch (err) {
125
+ showToast(err.message || t('profile.agents.detail.data_access.linkPackageError'), true);
126
+ }
127
+ }
128
+
129
+ async function toggleExpandKey(mk) {
130
+ if (expandedKey === mk.key) {
131
+ setExpandedKey(null);
132
+ setExpandedValue(null);
133
+ return;
134
+ }
135
+ setExpandedKey(mk.key);
136
+ setExpandedValue('...');
137
+ try {
138
+ const gaii = agent?.gaii || agentName;
139
+ const resp = await apiGet(`/v1/memory/${encodeURIComponent(gaii)}/${encodeURIComponent(mk.key)}`);
140
+ const val = resp?.data?.value;
141
+ setExpandedValue(typeof val === 'string' ? val : JSON.stringify(val, null, 2));
142
+ } catch {
143
+ const resp2 = await apiGet(`/v1/memory?agent=${encodeURIComponent(agent?.gaii || agentName)}&prefix=${encodeURIComponent(mk.key)}`);
144
+ const items = resp2?.data?.items || resp2?.data || [];
145
+ const found = Array.isArray(items) ? items.find(i => i.key === mk.key) : null;
146
+ setExpandedValue(found ? (typeof found.value === 'string' ? found.value : JSON.stringify(found.value, null, 2)) : 'Could not load value');
147
+ }
148
+ }
149
+
150
+ function getSharedWith(tag) {
151
+ if (!allAgents) return [];
152
+ return allAgents.filter(a => a.name !== agentName && (a.tags ?? []).includes(tag));
153
+ }
154
+
155
+ if (loading) {
156
+ return html`<div class="pf-agd-empty">${t('profile.loading')}</div>`;
157
+ }
158
+
159
+ const hasTags = tags.length > 0;
160
+ const hasAreas = memoryAreas.length > 0;
161
+ const hasResources = resources.length > 0;
162
+ const hasKeys = memoryKeys.length > 0;
163
+
164
+ if (!hasTags && !hasAreas && !hasResources && !hasKeys && !addingTag && !addingArea && !addingPackage) {
165
+ return html`
166
+ <div>
167
+ <div class="pf-agd-empty">${t('profile.agents.detail.empty.data_access')}</div>
168
+ <div class="pf-agd-form-actions">
169
+ <button class="btn-outline btn-sm" onClick=${() => setAddingTag(true)}>+ ${t('profile.agents.detail.data_access.addTag')}</button>
170
+ <button class="btn-outline btn-sm" onClick=${() => setAddingArea(true)}>+ ${t('profile.agents.detail.data_access.addArea')}</button>
171
+ <button class="btn-outline btn-sm" onClick=${() => setAddingPackage(true)}>+ ${t('profile.agents.detail.data_access.linkPackage')}</button>
172
+ </div>
173
+ </div>
174
+ `;
175
+ }
176
+
177
+ return html`
178
+ <div>
179
+ <!-- SHARED TAGS -->
180
+ <div class="pf-agd-data-section">
181
+ <div class="pf-agd-section-header">
182
+ <span class="pf-agd-section-title">${t('profile.agents.detail.data_access.sharedTagsTitle')}</span>
183
+ <button class="btn-outline btn-sm" onClick=${() => setAddingTag(!addingTag)}>+ ${t('profile.agents.detail.data_access.addTag')}</button>
184
+ </div>
185
+ ${addingTag && html`
186
+ <div class="pf-agd-form-field pf-agd-tag-input-row">
187
+ <input type="text" value=${newTag} onInput=${(e) => setNewTag(e.target.value)}
188
+ placeholder=${t('profile.agents.detail.data_access.tagPlaceholder')}
189
+ onKeyDown=${(e) => e.key === 'Enter' && handleAddTag()} />
190
+ <button class="btn-primary btn-sm" onClick=${handleAddTag}>${t('common.add')}</button>
191
+ </div>
192
+ `}
193
+ ${tags.map(tag => {
194
+ const shared = getSharedWith(tag);
195
+ return html`
196
+ <div key=${tag} class="pf-agd-tag-row">
197
+ <span class="pf-agd-tag-name">[${tag}]</span>
198
+ <span class="pf-agd-tag-prefix">agents.tag.${tag}.*</span>
199
+ <span class="pf-agd-tag-sharing">
200
+ ${shared.length > 0
201
+ ? `${t('profile.agents.detail.data_access.with')}: ${shared.map(a => a.name).join(', ')}`
202
+ : t('profile.agents.detail.data_access.onlyYou')}
203
+ </span>
204
+ <button class="pf-agd-remove-btn" onClick=${() => handleRemoveTag(tag)}>x</button>
205
+ </div>
206
+ `;
207
+ })}
208
+ ${tags.length === 0 && !addingTag && html`
209
+ <div class="pf-agd-empty">${t('profile.agents.detail.data_access.noTags')}</div>
210
+ `}
211
+ <div class="pf-agd-help-text">${t('profile.agents.detail.data_access.tagsHelp')}</div>
212
+ </div>
213
+
214
+ <!-- MEMORY AREAS -->
215
+ <div class="pf-agd-data-section">
216
+ <div class="pf-agd-section-header">
217
+ <span class="pf-agd-section-title">${t('profile.agents.detail.data_access.memoryAreasTitle')}</span>
218
+ <button class="btn-outline btn-sm" onClick=${() => setAddingArea(!addingArea)}>+ ${t('profile.agents.detail.data_access.addArea')}</button>
219
+ </div>
220
+ ${addingArea && html`
221
+ <div class="pf-agd-area-form">
222
+ <input type="text" value=${newAreaKey} onInput=${(e) => setNewAreaKey(e.target.value)}
223
+ placeholder=${t('profile.agents.detail.data_access.areaKeyPlaceholder')} />
224
+ <input type="text" value=${newAreaDesc} onInput=${(e) => setNewAreaDesc(e.target.value)}
225
+ placeholder=${t('profile.agents.detail.data_access.areaDescPlaceholder')} />
226
+ <select value=${newAreaPerm} onChange=${(e) => setNewAreaPerm(e.target.value)}>
227
+ <option value="read+write">${t('profile.agents.detail.data_access.permReadWrite')}</option>
228
+ <option value="read">${t('profile.agents.detail.data_access.permReadOnly')}</option>
229
+ </select>
230
+ <button class="btn-primary btn-sm" onClick=${handleAddArea}>${t('profile.agents.detail.data_access.addArea')}</button>
231
+ <button class="btn-outline btn-sm" onClick=${() => setAddingArea(false)}>${t('common.cancel')}</button>
232
+ </div>
233
+ `}
234
+ ${hasAreas ? memoryAreas.map(area => html`
235
+ <div key=${area.key || area} class="pf-agd-area-row">
236
+ <span class="pf-agd-area-key">${area.key || area}</span>
237
+ <span class="pf-agd-area-desc">${area.description || ''}</span>
238
+ <span class="pf-agd-area-perm ${area.access === 'read' ? 'pf-agd-area-perm--ro' : 'pf-agd-area-perm--rw'}">
239
+ ${area.access === 'read' ? t('profile.agents.detail.data_access.permReadOnly') : t('profile.agents.detail.data_access.permReadWrite')}
240
+ </span>
241
+ </div>
242
+ `) : !addingArea && html`
243
+ <div class="pf-agd-empty">${t('profile.agents.detail.data_access.noAreas')}</div>
244
+ `}
245
+ </div>
246
+
247
+ <!-- KNOWLEDGE PACKAGES -->
248
+ <div class="pf-agd-data-section">
249
+ <div class="pf-agd-section-header">
250
+ <span class="pf-agd-section-title">${t('profile.agents.detail.data_access.knowledgeTitle')}</span>
251
+ <button class="btn-outline btn-sm" onClick=${() => setAddingPackage(!addingPackage)}>+ ${t('profile.agents.detail.data_access.linkPackage')}</button>
252
+ </div>
253
+ ${addingPackage && html`
254
+ <div class="pf-agd-area-form">
255
+ <input type="text" value=${newPkgName} onInput=${(e) => setNewPkgName(e.target.value)}
256
+ placeholder=${t('profile.agents.detail.data_access.packageNamePlaceholder')} />
257
+ <input type="text" value=${newPkgDesc} onInput=${(e) => setNewPkgDesc(e.target.value)}
258
+ placeholder=${t('profile.agents.detail.data_access.packageDescPlaceholder')} />
259
+ <button class="btn-primary btn-sm" onClick=${handleLinkPackage}>${t('profile.agents.detail.data_access.linkPackage')}</button>
260
+ <button class="btn-outline btn-sm" onClick=${() => setAddingPackage(false)}>${t('common.cancel')}</button>
261
+ </div>
262
+ `}
263
+ ${hasResources ? resources.map(res => html`
264
+ <div key=${res.url || res.name || res} class="pf-agd-area-row">
265
+ <span class="pf-agd-area-key">${res.name || res.url || res}</span>
266
+ <span class="pf-agd-area-desc">${res.description || ''}</span>
267
+ ${(res.documentCount || res.count) ? html`<span class="pf-agd-package-count">${res.documentCount || res.count} ${t('profile.agents.detail.data_access.docCount')}</span>` : ''}
268
+ </div>
269
+ `) : !addingPackage && html`
270
+ <div class="pf-agd-empty">${t('profile.agents.detail.data_access.noPackages')}</div>
271
+ `}
272
+ </div>
273
+
274
+ <!-- STORED MEMORY KEYS -->
275
+ ${memoryKeys.length > 0 && html`
276
+ <div class="pf-agd-data-section">
277
+ <div class="pf-agd-section-header">
278
+ <span class="pf-agd-section-title">${t('profile.agents.detail.data_access.storedKeysTitle')}</span>
279
+ </div>
280
+ ${memoryKeys.map(mk => html`
281
+ <div key=${mk.key}>
282
+ <div class="pf-agd-area-row pf-agd-area-row--clickable" onClick=${() => toggleExpandKey(mk)}>
283
+ <span class="pf-agd-expand-icon">${expandedKey === mk.key ? '▼' : '▶'}</span>
284
+ <span class="pf-agd-area-key">${mk.key}</span>
285
+ <span class="pf-agd-area-perm pf-agd-area-perm--${mk.visibility === 'public' ? 'rw' : 'ro'}">${mk.visibility}</span>
286
+ </div>
287
+ ${expandedKey === mk.key && html`
288
+ <pre class="pf-agd-memory-preview">${expandedValue}</pre>
289
+ `}
290
+ </div>
291
+ `)}
292
+ </div>
293
+ `}
294
+
295
+ <!-- EFFECTIVE SCOPE SUMMARY -->
296
+ <div class="pf-agd-scope-summary">
297
+ ${t('profile.agents.detail.data_access.effectiveScope')}:\n${
298
+ [...memoryAreas.map(a => a.key || a), ...tags.map(tag => `agents.tag.${tag}.*`), 'agents.shared.index'].join(', ')
299
+ }${hasResources ? `\n${t('profile.agents.detail.data_access.knowledgeTitle')}: ${resources.map(r => r.name || r.url || r).join(', ')}` : ''}
300
+ <div class="pf-agd-scope-footer">${t('profile.agents.detail.data_access.scopeFooter')}</div>
301
+ </div>
302
+ </div>
303
+ `;
304
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @file tab-directives.js
3
+ * @description Directives tab -- structured text editor for behavioral instructions.
4
+ * View mode shows formatted text; edit mode uses a single textarea.
5
+ * Memory areas, knowledge packages, and config files live in their own tabs.
6
+ * @version-history
7
+ * v2.0.0 -- 2026-05-24 -- C5: rewrite as full structured text editor; M6: no SSE listener (owner-initiated only)
8
+ * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
9
+ */
10
+
11
+ import { h } from 'preact';
12
+ import { useState, useEffect } from 'preact/hooks';
13
+ import htm from 'htm';
14
+ import { t } from '/js/i18n.js';
15
+ import { getDirectives, upsertDirectives } from '/js/services/agent-directives.js';
16
+
17
+ const html = htm.bind(h);
18
+
19
+ /**
20
+ * Convert legacy rules array to plain text for display/editing.
21
+ */
22
+ function rulesToText(rules) {
23
+ if (!rules || rules.length === 0) return '';
24
+ return rules.map(r => {
25
+ if (typeof r === 'string') return r;
26
+ return r.text || r.rule || '';
27
+ }).filter(Boolean).join('\n');
28
+ }
29
+
30
+ export default function TabDirectives({ agentName, session, showToast }) {
31
+ const [loading, setLoading] = useState(true);
32
+ const [error, setError] = useState(null);
33
+ const [purpose, setPurpose] = useState('');
34
+ const [content, setContent] = useState('');
35
+ const [editing, setEditing] = useState(false);
36
+ const [editPurpose, setEditPurpose] = useState('');
37
+ const [editContent, setEditContent] = useState('');
38
+ const [saving, setSaving] = useState(false);
39
+
40
+ async function loadData() {
41
+ setLoading(true);
42
+ try {
43
+ const resp = await getDirectives(agentName);
44
+ const data = resp?.data || {};
45
+ setPurpose(data.purpose || data.agent_purpose || '');
46
+ // Support both new content format and legacy rules array
47
+ if (data.content) {
48
+ setContent(data.content);
49
+ } else {
50
+ setContent(rulesToText(data.rules));
51
+ }
52
+ setError(null);
53
+ } catch (err) {
54
+ if (err.status === 404) {
55
+ setPurpose('');
56
+ setContent('');
57
+ setError(null);
58
+ } else {
59
+ setError(err.message);
60
+ }
61
+ }
62
+ setLoading(false);
63
+ }
64
+
65
+ useEffect(() => { loadData(); }, [agentName]);
66
+
67
+ // M6: No SSE listener -- directives are owner-initiated only
68
+
69
+ function startEditing() {
70
+ setEditPurpose(purpose);
71
+ setEditContent(content);
72
+ setEditing(true);
73
+ }
74
+
75
+ function cancelEditing() {
76
+ setEditing(false);
77
+ }
78
+
79
+ async function handleSave() {
80
+ setSaving(true);
81
+ try {
82
+ const payload = {
83
+ content: editContent.trim() || undefined,
84
+ purpose: editPurpose.trim() || undefined,
85
+ };
86
+ await upsertDirectives(agentName, payload);
87
+ showToast(t('profile.agents.directives.saved'));
88
+ setEditing(false);
89
+ await loadData();
90
+ } catch (err) {
91
+ showToast(err.message || t('profile.agents.directives.saveError'), true);
92
+ }
93
+ setSaving(false);
94
+ }
95
+
96
+ if (loading) {
97
+ return html`<div class="pf-agd-empty">${t('profile.loading')}</div>`;
98
+ }
99
+
100
+ if (error) {
101
+ return html`<div class="pf-agd-empty">${error}</div>`;
102
+ }
103
+
104
+ const hasContent = purpose || content;
105
+
106
+ return html`
107
+ <div>
108
+ ${!editing ? html`
109
+ <!-- View mode -->
110
+ <div class="pf-agd-section-header">
111
+ <span class="pf-agd-section-title">${t('profile.agents.directives.title')}</span>
112
+ <div>
113
+ <button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); startEditing(); }}>
114
+ ${t('profile.agents.directives.edit')}
115
+ </button>
116
+ </div>
117
+ </div>
118
+
119
+ ${purpose && html`
120
+ <div class="pf-agd-directive-section">
121
+ <h4>${t('profile.agents.directives.purpose')}</h4>
122
+ <div class="pf-agd-purpose-text">${purpose}</div>
123
+ </div>
124
+ `}
125
+
126
+ ${content ? html`
127
+ <div class="pf-agd-directive-section">
128
+ <div class="pf-agd-directives-content">${content}</div>
129
+ </div>
130
+ ` : ''}
131
+
132
+ ${!hasContent && html`
133
+ <div class="pf-agd-empty">
134
+ ${t('profile.agents.directives.empty')}
135
+ </div>
136
+ `}
137
+
138
+ <div class="pf-agd-directive-footer">
139
+ ${t('profile.agents.detail.directives.footer')}
140
+ </div>
141
+ ` : html`
142
+ <!-- Edit mode -->
143
+ <div class="pf-agd-section-header">
144
+ <span class="pf-agd-section-title">${t('profile.agents.directives.editing')}</span>
145
+ </div>
146
+
147
+ <div class="pf-agd-directive-section">
148
+ <h4>${t('profile.agents.directives.purpose')}</h4>
149
+ <div class="pf-agd-form-field">
150
+ <textarea value=${editPurpose} onInput=${(e) => setEditPurpose(e.target.value)}
151
+ placeholder=${t('profile.agents.directives.purposePlaceholder')}></textarea>
152
+ </div>
153
+ </div>
154
+
155
+ <div class="pf-agd-directive-section">
156
+ <h4>${t('profile.agents.detail.directives.contentLabel')}</h4>
157
+ <textarea class="pf-agd-directives-textarea"
158
+ value=${editContent}
159
+ onInput=${(e) => setEditContent(e.target.value)}
160
+ placeholder=${t('profile.agents.detail.directives.contentPlaceholder')}></textarea>
161
+ </div>
162
+
163
+ <div class="pf-agd-form-actions">
164
+ <button class="btn-primary btn-sm" onClick=${(e) => { e.stopPropagation(); handleSave(); }} disabled=${saving}>
165
+ ${saving ? t('profile.agents.directives.saving') : t('profile.agents.directives.save')}
166
+ </button>
167
+ <button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); cancelEditing(); }}>
168
+ ${t('profile.agents.scopeUi.cancel')}
169
+ </button>
170
+ </div>
171
+
172
+ <div class="pf-agd-directive-footer">
173
+ ${t('profile.agents.detail.directives.footer')}
174
+ </div>
175
+ `}
176
+ </div>
177
+ `;
178
+ }