@andespindola/brainlink 0.1.0-beta.9 → 0.1.0-beta.90

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 (53) hide show
  1. package/AGENTS.md +8 -5
  2. package/CHANGELOG.md +26 -2
  3. package/CONTRIBUTING.md +2 -2
  4. package/COPYRIGHT.md +5 -0
  5. package/README.md +146 -17
  6. package/SECURITY.md +1 -1
  7. package/dist/application/analyze-vault.js +7 -7
  8. package/dist/application/build-context.js +56 -1
  9. package/dist/application/dedupe-notes.js +226 -0
  10. package/dist/application/frontend/client-css.js +154 -102
  11. package/dist/application/frontend/client-html.js +49 -40
  12. package/dist/application/frontend/client-js.js +3130 -166
  13. package/dist/application/frontend/client-worker-js.js +66 -0
  14. package/dist/application/get-graph-layout.js +18 -6
  15. package/dist/application/get-graph-node.js +12 -0
  16. package/dist/application/get-graph-summary.js +12 -0
  17. package/dist/application/get-graph.js +3 -3
  18. package/dist/application/import-legacy-sqlite.js +296 -0
  19. package/dist/application/index-vault.js +252 -19
  20. package/dist/application/list-agents.js +3 -3
  21. package/dist/application/list-links.js +5 -5
  22. package/dist/application/offline-pack-backup.js +44 -0
  23. package/dist/application/search-graph-node-ids.js +12 -0
  24. package/dist/application/search-knowledge.js +25 -10
  25. package/dist/application/server/routes.js +102 -1
  26. package/dist/application/start-server.js +75 -4
  27. package/dist/application/watch-vault.js +23 -2
  28. package/dist/benchmarks/large-vault.js +1 -1
  29. package/dist/cli/commands/agent-commands.js +20 -3
  30. package/dist/cli/commands/write-commands.js +818 -8
  31. package/dist/domain/context.js +53 -11
  32. package/dist/domain/embeddings.js +2 -1
  33. package/dist/domain/graph-layout.js +67 -16
  34. package/dist/domain/middle-out.js +18 -0
  35. package/dist/infrastructure/config.js +38 -0
  36. package/dist/infrastructure/file-index.js +358 -0
  37. package/dist/infrastructure/file-system-vault.js +15 -0
  38. package/dist/infrastructure/index-state.js +56 -0
  39. package/dist/infrastructure/private-pack-codec.js +134 -0
  40. package/dist/infrastructure/search-packs.js +452 -0
  41. package/dist/infrastructure/session-state.js +57 -2
  42. package/dist/mcp/server.js +11 -1
  43. package/dist/mcp/tools.js +215 -3
  44. package/docs/AGENT_USAGE.md +103 -16
  45. package/docs/ARCHITECTURE.md +25 -26
  46. package/docs/QUICKSTART.md +9 -1
  47. package/package.json +6 -4
  48. package/dist/infrastructure/sqlite/document-writer.js +0 -51
  49. package/dist/infrastructure/sqlite/graph-reader.js +0 -120
  50. package/dist/infrastructure/sqlite/schema.js +0 -111
  51. package/dist/infrastructure/sqlite/search-reader.js +0 -156
  52. package/dist/infrastructure/sqlite/types.js +0 -1
  53. package/dist/infrastructure/sqlite-index.js +0 -25
@@ -0,0 +1,226 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createEmbeddingBuckets, createLocalEmbedding, cosineSimilarity } from '../domain/embeddings.js';
3
+ import { parseMarkdownDocument } from '../domain/markdown.js';
4
+ import { writeMarkdownFile, ensureVault, readMarkdownFiles } from '../infrastructure/file-system-vault.js';
5
+ import { indexVault } from './index-vault.js';
6
+ const tokenPattern = /[\p{L}\p{N}_-]+/gu;
7
+ const frontmatterPattern = /^---\n[\s\S]*?\n---\n?/m;
8
+ const rootHeadingPattern = /^#\s+.+\n+/m;
9
+ const maxCandidatesPerBucket = 240;
10
+ const normalizePath = (path) => path.replaceAll('\\', '/').replace(/^\.\//, '');
11
+ const toComparableBody = (content) => content
12
+ .replace(frontmatterPattern, '')
13
+ .replace(rootHeadingPattern, '')
14
+ .replaceAll('\r\n', '\n')
15
+ .trim();
16
+ const normalizeStrictContent = (content) => toComparableBody(content);
17
+ const normalizeSemanticContent = (content) => toComparableBody(content)
18
+ .replace(/\s+/g, ' ')
19
+ .trim();
20
+ const toHash = (value) => createHash('sha256').update(value, 'utf8').digest('hex');
21
+ const toCandidateId = (leftPath, rightPath) => [normalizePath(leftPath), normalizePath(rightPath)].sort((left, right) => left.localeCompare(right)).join('|');
22
+ const hasSharedTokens = (left, right) => {
23
+ const leftTokens = new Set((left.match(tokenPattern) ?? []).map((token) => token.toLowerCase()).filter((token) => token.length > 2));
24
+ const rightTokens = new Set((right.match(tokenPattern) ?? []).map((token) => token.toLowerCase()).filter((token) => token.length > 2));
25
+ if (leftTokens.size === 0 || rightTokens.size === 0) {
26
+ return false;
27
+ }
28
+ for (const token of leftTokens) {
29
+ if (rightTokens.has(token)) {
30
+ return true;
31
+ }
32
+ }
33
+ return false;
34
+ };
35
+ const relatedMarker = (targetTitle) => `Related: [[${targetTitle}]] priority: low #related-to`;
36
+ const ensureRelatedEdgeLine = (content, targetTitle) => {
37
+ const linkPattern = new RegExp(`\\[\\[\\s*${targetTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*(?:[\\]|#])?`, 'i');
38
+ if (linkPattern.test(content)) {
39
+ return content;
40
+ }
41
+ const trimmed = content.trimEnd();
42
+ return `${trimmed}\n\n${relatedMarker(targetTitle)}\n`;
43
+ };
44
+ const ensureMergedMarker = (content, targetTitle) => {
45
+ const marker = `Merged into [[${targetTitle}]]`;
46
+ if (content.includes(marker)) {
47
+ return content;
48
+ }
49
+ return `${content.trimEnd()}\n\n${marker} priority: low #related-to\n`;
50
+ };
51
+ const appendMergedContent = (baseContent, mergedTitle, mergedContent) => {
52
+ const marker = `## Merged Memory From [[${mergedTitle}]]`;
53
+ if (baseContent.includes(marker)) {
54
+ return baseContent;
55
+ }
56
+ const mergedBody = normalizeSemanticContent(mergedContent);
57
+ return `${baseContent.trimEnd()}\n\n${marker}\n\n${mergedBody}\n`;
58
+ };
59
+ const loadNoteRecords = async (vaultPath, agentId) => {
60
+ const absoluteVaultPath = await ensureVault(vaultPath);
61
+ const files = await readMarkdownFiles(vaultPath);
62
+ return files
63
+ .map((file) => {
64
+ const parsed = parseMarkdownDocument({
65
+ absolutePath: file.absolutePath,
66
+ vaultPath: absoluteVaultPath,
67
+ content: file.content,
68
+ createdAt: file.createdAt,
69
+ updatedAt: file.updatedAt
70
+ });
71
+ const strict = normalizeStrictContent(parsed.content);
72
+ const semantic = normalizeSemanticContent(parsed.content);
73
+ const embedding = createLocalEmbedding(`${parsed.title}\n${semantic}`);
74
+ return {
75
+ title: parsed.title,
76
+ path: normalizePath(parsed.path),
77
+ agentId: parsed.agentId,
78
+ content: parsed.content,
79
+ normalizedStrictContent: strict,
80
+ semanticContent: semantic,
81
+ embedding,
82
+ buckets: createEmbeddingBuckets(embedding, 20)
83
+ };
84
+ })
85
+ .filter((record) => (agentId ? record.agentId === agentId : true));
86
+ };
87
+ const pairToCandidate = (left, right, kind, score, reason) => ({
88
+ id: toCandidateId(left.path, right.path),
89
+ possibleDuplicate: true,
90
+ kind,
91
+ score: Number(score.toFixed(4)),
92
+ left: {
93
+ title: left.title,
94
+ path: left.path,
95
+ agentId: left.agentId
96
+ },
97
+ right: {
98
+ title: right.title,
99
+ path: right.path,
100
+ agentId: right.agentId
101
+ },
102
+ reason
103
+ });
104
+ const indexCandidatePairs = (notes) => {
105
+ const bucketMap = new Map();
106
+ notes.forEach((note, index) => {
107
+ note.buckets.forEach((bucket) => {
108
+ const current = bucketMap.get(bucket) ?? [];
109
+ if (current.length < maxCandidatesPerBucket) {
110
+ current.push(index);
111
+ bucketMap.set(bucket, current);
112
+ }
113
+ });
114
+ });
115
+ const pairKeys = new Set();
116
+ const pairs = [];
117
+ bucketMap.forEach((indexes) => {
118
+ for (let leftIndex = 0; leftIndex < indexes.length; leftIndex += 1) {
119
+ for (let rightIndex = leftIndex + 1; rightIndex < indexes.length; rightIndex += 1) {
120
+ const left = Math.min(indexes[leftIndex] ?? 0, indexes[rightIndex] ?? 0);
121
+ const right = Math.max(indexes[leftIndex] ?? 0, indexes[rightIndex] ?? 0);
122
+ const key = `${left}|${right}`;
123
+ if (!pairKeys.has(key)) {
124
+ pairKeys.add(key);
125
+ pairs.push([left, right]);
126
+ }
127
+ }
128
+ }
129
+ });
130
+ return pairs;
131
+ };
132
+ export const scanDuplicateNotes = async (vaultPath, options = {}) => {
133
+ const notes = await loadNoteRecords(vaultPath, options.agentId);
134
+ if (notes.length < 2) {
135
+ return [];
136
+ }
137
+ const minSemanticScore = options.minSemanticScore ?? 0.92;
138
+ const includeSemantic = options.includeSemantic !== false;
139
+ const seen = new Map();
140
+ const byHash = notes.reduce((state, note) => {
141
+ const key = toHash(note.normalizedStrictContent);
142
+ const current = state.get(key) ?? [];
143
+ current.push(note);
144
+ state.set(key, current);
145
+ return state;
146
+ }, new Map());
147
+ byHash.forEach((group) => {
148
+ if (group.length < 2) {
149
+ return;
150
+ }
151
+ const [base, ...rest] = group.sort((left, right) => left.path.localeCompare(right.path));
152
+ rest.forEach((note) => {
153
+ const candidate = pairToCandidate(base, note, 'exact', 1, 'Exact content hash match');
154
+ seen.set(candidate.id, candidate);
155
+ });
156
+ });
157
+ if (includeSemantic) {
158
+ const pairs = indexCandidatePairs(notes);
159
+ pairs.forEach(([leftIndex, rightIndex]) => {
160
+ const left = notes[leftIndex];
161
+ const right = notes[rightIndex];
162
+ if (!left || !right || left.path === right.path) {
163
+ return;
164
+ }
165
+ const id = toCandidateId(left.path, right.path);
166
+ if (seen.has(id)) {
167
+ return;
168
+ }
169
+ const score = cosineSimilarity(left.embedding, right.embedding);
170
+ const titleShared = hasSharedTokens(left.title, right.title);
171
+ const contentShared = hasSharedTokens(left.semanticContent, right.semanticContent);
172
+ if (score >= minSemanticScore && (titleShared || contentShared || score >= 0.975)) {
173
+ const candidate = pairToCandidate(left, right, 'semantic', score, 'High semantic similarity');
174
+ seen.set(id, candidate);
175
+ }
176
+ });
177
+ }
178
+ const focusPath = options.focusPath ? normalizePath(options.focusPath) : undefined;
179
+ const limited = Array.from(seen.values())
180
+ .filter((item) => (focusPath ? item.left.path === focusPath || item.right.path === focusPath : true))
181
+ .sort((left, right) => right.score - left.score || left.left.path.localeCompare(right.left.path))
182
+ .slice(0, Math.max(1, options.limit ?? 25));
183
+ return limited;
184
+ };
185
+ export const resolveDuplicateNotes = async (vaultPath, options) => {
186
+ const leftPath = normalizePath(options.leftPath);
187
+ const rightPath = normalizePath(options.rightPath);
188
+ if (leftPath === rightPath) {
189
+ throw new Error('leftPath and rightPath must be different notes.');
190
+ }
191
+ const notes = await loadNoteRecords(vaultPath);
192
+ const byPath = new Map(notes.map((note) => [note.path, note]));
193
+ const left = byPath.get(leftPath);
194
+ const right = byPath.get(rightPath);
195
+ if (!left || !right) {
196
+ throw new Error(`Duplicate resolution paths were not found in vault index source: ${leftPath}, ${rightPath}`);
197
+ }
198
+ const updates = new Map();
199
+ const leftRelated = ensureRelatedEdgeLine(left.content, right.title);
200
+ const rightRelated = ensureRelatedEdgeLine(right.content, left.title);
201
+ if (options.action === 'link') {
202
+ updates.set(left.path, leftRelated);
203
+ updates.set(right.path, rightRelated);
204
+ }
205
+ else if (options.action === 'ignore') {
206
+ updates.set(left.path, leftRelated);
207
+ }
208
+ else {
209
+ const mergedLeft = appendMergedContent(leftRelated, right.title, right.content);
210
+ const mergedRight = ensureMergedMarker(rightRelated, left.title);
211
+ updates.set(left.path, mergedLeft);
212
+ updates.set(right.path, mergedRight);
213
+ }
214
+ for (const [path, content] of updates) {
215
+ await writeMarkdownFile(vaultPath, path, content);
216
+ }
217
+ const shouldIndex = options.autoIndex !== false;
218
+ const index = shouldIndex ? await indexVault(vaultPath) : undefined;
219
+ return {
220
+ action: options.action,
221
+ leftPath,
222
+ rightPath,
223
+ updatedPaths: Array.from(updates.keys()).sort((leftValue, rightValue) => leftValue.localeCompare(rightValue)),
224
+ ...(index ? { index } : {})
225
+ };
226
+ };
@@ -25,6 +25,13 @@ body {
25
25
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
26
26
  }
27
27
 
28
+ body {
29
+ display: flex;
30
+ flex-direction: column;
31
+ min-height: 100vh;
32
+ min-height: 100dvh;
33
+ }
34
+
28
35
  button,
29
36
  input,
30
37
  select {
@@ -32,70 +39,94 @@ select {
32
39
  }
33
40
 
34
41
  .shell {
35
- display: grid;
36
- grid-template-columns: minmax(0, 1fr) 360px;
42
+ flex: 1 1 auto;
37
43
  width: 100%;
38
- height: 100svh;
44
+ min-height: 0;
39
45
  overflow: hidden;
40
46
  }
41
47
 
42
48
  .workspace {
49
+ display: grid;
50
+ grid-template-rows: auto minmax(0, 1fr);
43
51
  position: relative;
52
+ width: 100%;
53
+ height: 100%;
44
54
  min-width: 0;
45
55
  min-height: 0;
46
56
  }
47
57
 
48
- #graph {
49
- display: block;
58
+ .graph-header {
59
+ z-index: 5;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 12px;
63
+ min-height: 72px;
64
+ padding: 10px 16px;
65
+ border-bottom: 1px solid var(--line);
66
+ background: linear-gradient(180deg, rgba(17, 21, 27, 0.96) 0%, rgba(17, 21, 27, 0.86) 100%);
67
+ backdrop-filter: blur(8px);
68
+ }
69
+
70
+ .brand-block {
71
+ display: grid;
72
+ gap: 2px;
73
+ min-width: max-content;
74
+ }
75
+
76
+ .brand-block strong {
77
+ font-size: 18px;
78
+ }
79
+
80
+ .graph-stage {
81
+ position: relative;
50
82
  width: 100%;
51
83
  height: 100%;
52
84
  background:
53
85
  radial-gradient(circle at 18% 20%, rgba(53, 208, 162, 0.12), transparent 28rem),
54
86
  linear-gradient(135deg, #0d0f12 0%, #12161c 55%, #0a0d10 100%);
55
- cursor: grab;
87
+ overflow: hidden;
56
88
  }
57
89
 
58
- #graph:active {
59
- cursor: grabbing;
90
+ #graph,
91
+ #graphGl {
92
+ display: block;
93
+ position: absolute;
94
+ inset: 0;
95
+ width: 100%;
96
+ height: 100%;
60
97
  }
61
98
 
62
- .topbar {
63
- position: absolute;
64
- top: 18px;
65
- left: 18px;
66
- right: 18px;
67
- display: flex;
68
- align-items: center;
69
- justify-content: space-between;
70
- gap: 18px;
71
- pointer-events: none;
99
+ #graph {
100
+ cursor: grab;
72
101
  }
73
102
 
74
- .topbar > div {
75
- display: flex;
76
- align-items: baseline;
77
- gap: 12px;
103
+ #graphGl {
104
+ pointer-events: none;
78
105
  }
79
106
 
80
- .topbar strong {
81
- font-size: 18px;
107
+ #graph:active {
108
+ cursor: grabbing;
82
109
  }
83
110
 
84
- .topbar span,
85
- .eyebrow,
86
- .inspector small {
111
+ .eyebrow {
87
112
  color: var(--muted);
88
113
  font-size: 12px;
89
114
  }
90
115
 
91
116
  .search {
92
- width: min(420px, 42vw);
93
- pointer-events: auto;
117
+ flex: 1 1 320px;
118
+ min-width: 220px;
94
119
  }
95
120
 
96
121
  .agent-filter {
97
122
  width: min(220px, 28vw);
98
- pointer-events: auto;
123
+ }
124
+
125
+ .header-actions {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 10px;
129
+ margin-left: auto;
99
130
  }
100
131
 
101
132
  .search input,
@@ -116,9 +147,6 @@ select {
116
147
  }
117
148
 
118
149
  .toolbar {
119
- position: absolute;
120
- left: 18px;
121
- bottom: 18px;
122
150
  display: flex;
123
151
  gap: 8px;
124
152
  }
@@ -138,70 +166,34 @@ select {
138
166
  color: var(--accent);
139
167
  }
140
168
 
141
- .inspector {
142
- display: grid;
143
- grid-template-rows: auto auto auto minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
144
- gap: 22px;
145
- min-width: 0;
146
- height: 100%;
147
- padding: 24px;
148
- border-left: 1px solid var(--line);
149
- background: var(--panel);
150
- overflow: auto;
169
+ .floating-metrics {
170
+ display: flex;
171
+ gap: 10px;
172
+ flex-wrap: wrap;
151
173
  }
152
174
 
153
- .inspector h1,
154
- .inspector h2,
155
- .inspector p {
156
- margin: 0;
175
+ .metric-chip {
176
+ min-width: 94px;
177
+ padding: 10px 12px;
178
+ border: 1px solid var(--line);
179
+ border-radius: 10px;
180
+ background: rgba(21, 25, 31, 0.88);
181
+ display: grid;
182
+ gap: 3px;
157
183
  }
158
184
 
159
- .inspector h1 {
160
- margin-top: 6px;
185
+ .metric-chip strong {
161
186
  font-size: 26px;
162
- line-height: 1.12;
163
- overflow-wrap: anywhere;
187
+ line-height: 1;
164
188
  }
165
189
 
166
- .inspector h2 {
167
- margin-bottom: 10px;
190
+ .metric-chip small {
168
191
  color: var(--muted);
169
- font-size: 12px;
170
- font-weight: 700;
192
+ font-size: 11px;
193
+ letter-spacing: 0.03em;
171
194
  text-transform: uppercase;
172
195
  }
173
196
 
174
- #path {
175
- margin-top: 10px;
176
- color: var(--muted);
177
- line-height: 1.45;
178
- overflow-wrap: anywhere;
179
- }
180
-
181
- .metrics {
182
- display: grid;
183
- grid-template-columns: repeat(3, 1fr);
184
- border: 1px solid var(--line);
185
- border-radius: 8px;
186
- overflow: hidden;
187
- }
188
-
189
- .metrics div {
190
- display: grid;
191
- gap: 4px;
192
- padding: 14px;
193
- background: var(--panel-strong);
194
- }
195
-
196
- .metrics div + div {
197
- border-left: 1px solid var(--line);
198
- }
199
-
200
- .metrics span {
201
- font-size: 22px;
202
- font-weight: 700;
203
- }
204
-
205
197
  .tags {
206
198
  display: flex;
207
199
  flex-wrap: wrap;
@@ -215,6 +207,7 @@ select {
215
207
  background: var(--accent-weak);
216
208
  color: var(--accent);
217
209
  font-size: 12px;
210
+ word-break: break-word;
218
211
  overflow-wrap: anywhere;
219
212
  }
220
213
 
@@ -230,6 +223,7 @@ li {
230
223
  padding: 10px 0;
231
224
  border-bottom: 1px solid var(--line);
232
225
  color: var(--text);
226
+ word-break: break-word;
233
227
  overflow-wrap: anywhere;
234
228
  }
235
229
 
@@ -268,8 +262,8 @@ li small {
268
262
  }
269
263
 
270
264
  .content-dialog {
271
- width: min(920px, calc(100vw - 32px));
272
- max-height: calc(100svh - 32px);
265
+ width: min(1240px, calc(100vw - 24px));
266
+ max-height: calc(100svh - 20px);
273
267
  padding: 0;
274
268
  border: 1px solid var(--line);
275
269
  border-radius: 8px;
@@ -285,8 +279,8 @@ li small {
285
279
 
286
280
  .content-dialog article {
287
281
  display: grid;
288
- grid-template-rows: auto minmax(0, 1fr);
289
- max-height: calc(100svh - 34px);
282
+ grid-template-rows: auto auto minmax(0, 1fr);
283
+ max-height: calc(100svh - 22px);
290
284
  }
291
285
 
292
286
  .content-dialog header {
@@ -333,6 +327,41 @@ li small {
333
327
  color: var(--accent);
334
328
  }
335
329
 
330
+ .content-meta {
331
+ display: grid;
332
+ grid-template-columns: repeat(3, minmax(0, 1fr));
333
+ gap: 10px;
334
+ padding: 14px 22px;
335
+ border-bottom: 1px solid var(--line);
336
+ }
337
+
338
+ .content-meta-section {
339
+ min-height: 0;
340
+ padding: 10px;
341
+ border: 1px solid var(--line);
342
+ border-radius: 8px;
343
+ background: var(--panel-strong);
344
+ display: grid;
345
+ grid-template-rows: auto minmax(0, 1fr);
346
+ gap: 8px;
347
+ }
348
+
349
+ .content-meta-section h3 {
350
+ margin: 0;
351
+ color: var(--muted);
352
+ font-size: 11px;
353
+ font-weight: 700;
354
+ text-transform: uppercase;
355
+ }
356
+
357
+ .content-meta-section ul,
358
+ .content-meta-section .tags {
359
+ max-height: 220px;
360
+ overflow: auto;
361
+ align-content: flex-start;
362
+ padding-right: 4px;
363
+ }
364
+
336
365
  .content-dialog .note-content {
337
366
  max-height: none;
338
367
  min-height: 0;
@@ -341,33 +370,56 @@ li small {
341
370
  padding: 22px;
342
371
  }
343
372
 
344
- @media (max-width: 860px) {
345
- .shell {
346
- grid-template-columns: 1fr;
347
- grid-template-rows: minmax(0, 1fr) 42svh;
348
- }
373
+ .app-footer {
374
+ flex: 0 0 28px;
375
+ height: 28px;
376
+ display: flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ background: transparent;
380
+ }
349
381
 
350
- .inspector {
351
- border-left: 0;
352
- border-top: 1px solid var(--line);
353
- padding: 18px;
354
- }
382
+ .app-footer small {
383
+ color: var(--muted);
384
+ font-size: 11px;
385
+ letter-spacing: 0.02em;
386
+ }
355
387
 
356
- .topbar {
388
+ @media (max-width: 860px) {
389
+ .graph-header {
357
390
  align-items: stretch;
358
- flex-direction: column;
391
+ flex-wrap: wrap;
392
+ padding: 10px 12px;
393
+ min-height: 0;
359
394
  }
360
395
 
361
396
  .search {
362
397
  width: 100%;
398
+ flex-basis: 100%;
399
+ order: 3;
363
400
  }
364
401
 
365
402
  .agent-filter {
366
403
  width: 100%;
367
404
  }
368
405
 
406
+ .header-actions {
407
+ width: 100%;
408
+ margin-left: 0;
409
+ justify-content: space-between;
410
+ order: 4;
411
+ }
412
+
369
413
  .content-dialog header {
370
414
  align-items: stretch;
371
415
  flex-direction: column;
372
416
  }
417
+
418
+ .metric-chip {
419
+ min-width: 82px;
420
+ }
421
+
422
+ .content-meta {
423
+ grid-template-columns: 1fr;
424
+ }
373
425
  }`;