aimeat 1.14.6 → 1.15.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.
- package/dist/locales/en.json +18 -1
- package/dist/locales/fi.json +18 -1
- package/dist/public/css/views/admin.css +12 -0
- package/dist/public/css/views/agents-detail.css +164 -2
- package/dist/public/spa.html +4 -0
- package/dist/public/views/admin/config-tab.js +34 -6
- package/dist/public/views/agent-solo.js +123 -0
- package/dist/public/views/profile/agents/agent-card.js +37 -12
- package/dist/public/views/profile/agents/tab-data-access.js +34 -3
- package/dist/public/views/profile/agents/tab-directives.js +25 -15
- package/dist/public/views/profile/agents-tab.js +105 -16
- package/dist/public/views/profile/agents-tasks-subtab.js +43 -0
- package/dist/src/routes/agent-directives.d.ts +5 -0
- package/dist/src/routes/agent-directives.d.ts.map +1 -1
- package/dist/src/routes/agent-directives.js +35 -16
- package/dist/src/routes/agent-directives.js.map +1 -1
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +7 -0
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/sse.d.ts +13 -0
- package/dist/src/routes/sse.d.ts.map +1 -1
- package/dist/src/routes/sse.js +20 -0
- package/dist/src/routes/sse.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +14 -2
- package/dist/src/server.js.map +1 -1
- package/dist/src/storage/interface.d.ts +1 -0
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +3 -0
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
- package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +4 -1
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +1 -0
package/dist/locales/en.json
CHANGED
|
@@ -394,6 +394,11 @@
|
|
|
394
394
|
"untagged": "Untagged",
|
|
395
395
|
"noMatches": "No agents match the current filter."
|
|
396
396
|
},
|
|
397
|
+
"reorderHint": "Drag to reorder",
|
|
398
|
+
"solo": {
|
|
399
|
+
"notFound": "Agent not found.",
|
|
400
|
+
"loginRequired": "Please log in to view this agent."
|
|
401
|
+
},
|
|
397
402
|
"approval": {
|
|
398
403
|
"credentials_ready_title": "Agent approved",
|
|
399
404
|
"credentials_ready_desc": "Copy the credentials below and paste them to your agent.",
|
|
@@ -450,6 +455,9 @@
|
|
|
450
455
|
"starting": "Starting...",
|
|
451
456
|
"startThisTask": "Start this task",
|
|
452
457
|
"noEventsRecorded": "No events recorded",
|
|
458
|
+
"deliverable": "Deliverable",
|
|
459
|
+
"viewDeliverable": "View",
|
|
460
|
+
"deliverableGone": "The deliverable entry no longer exists.",
|
|
453
461
|
"revision_requested": "Revision requested",
|
|
454
462
|
"requestChanges": "Request changes",
|
|
455
463
|
"requestChangesTitle": "Ask the agent to revise the plan",
|
|
@@ -655,6 +663,7 @@
|
|
|
655
663
|
},
|
|
656
664
|
"sharedTags": "Shared tags",
|
|
657
665
|
"lastSeen": "Last seen",
|
|
666
|
+
"popOut": "Open in its own window",
|
|
658
667
|
"deliveryMcp": "MCP notifications",
|
|
659
668
|
"deliveryWebhook": "Webhook",
|
|
660
669
|
"deliveryPolling": "Polling",
|
|
@@ -798,7 +807,14 @@
|
|
|
798
807
|
"keyValuePlaceholder": "Value — plain text or JSON",
|
|
799
808
|
"noKeys": "No memory keys stored yet",
|
|
800
809
|
"keyCreated": "Memory key created",
|
|
801
|
-
"createKeyError": "Failed to create memory key"
|
|
810
|
+
"createKeyError": "Failed to create memory key",
|
|
811
|
+
"created": "Created",
|
|
812
|
+
"updated": "Updated",
|
|
813
|
+
"sortBy": "Sort",
|
|
814
|
+
"sortUpdated": "Last updated",
|
|
815
|
+
"sortCreated": "Created",
|
|
816
|
+
"sortNewestFirst": "Newest first",
|
|
817
|
+
"sortOldestFirst": "Oldest first"
|
|
802
818
|
},
|
|
803
819
|
"agent_config": {
|
|
804
820
|
"copy": "Copy",
|
|
@@ -3138,6 +3154,7 @@
|
|
|
3138
3154
|
"cfgReadOnlyHelpDetail": "Config persistence requires a database (MongoDB or SQLite). In-memory nodes lose all changes on restart. Configure this node via .env file, aimeat.ini, aimeat.json, or CLI arguments (aimeat start --db sqlite).",
|
|
3139
3155
|
"cfgHelpTitle": "Help",
|
|
3140
3156
|
"cfgReset": "Reset",
|
|
3157
|
+
"cfgOnePerLine": "One entry per line",
|
|
3141
3158
|
"cfgResetDone": "Reset {path} — DB override removed.",
|
|
3142
3159
|
"cfgSource": "Source",
|
|
3143
3160
|
"consul": "Consul",
|
package/dist/locales/fi.json
CHANGED
|
@@ -394,6 +394,11 @@
|
|
|
394
394
|
"untagged": "Ei tageja",
|
|
395
395
|
"noMatches": "Yksikään agentti ei vastaa nykyistä suodatinta."
|
|
396
396
|
},
|
|
397
|
+
"reorderHint": "Raahaa järjestääksesi",
|
|
398
|
+
"solo": {
|
|
399
|
+
"notFound": "Agenttia ei löytynyt.",
|
|
400
|
+
"loginRequired": "Kirjaudu sisään nähdäksesi tämän agentin."
|
|
401
|
+
},
|
|
397
402
|
"approval": {
|
|
398
403
|
"credentials_ready_title": "Agentti hyväksytty",
|
|
399
404
|
"credentials_ready_desc": "Kopioi alla olevat tunnistetiedot ja liitä ne agentillesi.",
|
|
@@ -450,6 +455,9 @@
|
|
|
450
455
|
"starting": "Käynnistetään...",
|
|
451
456
|
"startThisTask": "Käynnistä tehtävä",
|
|
452
457
|
"noEventsRecorded": "Ei tallennettuja tapahtumia",
|
|
458
|
+
"deliverable": "Tuotos",
|
|
459
|
+
"viewDeliverable": "Näytä",
|
|
460
|
+
"deliverableGone": "Tuotosmerkintää ei ole enää olemassa.",
|
|
453
461
|
"revision_requested": "Korjauspyyntö lähetetty",
|
|
454
462
|
"requestChanges": "Pyydä muutoksia",
|
|
455
463
|
"requestChangesTitle": "Pyydä agenttia muokkaamaan suunnitelmaa",
|
|
@@ -655,6 +663,7 @@
|
|
|
655
663
|
},
|
|
656
664
|
"sharedTags": "Jaetut tunnisteet",
|
|
657
665
|
"lastSeen": "Viimeksi nähty",
|
|
666
|
+
"popOut": "Avaa omaan ikkunaan",
|
|
658
667
|
"deliveryMcp": "MCP-ilmoitukset",
|
|
659
668
|
"deliveryWebhook": "Webhook",
|
|
660
669
|
"deliveryPolling": "Pollaus",
|
|
@@ -798,7 +807,14 @@
|
|
|
798
807
|
"keyValuePlaceholder": "Arvo — pelkkä teksti tai JSON",
|
|
799
808
|
"noKeys": "Ei tallennettuja muistiavaimia vielä",
|
|
800
809
|
"keyCreated": "Muistiavain luotu",
|
|
801
|
-
"createKeyError": "Muistiavaimen luonti epäonnistui"
|
|
810
|
+
"createKeyError": "Muistiavaimen luonti epäonnistui",
|
|
811
|
+
"created": "Luotu",
|
|
812
|
+
"updated": "Päivitetty",
|
|
813
|
+
"sortBy": "Järjestä",
|
|
814
|
+
"sortUpdated": "Viimeksi päivitetty",
|
|
815
|
+
"sortCreated": "Luotu",
|
|
816
|
+
"sortNewestFirst": "Uusin ensin",
|
|
817
|
+
"sortOldestFirst": "Vanhin ensin"
|
|
802
818
|
},
|
|
803
819
|
"agent_config": {
|
|
804
820
|
"copy": "Kopioi",
|
|
@@ -3138,6 +3154,7 @@
|
|
|
3138
3154
|
"cfgReadOnlyHelpDetail": "Asetusten tallennus vaatii tietokannan (MongoDB tai SQLite). Muistinsisäiset solmut menettävät kaikki muutokset uudelleenkäynnistyksessä. Määritä tämä solmu .env-tiedoston, aimeat.ini:n, aimeat.json:n tai CLI-argumenttien kautta.",
|
|
3139
3155
|
"cfgHelpTitle": "Ohje",
|
|
3140
3156
|
"cfgReset": "Palauta",
|
|
3157
|
+
"cfgOnePerLine": "Yksi kohta per rivi",
|
|
3141
3158
|
"cfgResetDone": "Palautettu {path} — tietokantaohitus poistettu.",
|
|
3142
3159
|
"cfgSource": "Lähde",
|
|
3143
3160
|
"consul": "Consul",
|
|
@@ -333,6 +333,18 @@
|
|
|
333
333
|
border: 1px solid #FDE68A;
|
|
334
334
|
border-radius: 8px;
|
|
335
335
|
}
|
|
336
|
+
.adm-config-array-edit {
|
|
337
|
+
width: 360px;
|
|
338
|
+
max-width: 100%;
|
|
339
|
+
min-height: 4.5rem;
|
|
340
|
+
font-family: 'JetBrains Mono', monospace;
|
|
341
|
+
font-size: 0.8rem;
|
|
342
|
+
padding: 6px 8px;
|
|
343
|
+
border: 1px solid var(--border, #E5E7EB);
|
|
344
|
+
border-radius: 6px;
|
|
345
|
+
resize: vertical;
|
|
346
|
+
box-sizing: border-box;
|
|
347
|
+
}
|
|
336
348
|
.adm-config-readonly { opacity: 0.85; pointer-events: none; }
|
|
337
349
|
.adm-config-readonly input,
|
|
338
350
|
.adm-config-readonly select { background: #F3F4F6; cursor: not-allowed; }
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* v2.4.0 -- 2026-05-24 -- Add header-row flex layout for title + connect button alignment
|
|
20
20
|
* v2.5.0 -- 2026-05-31 -- Add stored-memory-key inline editor (textarea), memory-detail wrapper, in-row edit/delete action rows, and danger-tinted ghost action styles for the Data Access tab
|
|
21
21
|
* v2.6.0 -- 2026-05-31 -- Add stored-key create form layout (pf-agd-keyadd-form, vertical stack)
|
|
22
|
+
* v2.7.0 -- 2026-05-31 -- Add stored-key sort controls (pf-agd-keys-controls/sort-*) and per-key created/updated meta (pf-agd-key-meta)
|
|
23
|
+
* v2.8.0 -- 2026-05-31 -- Add pop-out button (pf-agd-popout-btn), drag-to-reorder rows (pf-agd-dnd-*), and solo pop-out window layout (pf-agd-solo*)
|
|
24
|
+
* v2.9.0 -- 2026-05-31 -- Collapsed bar two-row layout (pf-agd-collapsed-main + pf-agd-collapsed-meta divider) to stop status overflow; move drag grip into the left margin so the card border stays flush
|
|
25
|
+
* v2.10.0 -- 2026-05-31 -- Extend the drag-row drop target left over the grip gutter (padding-left + negative margin-left) so dropping on/near the handle registers; card border stays aligned
|
|
26
|
+
* v2.11.0 -- 2026-05-31 -- Widen the solo pop-out window (and pf-agd-solo max-width) so the agent card matches the list-view width and the full tab bar fits
|
|
27
|
+
* v2.12.0 -- 2026-05-31 -- Add completed-task deliverable styles (pf-agd-deliverable*) linking a task to its agent-memory output key
|
|
22
28
|
*/
|
|
23
29
|
|
|
24
30
|
/* Header row: title left, connect button right */
|
|
@@ -1262,8 +1268,9 @@
|
|
|
1262
1268
|
}
|
|
1263
1269
|
.pf .pf-agd-collapsed {
|
|
1264
1270
|
display: flex;
|
|
1265
|
-
|
|
1266
|
-
|
|
1271
|
+
flex-direction: column;
|
|
1272
|
+
align-items: stretch;
|
|
1273
|
+
gap: 0;
|
|
1267
1274
|
padding: 0.625rem 0.875rem;
|
|
1268
1275
|
background: var(--card-bg, #FFFFFF);
|
|
1269
1276
|
border: 1px solid var(--border, #E5E7EB);
|
|
@@ -1277,6 +1284,21 @@
|
|
|
1277
1284
|
.pf .pf-agd-collapsed--problem {
|
|
1278
1285
|
border-color: var(--danger, #ef4444);
|
|
1279
1286
|
}
|
|
1287
|
+
/* Top row: expand arrow + icon + name + badges + pop-out */
|
|
1288
|
+
.pf .pf-agd-collapsed-main {
|
|
1289
|
+
display: flex;
|
|
1290
|
+
align-items: center;
|
|
1291
|
+
gap: 0.5rem;
|
|
1292
|
+
}
|
|
1293
|
+
/* Second row: delivery + status + last seen, smaller, faint divider above */
|
|
1294
|
+
.pf .pf-agd-collapsed-meta {
|
|
1295
|
+
margin-top: 0.4rem;
|
|
1296
|
+
padding-top: 0.35rem;
|
|
1297
|
+
border-top: 1px solid var(--border-subtle, #F3F4F6);
|
|
1298
|
+
font-size: 0.7rem;
|
|
1299
|
+
color: var(--text-muted, #9CA3AF);
|
|
1300
|
+
line-height: 1.4;
|
|
1301
|
+
}
|
|
1280
1302
|
.pf .pf-agd-expand-icon {
|
|
1281
1303
|
font-size: 0.7rem;
|
|
1282
1304
|
color: var(--text-dim, #6B7280);
|
|
@@ -1313,6 +1335,74 @@
|
|
|
1313
1335
|
text-align: right;
|
|
1314
1336
|
}
|
|
1315
1337
|
|
|
1338
|
+
/* Pop-out button (open agent in its own window) */
|
|
1339
|
+
.pf .pf-agd-popout-btn {
|
|
1340
|
+
background: none;
|
|
1341
|
+
border: none;
|
|
1342
|
+
cursor: pointer;
|
|
1343
|
+
color: var(--text-dim, #6B7280);
|
|
1344
|
+
font-size: 0.95rem;
|
|
1345
|
+
line-height: 1;
|
|
1346
|
+
padding: 0.1rem 0.35rem;
|
|
1347
|
+
border-radius: 4px;
|
|
1348
|
+
flex-shrink: 0;
|
|
1349
|
+
}
|
|
1350
|
+
.pf .pf-agd-collapsed-main .pf-agd-popout-btn { margin-left: auto; }
|
|
1351
|
+
.pf .pf-agd-popout-btn:hover {
|
|
1352
|
+
color: var(--accent, #E8564A);
|
|
1353
|
+
background: var(--bg-dim, #F9FAFB);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/* ── Drag-to-reorder rows (ungrouped, unfiltered agent list) ── */
|
|
1357
|
+
/* The row's box (= the drop target) is extended LEFT to cover the grip handle
|
|
1358
|
+
via padding-left, while a matching negative margin-left pulls the box back so
|
|
1359
|
+
the card border stays flush with the rest of the list. Net effect: the card
|
|
1360
|
+
stays aligned with the dividers above, the grip sits in the left gutter, and
|
|
1361
|
+
dropping anywhere over that gutter still drops onto the row (people grab the
|
|
1362
|
+
handle and drop near it). */
|
|
1363
|
+
.pf .pf-agd-dnd-row {
|
|
1364
|
+
position: relative;
|
|
1365
|
+
padding-left: 1.5rem;
|
|
1366
|
+
margin-left: -1.5rem;
|
|
1367
|
+
}
|
|
1368
|
+
.pf .pf-agd-dnd-grip {
|
|
1369
|
+
position: absolute;
|
|
1370
|
+
left: 0.2rem;
|
|
1371
|
+
top: 1.1rem;
|
|
1372
|
+
z-index: 2;
|
|
1373
|
+
cursor: grab;
|
|
1374
|
+
color: var(--text-muted, #9CA3AF);
|
|
1375
|
+
font-size: 0.85rem;
|
|
1376
|
+
line-height: 1;
|
|
1377
|
+
opacity: 0;
|
|
1378
|
+
transition: opacity 0.15s;
|
|
1379
|
+
user-select: none;
|
|
1380
|
+
}
|
|
1381
|
+
.pf .pf-agd-dnd-row:hover .pf-agd-dnd-grip { opacity: 0.7; }
|
|
1382
|
+
.pf .pf-agd-dnd-grip:active { cursor: grabbing; }
|
|
1383
|
+
.pf .pf-agd-dnd-dragging { opacity: 0.45; }
|
|
1384
|
+
|
|
1385
|
+
/* ── Solo (pop-out) window layout ── */
|
|
1386
|
+
.pf-agd-solo {
|
|
1387
|
+
padding: 0.5rem 1rem;
|
|
1388
|
+
max-width: 860px; /* card gets ≈828px — same usable width as the list view */
|
|
1389
|
+
margin: 0 auto;
|
|
1390
|
+
}
|
|
1391
|
+
.pf .pf-agd-solo-toast {
|
|
1392
|
+
position: fixed;
|
|
1393
|
+
bottom: 1rem;
|
|
1394
|
+
left: 50%;
|
|
1395
|
+
transform: translateX(-50%);
|
|
1396
|
+
background: var(--text, #1A1A2E);
|
|
1397
|
+
color: #FFFFFF;
|
|
1398
|
+
padding: 0.5rem 0.9rem;
|
|
1399
|
+
border-radius: 6px;
|
|
1400
|
+
font-size: 0.85rem;
|
|
1401
|
+
z-index: 1000;
|
|
1402
|
+
box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
|
|
1403
|
+
}
|
|
1404
|
+
.pf .pf-agd-solo-toast--err { background: var(--danger, #EF4444); }
|
|
1405
|
+
|
|
1316
1406
|
/* ── Badges (platform, readiness, federation) ── */
|
|
1317
1407
|
|
|
1318
1408
|
.pf .pf-agd-badge {
|
|
@@ -2001,6 +2091,34 @@
|
|
|
2001
2091
|
margin-top: 0.4rem;
|
|
2002
2092
|
}
|
|
2003
2093
|
|
|
2094
|
+
/* Completed-task deliverable link (task → agent-memory key) */
|
|
2095
|
+
.pf .pf-agd-deliverable {
|
|
2096
|
+
display: flex;
|
|
2097
|
+
align-items: center;
|
|
2098
|
+
gap: 0.5rem;
|
|
2099
|
+
flex-wrap: wrap;
|
|
2100
|
+
margin: 0.4rem 0;
|
|
2101
|
+
}
|
|
2102
|
+
.pf .pf-agd-deliverable-label {
|
|
2103
|
+
font-size: 0.78rem;
|
|
2104
|
+
font-weight: 600;
|
|
2105
|
+
color: var(--text-dim, #6B7280);
|
|
2106
|
+
}
|
|
2107
|
+
.pf .pf-agd-deliverable-key {
|
|
2108
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2109
|
+
font-size: 0.75rem;
|
|
2110
|
+
background: var(--code-bg, #F3F4F6);
|
|
2111
|
+
padding: 1px 6px;
|
|
2112
|
+
border-radius: 3px;
|
|
2113
|
+
word-break: break-all;
|
|
2114
|
+
}
|
|
2115
|
+
.pf .pf-agd-deliverable-gone {
|
|
2116
|
+
font-size: 0.78rem;
|
|
2117
|
+
color: var(--warn, #F59E0B);
|
|
2118
|
+
font-style: italic;
|
|
2119
|
+
margin: 0.25rem 0 0.5rem;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2004
2122
|
/* Danger-tinted ghost button used for in-row delete actions */
|
|
2005
2123
|
.pf .pf-agd-action-danger {
|
|
2006
2124
|
color: var(--danger, #EF4444);
|
|
@@ -2287,6 +2405,50 @@
|
|
|
2287
2405
|
.pf .pf-agd-keyadd-form select { width: 100%; box-sizing: border-box; }
|
|
2288
2406
|
.pf .pf-agd-keyadd-form .pf-agd-memory-edit { margin: 0; }
|
|
2289
2407
|
|
|
2408
|
+
/* Data access: stored-key sort controls (in the section header) */
|
|
2409
|
+
.pf .pf-agd-keys-controls {
|
|
2410
|
+
display: flex;
|
|
2411
|
+
align-items: center;
|
|
2412
|
+
gap: 0.4rem;
|
|
2413
|
+
flex-wrap: wrap;
|
|
2414
|
+
}
|
|
2415
|
+
.pf .pf-agd-sort-label {
|
|
2416
|
+
font-size: 0.72rem;
|
|
2417
|
+
color: var(--text-dim, #6B7280);
|
|
2418
|
+
}
|
|
2419
|
+
.pf .pf-agd-sort-select {
|
|
2420
|
+
padding: 0.2rem 0.4rem;
|
|
2421
|
+
border: 1px solid var(--border, #E5E7EB);
|
|
2422
|
+
border-radius: 4px;
|
|
2423
|
+
background: var(--card-bg, #FFFFFF);
|
|
2424
|
+
color: var(--text, #1A1A2E);
|
|
2425
|
+
font-size: 0.78rem;
|
|
2426
|
+
}
|
|
2427
|
+
.pf .pf-agd-sort-dir {
|
|
2428
|
+
border: 1px solid var(--border, #E5E7EB);
|
|
2429
|
+
border-radius: 4px;
|
|
2430
|
+
background: var(--card-bg, #FFFFFF);
|
|
2431
|
+
color: var(--text-dim, #6B7280);
|
|
2432
|
+
cursor: pointer;
|
|
2433
|
+
font-size: 0.85rem;
|
|
2434
|
+
line-height: 1;
|
|
2435
|
+
padding: 0.2rem 0.45rem;
|
|
2436
|
+
}
|
|
2437
|
+
.pf .pf-agd-sort-dir:hover {
|
|
2438
|
+
border-color: var(--accent, #E8564A);
|
|
2439
|
+
color: var(--accent, #E8564A);
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/* Data access: per-key created/updated meta in the stored-key row */
|
|
2443
|
+
.pf .pf-agd-key-meta {
|
|
2444
|
+
margin-left: auto;
|
|
2445
|
+
display: flex;
|
|
2446
|
+
gap: 0.75rem;
|
|
2447
|
+
font-size: 0.7rem;
|
|
2448
|
+
color: var(--text-muted, #9CA3AF);
|
|
2449
|
+
white-space: nowrap;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2290
2452
|
/* Data access: package picker */
|
|
2291
2453
|
.pf .pf-agd-package-picker {
|
|
2292
2454
|
margin-bottom: 0.5rem;
|
package/dist/public/spa.html
CHANGED
|
@@ -238,6 +238,10 @@
|
|
|
238
238
|
if (pathname === '/v1/portal' && search.includes('view=dev')) {
|
|
239
239
|
return () => import('/views/portal-dev.js' + B);
|
|
240
240
|
}
|
|
241
|
+
// Single-agent pop-out window: /v1/profile?solo=<agentName>
|
|
242
|
+
if (pathname === '/v1/profile' && /[?&]solo=/.test(search)) {
|
|
243
|
+
return () => import('/views/agent-solo.js' + B);
|
|
244
|
+
}
|
|
241
245
|
// Portfolio public view: /v1/portfolio/<username>
|
|
242
246
|
if (pathname.startsWith('/v1/portfolio/') && pathname !== '/v1/portfolio/') {
|
|
243
247
|
return () => import('/views/portfolio.js' + B);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file config-tab.js
|
|
3
|
+
* @description Admin dashboard Config tab — renders the mutable node config
|
|
4
|
+
* schema (GET /v1/admin/config) with per-type editors and persists changes
|
|
5
|
+
* via PUT /v1/admin/config.
|
|
6
|
+
* @version-history
|
|
7
|
+
* v1.1.0 -- 2026-05-31 -- Add an editor for array-of-strings config fields
|
|
8
|
+
* (e.g. agent.system_principles): one item per line in a textarea, split
|
|
9
|
+
* back to an array on save. Previously object/array fields were read-only.
|
|
10
|
+
* v1.2.0 -- 2026-05-31 -- Fix: inputs were bound to the saved value, not the
|
|
11
|
+
* pending edit, so toggling a checkbox / typing snapped straight back on
|
|
12
|
+
* re-render (looked un-editable). All inputs now reflect pending[path].
|
|
13
|
+
*/
|
|
1
14
|
import { h } from 'preact';
|
|
2
15
|
import { useState } from 'preact/hooks';
|
|
3
16
|
import htm from 'htm';
|
|
@@ -51,7 +64,16 @@ export default function ConfigTab({ data, reload }) {
|
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
async function save() {
|
|
54
|
-
const changes = Object.entries(pending).map(([path, value]) =>
|
|
67
|
+
const changes = Object.entries(pending).map(([path, value]) => {
|
|
68
|
+
const entry = schema[path];
|
|
69
|
+
// Array-of-strings fields (e.g. agent.system_principles) are edited as
|
|
70
|
+
// one item per line; split the raw text back into an array on save so it
|
|
71
|
+
// matches what the API validator expects.
|
|
72
|
+
if (entry && entry.type === 'object' && Array.isArray(entry.value) && typeof value === 'string') {
|
|
73
|
+
return { path, value: value.split('\n').map(l => l.trim()).filter(Boolean) };
|
|
74
|
+
}
|
|
75
|
+
return { path, value };
|
|
76
|
+
});
|
|
55
77
|
if (!changes.length) return;
|
|
56
78
|
try {
|
|
57
79
|
const r = await saveConfig(changes);
|
|
@@ -129,15 +151,21 @@ export default function ConfigTab({ data, reload }) {
|
|
|
129
151
|
? html`${e.value ? html`<${Badge} type="healthy" /> ${t('dashboard.yesLabel')}` : html`<${Badge} type="critical" /> ${t('dashboard.noLabel')}`}`
|
|
130
152
|
: html`<code>${escHtml(String(e.value))}</code> <span class="adm-text-dim adm-text-xs">${t('dashboard.readOnly')}</span>`)
|
|
131
153
|
: e.type === 'boolean'
|
|
132
|
-
? html`<label style="cursor:pointer"><input type="checkbox" checked=${e.value} onChange=${ev => onChange(p, ev.target.checked)} disabled=${!editable} /> ${e.value ? t('dashboard.enabled') : t('dashboard.disabled')}</label>`
|
|
154
|
+
? html`<label style="cursor:pointer"><input type="checkbox" checked=${p in pending ? pending[p] : e.value} onChange=${ev => onChange(p, ev.target.checked)} disabled=${!editable} /> ${(p in pending ? pending[p] : e.value) ? t('dashboard.enabled') : t('dashboard.disabled')}</label>`
|
|
133
155
|
: e.type === 'integer'
|
|
134
|
-
? html`<input type="number" value=${e.value} style="width:120px"
|
|
156
|
+
? html`<input type="number" value=${p in pending ? pending[p] : e.value} style="width:120px" onInput=${ev => onChange(p, parseInt(ev.target.value))} disabled=${!editable} />${e.range ? html` <span class="adm-text-dim adm-text-xs">${escHtml(e.range)}</span>` : null}`
|
|
135
157
|
: e.type === 'float'
|
|
136
|
-
? html`<input type="number" step="0.01" value=${e.value} style="width:120px"
|
|
158
|
+
? html`<input type="number" step="0.01" value=${p in pending ? pending[p] : e.value} style="width:120px" onInput=${ev => onChange(p, parseFloat(ev.target.value))} disabled=${!editable} />${e.range ? html` <span class="adm-text-dim adm-text-xs">${escHtml(e.range)}</span>` : null}`
|
|
137
159
|
: e.type === 'string'
|
|
138
|
-
? html`<input type="text" value=${e.value || ''} style="width:250px"
|
|
160
|
+
? html`<input type="text" value=${p in pending ? pending[p] : (e.value || '')} style="width:250px" onInput=${ev => onChange(p, ev.target.value)} disabled=${!editable} />`
|
|
139
161
|
: e.type === 'object'
|
|
140
|
-
?
|
|
162
|
+
? (Array.isArray(e.value)
|
|
163
|
+
? html`<textarea class="adm-config-array-edit" rows="4" disabled=${!editable}
|
|
164
|
+
value=${pending[p] !== undefined ? pending[p] : e.value.join('\n')}
|
|
165
|
+
onInput=${ev => onChange(p, ev.target.value)}
|
|
166
|
+
placeholder=${t('dashboard.cfgOnePerLine')}></textarea>
|
|
167
|
+
<div class="adm-text-dim adm-text-xs">${t('dashboard.cfgOnePerLine')}</div>`
|
|
168
|
+
: html`<code style="font-size:.75rem">${escHtml(JSON.stringify(e.value)).substring(0, 100)}...</code>`)
|
|
141
169
|
: html`<code>${escHtml(String(e.value))}</code>`
|
|
142
170
|
}
|
|
143
171
|
${e.canReset && editable && html` <button class="adm-btn-sm" onClick=${() => resetConfig(p)}>${t('dashboard.cfgReset')}</button>`}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file agent-solo.js
|
|
3
|
+
* @description Standalone single-agent view, served at /v1/profile?solo=<name>.
|
|
4
|
+
* Opened in its own window by the agent "pop out" button so the owner can
|
|
5
|
+
* place several agents side by side. Reuses the AgentCard component in
|
|
6
|
+
* soloMode (always expanded, no collapse, no pop-out button) and shares the
|
|
7
|
+
* logged-in session via localStorage (same origin). Opens its own SSE
|
|
8
|
+
* connection so the card live-updates independently.
|
|
9
|
+
* @structure AgentSolo (default export)
|
|
10
|
+
* @version-history
|
|
11
|
+
* v1.0.0 -- 2026-05-31 -- Initial creation for the agent pop-out window
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { h } from 'preact';
|
|
15
|
+
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
|
|
16
|
+
import htm from 'htm';
|
|
17
|
+
import { t } from '/js/i18n.js';
|
|
18
|
+
import { getSession } from '/js/services/auth.js';
|
|
19
|
+
import { connect, disconnect, onUpdate, offUpdate } from '/lib/live-updates.js';
|
|
20
|
+
import { listAgents } from '/js/services/agents.js';
|
|
21
|
+
import { getOnboarding } from '/js/services/agent-integration.js';
|
|
22
|
+
import { listTasks } from '/js/services/agent-tasks.js';
|
|
23
|
+
import AgentCard from './profile/agents/agent-card.js';
|
|
24
|
+
|
|
25
|
+
const html = htm.bind(h);
|
|
26
|
+
|
|
27
|
+
function getSoloName() {
|
|
28
|
+
try { return new URLSearchParams(window.location.search).get('solo') || ''; }
|
|
29
|
+
catch { return ''; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function AgentSolo() {
|
|
33
|
+
const session = getSession();
|
|
34
|
+
const name = getSoloName();
|
|
35
|
+
const [agent, setAgent] = useState(null);
|
|
36
|
+
const [onboarding, setOnboarding] = useState(null);
|
|
37
|
+
const [allAgents, setAllAgents] = useState([]);
|
|
38
|
+
const [error, setError] = useState(null);
|
|
39
|
+
const [loading, setLoading] = useState(true);
|
|
40
|
+
const [toast, setToast] = useState(null);
|
|
41
|
+
|
|
42
|
+
const showToast = useCallback((msg, isErr = false) => {
|
|
43
|
+
setToast({ msg, isErr });
|
|
44
|
+
setTimeout(() => setToast(cur => (cur && cur.msg === msg ? null : cur)), 3000);
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const load = useCallback(async () => {
|
|
48
|
+
if (!session || !name) return;
|
|
49
|
+
try {
|
|
50
|
+
const list = await listAgents(session.owner);
|
|
51
|
+
setAllAgents(list);
|
|
52
|
+
const a = list.find(x => x.name === name);
|
|
53
|
+
if (!a) { setError(t('profile.agents.solo.notFound')); setLoading(false); return; }
|
|
54
|
+
// Enrich with today's task stats, same as the list view does.
|
|
55
|
+
let taskStats = null;
|
|
56
|
+
try {
|
|
57
|
+
const [doneResp, activeResp] = await Promise.all([
|
|
58
|
+
listTasks(name, { status: 'done', per_page: 100 }),
|
|
59
|
+
listTasks(name, { status: 'active', per_page: 100 }),
|
|
60
|
+
]);
|
|
61
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
62
|
+
taskStats = {
|
|
63
|
+
done: (doneResp?.data?.tasks || []).filter(tk => tk.completedAt?.startsWith(today)).length,
|
|
64
|
+
active: (activeResp?.data?.tasks || []).length,
|
|
65
|
+
};
|
|
66
|
+
} catch { /* stats are best-effort */ }
|
|
67
|
+
setAgent({ ...a, taskStats });
|
|
68
|
+
try { const ob = await getOnboarding(name); setOnboarding(ob?.data?.onboarding || null); }
|
|
69
|
+
catch { setOnboarding(null); }
|
|
70
|
+
setError(null);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
setError(e.message || 'Error');
|
|
73
|
+
}
|
|
74
|
+
setLoading(false);
|
|
75
|
+
}, [session, name]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => { load(); }, [load]);
|
|
78
|
+
|
|
79
|
+
// Title the window after the agent so multiple pop-outs are distinguishable.
|
|
80
|
+
useEffect(() => { if (name) document.title = `${name} — AIMEAT`; }, [name]);
|
|
81
|
+
|
|
82
|
+
// This is a standalone window — profile.js (which normally bridges SSE to the
|
|
83
|
+
// `aimeat-live-update` event the tabs listen for) is not mounted here, so we
|
|
84
|
+
// open our own connection and dispatch the event ourselves.
|
|
85
|
+
const loadRef = useRef(load);
|
|
86
|
+
loadRef.current = load;
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!session) return;
|
|
89
|
+
const notify = () => {
|
|
90
|
+
window.dispatchEvent(new CustomEvent('aimeat-live-update'));
|
|
91
|
+
loadRef.current();
|
|
92
|
+
};
|
|
93
|
+
connect(() => getSession()?.jwt);
|
|
94
|
+
onUpdate(notify);
|
|
95
|
+
return () => { offUpdate(notify); disconnect(); };
|
|
96
|
+
}, [session?.owner]);
|
|
97
|
+
|
|
98
|
+
if (!session) {
|
|
99
|
+
return html`<div class="pf pf-agd-solo"><div class="pf-agd-empty">${t('profile.agents.solo.loginRequired')}</div></div>`;
|
|
100
|
+
}
|
|
101
|
+
if (loading && !agent) {
|
|
102
|
+
return html`<div class="pf pf-agd-solo"><div class="pf-agd-empty">${t('profile.loading')}</div></div>`;
|
|
103
|
+
}
|
|
104
|
+
if (error) {
|
|
105
|
+
return html`<div class="pf pf-agd-solo"><div class="pf-agd-empty">${error}</div></div>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return html`
|
|
109
|
+
<div class="pf pf-agd-solo">
|
|
110
|
+
<${AgentCard}
|
|
111
|
+
agent=${agent}
|
|
112
|
+
onboarding=${onboarding}
|
|
113
|
+
expanded=${true}
|
|
114
|
+
soloMode=${true}
|
|
115
|
+
onToggle=${() => {}}
|
|
116
|
+
session=${session}
|
|
117
|
+
showToast=${showToast}
|
|
118
|
+
allAgents=${allAgents}
|
|
119
|
+
/>
|
|
120
|
+
${toast && html`<div class="pf-agd-solo-toast ${toast.isErr ? 'pf-agd-solo-toast--err' : ''}">${toast.msg}</div>`}
|
|
121
|
+
</div>
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
* v1.5.0 -- 2026-05-31 -- Add README tab, rendered from the agents.<name>.readme
|
|
12
12
|
* owner-namespaced memory entry. Shown first + selected by default when a
|
|
13
13
|
* non-empty README exists; hidden otherwise.
|
|
14
|
+
* v1.6.0 -- 2026-05-31 -- Add `onPopOut` prop (pop-out ⤢ button on collapsed
|
|
15
|
+
* bar + expanded footer that opens the agent in its own window) and
|
|
16
|
+
* `soloMode` prop (used by the standalone /v1/profile?solo= view: forces
|
|
17
|
+
* expanded, disables collapse, hides the pop-out button).
|
|
18
|
+
* v1.7.0 -- 2026-05-31 -- Collapsed bar split into two rows (identity+badges on
|
|
19
|
+
* top, delivery/status/last-seen on a faint-divider-separated second line)
|
|
20
|
+
* so the status text stops overflowing the card.
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import { h } from 'preact';
|
|
@@ -68,7 +75,7 @@ async function fetchReadme(agent) {
|
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, onScopesClick, onDeleteClick, onFederateToggle }) {
|
|
78
|
+
export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, onScopesClick, onDeleteClick, onFederateToggle, onPopOut, soloMode = false }) {
|
|
72
79
|
const state = detectAgentState(agent, onboarding);
|
|
73
80
|
const [activeTab, setActiveTab] = useState(null);
|
|
74
81
|
// README markdown: undefined = not loaded, '' = none published, string = show tab.
|
|
@@ -99,28 +106,36 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
99
106
|
}, [expanded, readme]);
|
|
100
107
|
|
|
101
108
|
const handleCollapse = useCallback(() => {
|
|
109
|
+
// In solo mode the card is the whole window — clicking the header must not
|
|
110
|
+
// collapse it (there is nothing to collapse back into).
|
|
111
|
+
if (soloMode) return;
|
|
102
112
|
onToggle(agent.name);
|
|
103
113
|
if (expanded) { setActiveTab(null); userPickedTab.current = false; }
|
|
104
|
-
}, [expanded, agent.name, onToggle]);
|
|
114
|
+
}, [expanded, agent.name, onToggle, soloMode]);
|
|
105
115
|
|
|
106
116
|
if (!expanded) {
|
|
107
117
|
return html`
|
|
108
118
|
<div class="pf-agd-card">
|
|
109
119
|
<div class="pf-agd-collapsed ${state === 'problem' ? 'pf-agd-collapsed--problem' : ''}"
|
|
110
120
|
onClick=${() => onToggle(agent.name)}>
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
<div class="pf-agd-collapsed-main">
|
|
122
|
+
<span class="pf-agd-expand-icon">▶</span>
|
|
123
|
+
<span class="pf-agd-collapsed-icon">🤖</span>
|
|
124
|
+
<span class="pf-agd-collapsed-name">${agent.display_name || agent.name}</span>
|
|
125
|
+
<div class="pf-agd-collapsed-badges">
|
|
126
|
+
${renderModeBadge(agent)}
|
|
127
|
+
${renderPlatformBadge(onboarding)}
|
|
128
|
+
${renderReadinessBadge(state, onboarding)}
|
|
129
|
+
${agent.federate && html`<span class="pf-agd-badge pf-agd-badge--federation">${t('profile.federated')}</span>`}
|
|
130
|
+
</div>
|
|
131
|
+
${renderPopOut(onPopOut, agent)}
|
|
119
132
|
</div>
|
|
120
|
-
|
|
133
|
+
<!-- Second line: faint-divider-separated meta (delivery + status + last seen)
|
|
134
|
+
so the long status text gets its own row instead of overflowing. -->
|
|
135
|
+
<div class="pf-agd-collapsed-meta">
|
|
121
136
|
${renderDeliveryIndicator(agent)}
|
|
122
137
|
${renderCollapsedStats(state, agent, onboarding)}
|
|
123
|
-
</
|
|
138
|
+
</div>
|
|
124
139
|
</div>
|
|
125
140
|
</div>
|
|
126
141
|
`;
|
|
@@ -189,6 +204,7 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
189
204
|
<!-- Card Footer: Scopes + Delete -->
|
|
190
205
|
<div class="pf-agd-card-footer">
|
|
191
206
|
<div class="pf-agd-card-actions">
|
|
207
|
+
${renderPopOut(onPopOut, agent)}
|
|
192
208
|
${onScopesClick && html`
|
|
193
209
|
<button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); onScopesClick(agent); }}>
|
|
194
210
|
${t('profile.agents.scopeUi.manage')}
|
|
@@ -211,6 +227,15 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
211
227
|
`;
|
|
212
228
|
}
|
|
213
229
|
|
|
230
|
+
// Pop-out button: opens this agent in its own window (one window per agent,
|
|
231
|
+
// so re-clicking focuses the existing one). Only rendered when the parent
|
|
232
|
+
// supplies onPopOut — the standalone solo view omits it.
|
|
233
|
+
function renderPopOut(onPopOut, agent) {
|
|
234
|
+
if (!onPopOut) return null;
|
|
235
|
+
return html`<button class="pf-agd-popout-btn" title=${t('profile.agents.detail.popOut')}
|
|
236
|
+
onClick=${(e) => { e.stopPropagation(); onPopOut(agent); }}>⤢</button>`;
|
|
237
|
+
}
|
|
238
|
+
|
|
214
239
|
function renderDeliveryIndicator(agent) {
|
|
215
240
|
const hasWebhook = agent.webhookUrl || agent.webhook_url;
|
|
216
241
|
const hasMcp = agent.mcpEnabled || agent.mcp_enabled;
|