@agenticmail/enterprise 0.5.298 → 0.5.299

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.
@@ -0,0 +1,335 @@
1
+ import { h, useState, useEffect, useCallback, Fragment, useApp, apiCall } from '../components/utils.js';
2
+ import { I } from '../components/icons.js';
3
+ import { Modal } from '../components/modal.js';
4
+ import { HelpButton } from '../components/help-button.js';
5
+
6
+ function slugify(text) {
7
+ return (text || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
8
+ }
9
+
10
+ export function OrganizationsPage() {
11
+ var app = useApp();
12
+ var toast = app.toast;
13
+
14
+ var _orgs = useState([]);
15
+ var orgs = _orgs[0]; var setOrgs = _orgs[1];
16
+ var _loading = useState(true);
17
+ var loading = _loading[0]; var setLoading = _loading[1];
18
+ var _showCreate = useState(false);
19
+ var showCreate = _showCreate[0]; var setShowCreate = _showCreate[1];
20
+ var _editOrg = useState(null);
21
+ var editOrg = _editOrg[0]; var setEditOrg = _editOrg[1];
22
+ var _detailOrg = useState(null);
23
+ var detailOrg = _detailOrg[0]; var setDetailOrg = _detailOrg[1];
24
+ var _detailAgents = useState([]);
25
+ var detailAgents = _detailAgents[0]; var setDetailAgents = _detailAgents[1];
26
+ var _allAgents = useState([]);
27
+ var allAgents = _allAgents[0]; var setAllAgents = _allAgents[1];
28
+ var _assignAgentId = useState('');
29
+ var assignAgentId = _assignAgentId[0]; var setAssignAgentId = _assignAgentId[1];
30
+ var _acting = useState('');
31
+ var acting = _acting[0]; var setActing = _acting[1];
32
+
33
+ // Form state
34
+ var _fname = useState('');
35
+ var fname = _fname[0]; var setFname = _fname[1];
36
+ var _fslug = useState('');
37
+ var fslug = _fslug[0]; var setFslug = _fslug[1];
38
+ var _fcontact = useState('');
39
+ var fcontact = _fcontact[0]; var setFcontact = _fcontact[1];
40
+ var _femail = useState('');
41
+ var femail = _femail[0]; var setFemail = _femail[1];
42
+ var _fdesc = useState('');
43
+ var fdesc = _fdesc[0]; var setFdesc = _fdesc[1];
44
+ var _slugManual = useState(false);
45
+ var slugManual = _slugManual[0]; var setSlugManual = _slugManual[1];
46
+
47
+ var loadOrgs = useCallback(function() {
48
+ setLoading(true);
49
+ apiCall('/organizations').then(function(data) {
50
+ setOrgs(data.organizations || []);
51
+ }).catch(function(err) {
52
+ toast(err.message, 'error');
53
+ }).finally(function() { setLoading(false); });
54
+ }, []);
55
+
56
+ useEffect(function() { loadOrgs(); }, []);
57
+
58
+ var loadAllAgents = function() {
59
+ apiCall('/agents?limit=200').then(function(data) {
60
+ setAllAgents(data.agents || []);
61
+ }).catch(function() {});
62
+ };
63
+
64
+ var openCreate = function() {
65
+ setFname(''); setFslug(''); setFcontact(''); setFemail(''); setFdesc(''); setSlugManual(false);
66
+ setShowCreate(true);
67
+ };
68
+
69
+ var openEdit = function(org) {
70
+ setFname(org.name || ''); setFslug(org.slug || ''); setFcontact(org.contact_name || ''); setFemail(org.contact_email || ''); setFdesc(org.description || '');
71
+ setEditOrg(org);
72
+ };
73
+
74
+ var openDetail = function(org) {
75
+ setDetailOrg(org);
76
+ loadAllAgents();
77
+ apiCall('/organizations/' + org.id).then(function(data) {
78
+ setDetailAgents(data.agents || []);
79
+ setDetailOrg(data);
80
+ }).catch(function(err) { toast(err.message, 'error'); });
81
+ };
82
+
83
+ var doCreate = function() {
84
+ setActing('create');
85
+ apiCall('/organizations', {
86
+ method: 'POST',
87
+ body: JSON.stringify({ name: fname, slug: fslug, contact_name: fcontact, contact_email: femail, description: fdesc })
88
+ }).then(function() {
89
+ toast('Organization created', 'success');
90
+ setShowCreate(false);
91
+ loadOrgs();
92
+ }).catch(function(err) { toast(err.message, 'error'); })
93
+ .finally(function() { setActing(''); });
94
+ };
95
+
96
+ var doEdit = function() {
97
+ setActing('edit');
98
+ apiCall('/organizations/' + editOrg.id, {
99
+ method: 'PATCH',
100
+ body: JSON.stringify({ name: fname, contact_name: fcontact, contact_email: femail, description: fdesc })
101
+ }).then(function() {
102
+ toast('Organization updated', 'success');
103
+ setEditOrg(null);
104
+ loadOrgs();
105
+ }).catch(function(err) { toast(err.message, 'error'); })
106
+ .finally(function() { setActing(''); });
107
+ };
108
+
109
+ var doToggle = function(org) {
110
+ setActing('toggle-' + org.id);
111
+ apiCall('/organizations/' + org.id + '/toggle', { method: 'POST' })
112
+ .then(function() {
113
+ toast('Organization ' + (org.is_active ? 'deactivated' : 'activated'), 'success');
114
+ loadOrgs();
115
+ if (detailOrg && detailOrg.id === org.id) openDetail(org);
116
+ }).catch(function(err) { toast(err.message, 'error'); })
117
+ .finally(function() { setActing(''); });
118
+ };
119
+
120
+ var doDelete = function(org) {
121
+ if (!window.__showConfirm) return;
122
+ window.__showConfirm({ title: 'Delete Organization', message: 'Are you sure you want to delete "' + org.name + '"? This cannot be undone.' }).then(function(confirmed) {
123
+ if (!confirmed) return;
124
+ setActing('delete-' + org.id);
125
+ apiCall('/organizations/' + org.id, { method: 'DELETE' })
126
+ .then(function() {
127
+ toast('Organization deleted', 'success');
128
+ loadOrgs();
129
+ if (detailOrg && detailOrg.id === org.id) setDetailOrg(null);
130
+ }).catch(function(err) { toast(err.message, 'error'); })
131
+ .finally(function() { setActing(''); });
132
+ });
133
+ };
134
+
135
+ var doAssignAgent = function() {
136
+ if (!assignAgentId || !detailOrg) return;
137
+ setActing('assign');
138
+ apiCall('/agents/' + assignAgentId + '/assign-org', {
139
+ method: 'POST',
140
+ body: JSON.stringify({ orgId: detailOrg.id })
141
+ }).then(function() {
142
+ toast('Agent assigned', 'success');
143
+ setAssignAgentId('');
144
+ openDetail(detailOrg);
145
+ loadOrgs();
146
+ }).catch(function(err) { toast(err.message, 'error'); })
147
+ .finally(function() { setActing(''); });
148
+ };
149
+
150
+ var doUnassignAgent = function(agentId) {
151
+ setActing('unassign-' + agentId);
152
+ apiCall('/agents/' + agentId + '/unassign-org', { method: 'POST' })
153
+ .then(function() {
154
+ toast('Agent unassigned', 'success');
155
+ openDetail(detailOrg);
156
+ loadOrgs();
157
+ }).catch(function(err) { toast(err.message, 'error'); })
158
+ .finally(function() { setActing(''); });
159
+ };
160
+
161
+ var unassignedAgents = allAgents.filter(function(a) {
162
+ return !a.client_org_id && detailAgents.every(function(da) { return da.id !== a.id; });
163
+ });
164
+
165
+ if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading organizations...');
166
+
167
+ return h(Fragment, null,
168
+ // Header
169
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 } },
170
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
171
+ h('h1', { style: { fontSize: 20, fontWeight: 700 } }, 'Organizations'),
172
+ h(HelpButton, { label: 'Organizations' },
173
+ h('p', null, 'Manage client organizations and assign agents to them. Each organization represents a tenant or client that your agents serve.'),
174
+ h('ul', { style: { paddingLeft: 20, margin: '8px 0' } },
175
+ h('li', null, 'Create organizations for each client or department'),
176
+ h('li', null, 'Assign agents to organizations to control access'),
177
+ h('li', null, 'Toggle organizations active/inactive to suspend all linked agents'),
178
+ h('li', null, 'Delete organizations only after unassigning all agents')
179
+ )
180
+ )
181
+ ),
182
+ h('button', { className: 'btn btn-primary', onClick: openCreate }, I.plus(), ' New Organization')
183
+ ),
184
+
185
+ // Org cards
186
+ orgs.length === 0
187
+ ? h('div', { className: 'card', style: { textAlign: 'center', padding: 40 } },
188
+ h('div', { style: { fontSize: 48, marginBottom: 12 } }, '🏢'),
189
+ h('div', { style: { fontSize: 15, fontWeight: 600, marginBottom: 4 } }, 'No organizations yet'),
190
+ h('div', { style: { color: 'var(--text-muted)', fontSize: 13, marginBottom: 16 } }, 'Create your first client organization to start managing multi-tenant agent deployments.'),
191
+ h('button', { className: 'btn btn-primary', onClick: openCreate }, I.plus(), ' Create Organization')
192
+ )
193
+ : h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))', gap: 16 } },
194
+ orgs.map(function(org) {
195
+ return h('div', { key: org.id, className: 'card', style: { cursor: 'pointer', transition: 'border-color 0.15s', position: 'relative' }, onClick: function() { openDetail(org); } },
196
+ h('div', { className: 'card-body' },
197
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 } },
198
+ h('div', null,
199
+ h('div', { style: { fontSize: 16, fontWeight: 700, marginBottom: 2 } }, org.name),
200
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono, monospace)' } }, org.slug)
201
+ ),
202
+ h('span', { className: 'badge badge-' + (org.is_active ? 'success' : 'warning') }, org.is_active ? 'Active' : 'Inactive')
203
+ ),
204
+ org.description && h('div', { style: { fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12, lineHeight: 1.5 } }, org.description),
205
+ h('div', { style: { display: 'flex', gap: 16, fontSize: 12, color: 'var(--text-muted)' } },
206
+ h('span', null, I.agents(), ' ', (org.agent_count || 0), ' agent', (org.agent_count || 0) !== 1 ? 's' : ''),
207
+ org.contact_email && h('span', null, '✉ ', org.contact_email),
208
+ org.created_at && h('span', null, new Date(org.created_at).toLocaleDateString())
209
+ ),
210
+ h('div', { style: { display: 'flex', gap: 6, marginTop: 12, borderTop: '1px solid var(--border)', paddingTop: 10 }, onClick: function(e) { e.stopPropagation(); } },
211
+ h('button', { className: 'btn btn-ghost btn-sm', onClick: function() { openEdit(org); } }, I.edit(), ' Edit'),
212
+ h('button', { className: 'btn btn-ghost btn-sm', disabled: acting === 'toggle-' + org.id, onClick: function() { doToggle(org); } },
213
+ org.is_active ? 'Deactivate' : 'Activate'
214
+ ),
215
+ (org.agent_count || 0) === 0 && h('button', { className: 'btn btn-ghost btn-sm', style: { color: 'var(--danger)' }, disabled: acting === 'delete-' + org.id, onClick: function() { doDelete(org); } }, I.trash(), ' Delete')
216
+ )
217
+ )
218
+ );
219
+ })
220
+ ),
221
+
222
+ // Create Modal
223
+ showCreate && h(Modal, { title: 'Create Organization', onClose: function() { setShowCreate(false); } },
224
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: 14, padding: 4 } },
225
+ h('div', null,
226
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Name *'),
227
+ h('input', { className: 'input', value: fname, onInput: function(e) { setFname(e.target.value); if (!slugManual) setFslug(slugify(e.target.value)); }, placeholder: 'Acme Corporation' })
228
+ ),
229
+ h('div', null,
230
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Slug *'),
231
+ h('input', { className: 'input', value: fslug, onInput: function(e) { setFslug(e.target.value); setSlugManual(true); }, placeholder: 'acme-corporation', style: { fontFamily: 'var(--font-mono, monospace)' } })
232
+ ),
233
+ h('div', null,
234
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Contact Name'),
235
+ h('input', { className: 'input', value: fcontact, onInput: function(e) { setFcontact(e.target.value); }, placeholder: 'John Doe' })
236
+ ),
237
+ h('div', null,
238
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Contact Email'),
239
+ h('input', { className: 'input', type: 'email', value: femail, onInput: function(e) { setFemail(e.target.value); }, placeholder: 'john@acme.com' })
240
+ ),
241
+ h('div', null,
242
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Description'),
243
+ h('textarea', { className: 'input', value: fdesc, onInput: function(e) { setFdesc(e.target.value); }, placeholder: 'Brief description...', rows: 3, style: { resize: 'vertical' } })
244
+ ),
245
+ h('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 8 } },
246
+ h('button', { className: 'btn btn-secondary', onClick: function() { setShowCreate(false); } }, 'Cancel'),
247
+ h('button', { className: 'btn btn-primary', disabled: !fname || !fslug || acting === 'create', onClick: doCreate }, acting === 'create' ? 'Creating...' : 'Create')
248
+ )
249
+ )
250
+ ),
251
+
252
+ // Edit Modal
253
+ editOrg && h(Modal, { title: 'Edit Organization', onClose: function() { setEditOrg(null); } },
254
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: 14, padding: 4 } },
255
+ h('div', null,
256
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Name'),
257
+ h('input', { className: 'input', value: fname, onInput: function(e) { setFname(e.target.value); } })
258
+ ),
259
+ h('div', null,
260
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Slug'),
261
+ h('input', { className: 'input', value: fslug, disabled: true, style: { fontFamily: 'var(--font-mono, monospace)', opacity: 0.6 } })
262
+ ),
263
+ h('div', null,
264
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Contact Name'),
265
+ h('input', { className: 'input', value: fcontact, onInput: function(e) { setFcontact(e.target.value); } })
266
+ ),
267
+ h('div', null,
268
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Contact Email'),
269
+ h('input', { className: 'input', type: 'email', value: femail, onInput: function(e) { setFemail(e.target.value); } })
270
+ ),
271
+ h('div', null,
272
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Description'),
273
+ h('textarea', { className: 'input', value: fdesc, onInput: function(e) { setFdesc(e.target.value); }, rows: 3, style: { resize: 'vertical' } })
274
+ ),
275
+ h('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 8 } },
276
+ h('button', { className: 'btn btn-secondary', onClick: function() { setEditOrg(null); } }, 'Cancel'),
277
+ h('button', { className: 'btn btn-primary', disabled: !fname || acting === 'edit', onClick: doEdit }, acting === 'edit' ? 'Saving...' : 'Save Changes')
278
+ )
279
+ )
280
+ ),
281
+
282
+ // Detail Modal
283
+ detailOrg && h(Modal, { title: detailOrg.name || 'Organization Detail', onClose: function() { setDetailOrg(null); }, wide: true },
284
+ h('div', { style: { padding: 4 } },
285
+ // Org info
286
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16, marginBottom: 20 } },
287
+ h('div', null,
288
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 } }, 'Slug'),
289
+ h('div', { style: { fontFamily: 'var(--font-mono, monospace)', fontSize: 13 } }, detailOrg.slug)
290
+ ),
291
+ h('div', null,
292
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 } }, 'Status'),
293
+ h('span', { className: 'badge badge-' + (detailOrg.is_active ? 'success' : 'warning') }, detailOrg.is_active ? 'Active' : 'Inactive')
294
+ ),
295
+ h('div', null,
296
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 } }, 'Contact'),
297
+ h('div', { style: { fontSize: 13 } }, detailOrg.contact_name || '—'),
298
+ detailOrg.contact_email && h('div', { style: { fontSize: 12, color: 'var(--text-muted)' } }, detailOrg.contact_email)
299
+ )
300
+ ),
301
+ detailOrg.description && h('div', { style: { fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16, padding: 12, background: 'var(--bg-tertiary)', borderRadius: 'var(--radius)' } }, detailOrg.description),
302
+
303
+ // Linked agents
304
+ h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 10 } }, 'Linked Agents (' + detailAgents.length + ')'),
305
+ detailAgents.length > 0
306
+ ? h('div', { style: { display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 16 } },
307
+ detailAgents.map(function(a) {
308
+ var stateColor = { active: 'success', running: 'success', suspended: 'warning', archived: 'neutral' }[a.status] || 'neutral';
309
+ return h('div', { key: a.id, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', background: 'var(--bg-tertiary)', borderRadius: 'var(--radius)' } },
310
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
311
+ h('span', { style: { fontWeight: 600, fontSize: 13 } }, a.name),
312
+ h('span', { style: { fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono, monospace)' } }, a.email),
313
+ h('span', { className: 'badge badge-' + stateColor, style: { fontSize: 10 } }, a.status)
314
+ ),
315
+ h('button', { className: 'btn btn-ghost btn-sm', style: { color: 'var(--danger)' }, disabled: acting === 'unassign-' + a.id, onClick: function() { doUnassignAgent(a.id); } }, 'Unassign')
316
+ );
317
+ })
318
+ )
319
+ : h('div', { style: { padding: 16, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13, background: 'var(--bg-tertiary)', borderRadius: 'var(--radius)', marginBottom: 16 } }, 'No agents assigned to this organization'),
320
+
321
+ // Assign agent
322
+ h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 8 } }, 'Assign Agent'),
323
+ h('div', { style: { display: 'flex', gap: 8 } },
324
+ h('select', { className: 'input', value: assignAgentId, onChange: function(e) { setAssignAgentId(e.target.value); }, style: { flex: 1 } },
325
+ h('option', { value: '' }, '— Select an unassigned agent —'),
326
+ unassignedAgents.map(function(a) {
327
+ return h('option', { key: a.id, value: a.id }, a.name + (a.role ? ' (' + a.role + ')' : ''));
328
+ })
329
+ ),
330
+ h('button', { className: 'btn btn-primary btn-sm', disabled: !assignAgentId || acting === 'assign', onClick: doAssignAgent }, acting === 'assign' ? 'Assigning...' : 'Assign')
331
+ )
332
+ )
333
+ )
334
+ );
335
+ }
@@ -193,6 +193,35 @@ export class PostgresAdapter extends DatabaseAdapter {
193
193
  ALTER TABLE users ADD COLUMN IF NOT EXISTS must_reset_password BOOLEAN DEFAULT FALSE;
194
194
  ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE;
195
195
  `).catch(() => {});
196
+ // ─── Client Organizations ────────────────────────────
197
+ await client.query(`
198
+ CREATE TABLE IF NOT EXISTS client_organizations (
199
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid(),
200
+ name TEXT NOT NULL,
201
+ slug TEXT NOT NULL UNIQUE,
202
+ contact_name TEXT,
203
+ contact_email TEXT,
204
+ description TEXT,
205
+ is_active BOOLEAN DEFAULT TRUE,
206
+ settings JSONB DEFAULT '{}',
207
+ created_at TIMESTAMP DEFAULT NOW(),
208
+ updated_at TIMESTAMP DEFAULT NOW()
209
+ );
210
+ `);
211
+ await client.query(`
212
+ ALTER TABLE agents ADD COLUMN IF NOT EXISTS client_org_id TEXT REFERENCES client_organizations(id);
213
+ `).catch(() => {});
214
+ await client.query(`
215
+ CREATE TABLE IF NOT EXISTS agent_knowledge_access (
216
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid(),
217
+ agent_id TEXT NOT NULL,
218
+ knowledge_base_id TEXT NOT NULL,
219
+ access_type TEXT NOT NULL DEFAULT 'read',
220
+ created_at TIMESTAMP DEFAULT NOW(),
221
+ UNIQUE(agent_id, knowledge_base_id)
222
+ );
223
+ `);
224
+
196
225
  await client.query('COMMIT');
197
226
  } catch (err) {
198
227
  await client.query('ROLLBACK');
package/src/db/sqlite.ts CHANGED
@@ -63,6 +63,32 @@ export class SqliteAdapter extends DatabaseAdapter {
63
63
  try { this.db.exec(`ALTER TABLE users ADD COLUMN permissions TEXT DEFAULT '"*"'`); } catch { /* exists */ }
64
64
  try { this.db.exec(`ALTER TABLE users ADD COLUMN must_reset_password INTEGER DEFAULT 0`); } catch { /* exists */ }
65
65
  try { this.db.exec(`ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1`); } catch { /* exists */ }
66
+ // ─── Client Organizations ────────────────────────────
67
+ this.db.exec(`
68
+ CREATE TABLE IF NOT EXISTS client_organizations (
69
+ id TEXT PRIMARY KEY,
70
+ name TEXT NOT NULL,
71
+ slug TEXT NOT NULL UNIQUE,
72
+ contact_name TEXT,
73
+ contact_email TEXT,
74
+ description TEXT,
75
+ is_active INTEGER DEFAULT 1,
76
+ settings TEXT DEFAULT '{}',
77
+ created_at TEXT DEFAULT (datetime('now')),
78
+ updated_at TEXT DEFAULT (datetime('now'))
79
+ );
80
+ `);
81
+ try { this.db.exec(`ALTER TABLE agents ADD COLUMN client_org_id TEXT REFERENCES client_organizations(id)`); } catch { /* exists */ }
82
+ this.db.exec(`
83
+ CREATE TABLE IF NOT EXISTS agent_knowledge_access (
84
+ id TEXT PRIMARY KEY,
85
+ agent_id TEXT NOT NULL,
86
+ knowledge_base_id TEXT NOT NULL,
87
+ access_type TEXT NOT NULL DEFAULT 'read',
88
+ created_at TEXT DEFAULT (datetime('now')),
89
+ UNIQUE(agent_id, knowledge_base_id)
90
+ );
91
+ `);
66
92
  });
67
93
  tx();
68
94
  }