ltcai 1.6.0 → 2.0.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/README.md +40 -19
- package/docs/CHANGELOG.md +107 -0
- package/docs/EDITION_STRATEGY.md +14 -4
- package/docs/ENTERPRISE.md +11 -3
- package/docs/MULTI_AGENT_RUNTIME.md +410 -0
- package/docs/PLUGIN_SDK.md +651 -0
- package/docs/REALTIME_COLLABORATION.md +410 -0
- package/docs/V2_ARCHITECTURE.md +528 -0
- package/docs/WORKFLOW_DESIGNER.md +475 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agents.py +98 -0
- package/latticeai/api/plugins.py +115 -0
- package/latticeai/api/realtime.py +91 -0
- package/latticeai/api/workflow_designer.py +207 -0
- package/latticeai/core/multi_agent.py +270 -0
- package/latticeai/core/plugins.py +400 -0
- package/latticeai/core/realtime.py +190 -0
- package/latticeai/core/workflow_engine.py +329 -0
- package/latticeai/core/workspace_os.py +165 -2
- package/latticeai/server_app.py +76 -2
- package/latticeai/services/platform_runtime.py +200 -0
- package/package.json +17 -2
- package/plugins/README.md +35 -0
- package/plugins/git-insights/plugin.json +15 -0
- package/plugins/hello-world/plugin.json +16 -0
- package/plugins/hello-world/skills/hello_skill/SKILL.md +15 -0
- package/static/activity.html +70 -0
- package/static/admin.html +62 -0
- package/static/agents.html +92 -0
- package/static/graph.html +7 -1
- package/static/lattice-reference.css +184 -0
- package/static/platform.css +75 -0
- package/static/plugins.html +82 -0
- package/static/scripts/admin.js +121 -1
- package/static/scripts/graph.js +296 -14
- package/static/scripts/platform.js +64 -0
- package/static/scripts/workspace.js +107 -10
- package/static/workflows.html +121 -0
- package/static/workspace.css +73 -0
- package/static/workspace.html +18 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Multi-Agent Runtime — Lattice AI</title>
|
|
7
|
+
<link rel="stylesheet" href="/static/platform.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Multi-Agent Runtime</h1>
|
|
12
|
+
<p class="sub">Planner · Executor · Reviewer · Researcher · Release — with handoff, retry, and an observable timeline.</p>
|
|
13
|
+
|
|
14
|
+
<div class="section">
|
|
15
|
+
<label>Goal</label>
|
|
16
|
+
<textarea id="goal" placeholder="e.g. Draft a release checklist for v2.0.0">Summarize and verify the latest workspace activity</textarea>
|
|
17
|
+
<label>Roles (pipeline)</label>
|
|
18
|
+
<div id="roleChips" class="row"></div>
|
|
19
|
+
<div class="row" style="margin-top:14px"><button id="runBtn">Run agents</button></div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="section">
|
|
23
|
+
<h3>Run result</h3>
|
|
24
|
+
<div id="result"><div class="empty">No run yet.</div></div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="section">
|
|
28
|
+
<h3>Recent agent runs</h3>
|
|
29
|
+
<div id="runs"><div class="empty">Loading…</div></div>
|
|
30
|
+
</div>
|
|
31
|
+
</main>
|
|
32
|
+
|
|
33
|
+
<script type="module">
|
|
34
|
+
import { mountHeader, api, escapeHtml, badge, toast } from "/static/scripts/platform.js";
|
|
35
|
+
mountHeader("/agents");
|
|
36
|
+
|
|
37
|
+
const selected = new Set(["planner", "executor", "reviewer"]);
|
|
38
|
+
async function loadRoles() {
|
|
39
|
+
const data = await api("/agents/api/roles");
|
|
40
|
+
document.getElementById("roleChips").innerHTML = data.roles.map((r) =>
|
|
41
|
+
`<label class="badge" style="cursor:pointer"><input type="checkbox" value="${r.role}" ${selected.has(r.role)?"checked":""} style="width:auto;margin-right:6px">${escapeHtml(r.role)}</label>`
|
|
42
|
+
).join(" ");
|
|
43
|
+
document.getElementById("roleChips").addEventListener("change", (e) => {
|
|
44
|
+
if (e.target.checked) selected.add(e.target.value); else selected.delete(e.target.value);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderTimeline(timeline) {
|
|
49
|
+
return (timeline || []).map((t) => {
|
|
50
|
+
const label = t.event === "handoff" ? `↪ handoff ${escapeHtml(t.from)} → ${escapeHtml(t.to)}`
|
|
51
|
+
: t.event === "role" ? `● ${escapeHtml(t.role)} ${badge(t.status)}`
|
|
52
|
+
: `· ${escapeHtml(t.event)}`;
|
|
53
|
+
return `<div class="timeline-item">${label}<div class="t-meta">${escapeHtml(t.note||t.timestamp||"")}</div></div>`;
|
|
54
|
+
}).join("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
document.getElementById("runBtn").addEventListener("click", async () => {
|
|
58
|
+
const btn = document.getElementById("runBtn");
|
|
59
|
+
btn.disabled = true;
|
|
60
|
+
try {
|
|
61
|
+
const res = await api("/agents/api/run", { method: "POST", body: JSON.stringify({
|
|
62
|
+
goal: document.getElementById("goal").value, roles: [...selected], inputs: {}
|
|
63
|
+
}) });
|
|
64
|
+
const r = res.result;
|
|
65
|
+
document.getElementById("result").innerHTML = `
|
|
66
|
+
<div class="card">
|
|
67
|
+
<div class="row"><h3>${escapeHtml(r.output)}</h3><div class="spacer"></div>${badge(r.status)}</div>
|
|
68
|
+
<div class="meta">retries: ${r.retries} · roles: ${(r.roles_run||[]).join(" → ")}</div>
|
|
69
|
+
<div class="section">${renderTimeline(r.timeline)}</div>
|
|
70
|
+
</div>`;
|
|
71
|
+
toast(`Agent run: ${r.status}`);
|
|
72
|
+
await loadRuns();
|
|
73
|
+
} catch (err) { toast(err.message); } finally { btn.disabled = false; }
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
async function loadRuns() {
|
|
77
|
+
const data = await api("/agents/api/runs");
|
|
78
|
+
const runs = data.runs || [];
|
|
79
|
+
const box = document.getElementById("runs");
|
|
80
|
+
if (!runs.length) { box.innerHTML = `<div class="empty">No agent runs yet.</div>`; return; }
|
|
81
|
+
box.innerHTML = runs.slice(0, 20).map((r) => `
|
|
82
|
+
<div class="card" style="margin-bottom:10px">
|
|
83
|
+
<div class="row"><h3>${escapeHtml((r.input||"").slice(0,80))}</h3><div class="spacer"></div>${badge(r.status)}</div>
|
|
84
|
+
<div class="meta">${escapeHtml(r.agent_id)} · ${escapeHtml(r.created_at)} · ${(r.timeline||[]).length} timeline events</div>
|
|
85
|
+
</div>`).join("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
loadRoles().catch((e) => toast(e.message));
|
|
89
|
+
loadRuns().catch(() => {});
|
|
90
|
+
</script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|
package/static/graph.html
CHANGED
|
@@ -51,7 +51,12 @@
|
|
|
51
51
|
</section>
|
|
52
52
|
|
|
53
53
|
<div class="toolbar">
|
|
54
|
-
<button class="tb-btn" id="refresh-btn"
|
|
54
|
+
<button class="tb-btn" id="refresh-btn"><i class="ti ti-refresh"></i> Refresh</button>
|
|
55
|
+
<button class="tb-btn" id="fit-btn" title="Fit graph"><i class="ti ti-arrows-maximize"></i> Fit</button>
|
|
56
|
+
<button class="tb-btn" id="expand-btn" title="Expand selected node"><i class="ti ti-circle-plus"></i> Expand</button>
|
|
57
|
+
<button class="tb-btn" id="collapse-btn" title="Collapse selected neighbors"><i class="ti ti-circle-minus"></i> Collapse</button>
|
|
58
|
+
<button class="tb-btn" id="focus-btn" title="Focus selected subgraph"><i class="ti ti-focus-2"></i> Focus</button>
|
|
59
|
+
<button class="tb-btn" id="path-btn" title="Shortest path from saved start"><i class="ti ti-route"></i> Path</button>
|
|
55
60
|
<div class="lang-picker" id="graph-lang-picker">
|
|
56
61
|
<button class="tb-btn" id="graph-lang-btn" type="button" onclick="toggleLangMenu('graph-lang-picker')">Language</button>
|
|
57
62
|
<div class="lang-picker-menu" id="graph-lang-picker-menu">
|
|
@@ -60,6 +65,7 @@
|
|
|
60
65
|
</div>
|
|
61
66
|
</div>
|
|
62
67
|
</div>
|
|
68
|
+
<div id="graph-focus-chip" class="focus-chip" hidden></div>
|
|
63
69
|
</main>
|
|
64
70
|
|
|
65
71
|
<aside>
|
|
@@ -1555,6 +1555,91 @@
|
|
|
1555
1555
|
line-height: 1.5;
|
|
1556
1556
|
}
|
|
1557
1557
|
|
|
1558
|
+
.lattice-ref-admin .enterprise-grid {
|
|
1559
|
+
display: grid;
|
|
1560
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
1561
|
+
gap: 10px;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.lattice-ref-admin .enterprise-cap-card {
|
|
1565
|
+
display: flex;
|
|
1566
|
+
align-items: center;
|
|
1567
|
+
gap: 10px;
|
|
1568
|
+
min-width: 0;
|
|
1569
|
+
border: 1px solid rgba(111,66,232,0.12);
|
|
1570
|
+
border-radius: 8px;
|
|
1571
|
+
background: rgba(255,255,255,0.70);
|
|
1572
|
+
padding: 11px 12px;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
.lattice-ref-admin .enterprise-cap-card i {
|
|
1576
|
+
color: #7a74a0;
|
|
1577
|
+
font-size: 18px;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
.lattice-ref-admin .enterprise-cap-card.on i {
|
|
1581
|
+
color: #0d8f72;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.lattice-ref-admin .enterprise-cap-card span {
|
|
1585
|
+
flex: 1;
|
|
1586
|
+
min-width: 0;
|
|
1587
|
+
color: #14162c;
|
|
1588
|
+
font-size: 13px;
|
|
1589
|
+
font-weight: 800;
|
|
1590
|
+
overflow: hidden;
|
|
1591
|
+
text-overflow: ellipsis;
|
|
1592
|
+
white-space: nowrap;
|
|
1593
|
+
text-transform: capitalize;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.lattice-ref-admin .enterprise-cap-card strong {
|
|
1597
|
+
color: #4a4668;
|
|
1598
|
+
font-size: 11px;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.lattice-ref-admin .enterprise-kv {
|
|
1602
|
+
display: grid;
|
|
1603
|
+
gap: 8px;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
.lattice-ref-admin .enterprise-kv div {
|
|
1607
|
+
display: grid;
|
|
1608
|
+
grid-template-columns: 150px minmax(0, 1fr);
|
|
1609
|
+
gap: 10px;
|
|
1610
|
+
align-items: start;
|
|
1611
|
+
border: 1px solid rgba(111,66,232,0.10);
|
|
1612
|
+
border-radius: 8px;
|
|
1613
|
+
background: rgba(255,255,255,0.62);
|
|
1614
|
+
padding: 9px 10px;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.lattice-ref-admin .enterprise-kv span {
|
|
1618
|
+
color: #4a4668;
|
|
1619
|
+
font-size: 12px;
|
|
1620
|
+
font-weight: 800;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
.lattice-ref-admin .enterprise-kv strong {
|
|
1624
|
+
color: #14162c;
|
|
1625
|
+
font-size: 12px;
|
|
1626
|
+
line-height: 1.45;
|
|
1627
|
+
overflow-wrap: anywhere;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.lattice-ref-admin .enterprise-json {
|
|
1631
|
+
max-height: 280px;
|
|
1632
|
+
overflow: auto;
|
|
1633
|
+
margin: 12px 0 0;
|
|
1634
|
+
border: 1px solid rgba(111,66,232,0.10);
|
|
1635
|
+
border-radius: 8px;
|
|
1636
|
+
background: rgba(20,22,44,0.05);
|
|
1637
|
+
color: #14162c;
|
|
1638
|
+
padding: 12px;
|
|
1639
|
+
font-size: 12px;
|
|
1640
|
+
white-space: pre-wrap;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1558
1643
|
@media (max-width: 980px) {
|
|
1559
1644
|
.lattice-ref-chat .reference-card-grid,
|
|
1560
1645
|
.reference-lists {
|
|
@@ -2784,9 +2869,11 @@ body.lattice-ref-graph {
|
|
|
2784
2869
|
top: 16px;
|
|
2785
2870
|
right: 16px;
|
|
2786
2871
|
display: flex;
|
|
2872
|
+
flex-wrap: wrap;
|
|
2787
2873
|
gap: 8px;
|
|
2788
2874
|
padding: 8px;
|
|
2789
2875
|
border-radius: 10px;
|
|
2876
|
+
max-width: min(760px, calc(100% - 32px));
|
|
2790
2877
|
}
|
|
2791
2878
|
|
|
2792
2879
|
.tb-btn {
|
|
@@ -3301,6 +3388,103 @@ body.lattice-ref-graph {
|
|
|
3301
3388
|
|
|
3302
3389
|
.jump-btn:hover { filter: brightness(1.04); }
|
|
3303
3390
|
|
|
3391
|
+
.jump-btn.secondary {
|
|
3392
|
+
border: 1px solid rgba(111,66,232,0.20);
|
|
3393
|
+
color: var(--text);
|
|
3394
|
+
background: rgba(255,255,255,0.82);
|
|
3395
|
+
box-shadow: none;
|
|
3396
|
+
cursor: pointer;
|
|
3397
|
+
font: inherit;
|
|
3398
|
+
font-size: 12px;
|
|
3399
|
+
font-weight: 700;
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
.detail-actions {
|
|
3403
|
+
display: flex;
|
|
3404
|
+
flex-wrap: wrap;
|
|
3405
|
+
gap: 8px;
|
|
3406
|
+
margin-bottom: 14px;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
.related-node-list {
|
|
3410
|
+
display: grid;
|
|
3411
|
+
gap: 7px;
|
|
3412
|
+
margin-bottom: 14px;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
.related-node-btn {
|
|
3416
|
+
width: 100%;
|
|
3417
|
+
min-width: 0;
|
|
3418
|
+
display: grid;
|
|
3419
|
+
grid-template-columns: 18px minmax(0, 1fr) auto;
|
|
3420
|
+
align-items: center;
|
|
3421
|
+
gap: 8px;
|
|
3422
|
+
border: 1px solid rgba(111,66,232,0.13);
|
|
3423
|
+
border-radius: 8px;
|
|
3424
|
+
background: rgba(255,255,255,0.76);
|
|
3425
|
+
color: var(--text);
|
|
3426
|
+
padding: 8px 10px;
|
|
3427
|
+
text-align: left;
|
|
3428
|
+
cursor: pointer;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
.related-node-btn:hover {
|
|
3432
|
+
border-color: rgba(111,66,232,0.34);
|
|
3433
|
+
background: rgba(111,66,232,0.07);
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
.related-node-btn strong {
|
|
3437
|
+
min-width: 0;
|
|
3438
|
+
overflow: hidden;
|
|
3439
|
+
text-overflow: ellipsis;
|
|
3440
|
+
white-space: nowrap;
|
|
3441
|
+
font-size: 12px;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
.related-node-btn em {
|
|
3445
|
+
color: var(--faint);
|
|
3446
|
+
font-size: 11px;
|
|
3447
|
+
font-style: normal;
|
|
3448
|
+
white-space: nowrap;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
.focus-chip {
|
|
3452
|
+
position: absolute;
|
|
3453
|
+
z-index: 21;
|
|
3454
|
+
left: 16px;
|
|
3455
|
+
bottom: 16px;
|
|
3456
|
+
max-width: min(560px, calc(100% - 32px));
|
|
3457
|
+
display: flex;
|
|
3458
|
+
flex-wrap: wrap;
|
|
3459
|
+
gap: 8px;
|
|
3460
|
+
padding: 8px;
|
|
3461
|
+
border: 1px solid var(--line);
|
|
3462
|
+
border-radius: 10px;
|
|
3463
|
+
background: rgba(255,255,255,0.90);
|
|
3464
|
+
box-shadow: var(--shadow);
|
|
3465
|
+
backdrop-filter: blur(18px);
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
.focus-chip span {
|
|
3469
|
+
display: inline-flex;
|
|
3470
|
+
align-items: center;
|
|
3471
|
+
gap: 6px;
|
|
3472
|
+
border-radius: 999px;
|
|
3473
|
+
background: rgba(111,66,232,0.08);
|
|
3474
|
+
color: var(--text);
|
|
3475
|
+
padding: 5px 10px;
|
|
3476
|
+
font-size: 11px;
|
|
3477
|
+
font-weight: 800;
|
|
3478
|
+
max-width: 260px;
|
|
3479
|
+
overflow: hidden;
|
|
3480
|
+
text-overflow: ellipsis;
|
|
3481
|
+
white-space: nowrap;
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
.search-shell.search-open .search-results {
|
|
3485
|
+
display: block;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3304
3488
|
.empty-hint {
|
|
3305
3489
|
margin: 0;
|
|
3306
3490
|
color: var(--muted);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* Lattice AI v2.0 — shared styling for the Agentic Workspace Platform pages
|
|
2
|
+
(Plugin SDK, Workflow Designer, Multi-Agent Runtime, Realtime Activity). */
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #0f1115;
|
|
5
|
+
--panel: #16191f;
|
|
6
|
+
--panel-2: #1c2027;
|
|
7
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
8
|
+
--text: #e7ecf3;
|
|
9
|
+
--muted: #94a3b8;
|
|
10
|
+
--accent: #378ADD;
|
|
11
|
+
--accent-2: #5ea7ec;
|
|
12
|
+
--ok: #34d399;
|
|
13
|
+
--warn: #fbbf24;
|
|
14
|
+
--err: #f87171;
|
|
15
|
+
}
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
background: var(--bg);
|
|
20
|
+
color: var(--text);
|
|
21
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
}
|
|
24
|
+
a { color: var(--accent-2); text-decoration: none; }
|
|
25
|
+
header.app {
|
|
26
|
+
display: flex; align-items: center; gap: 18px;
|
|
27
|
+
padding: 14px 24px; border-bottom: 1px solid var(--border);
|
|
28
|
+
background: rgba(22, 25, 31, 0.8); position: sticky; top: 0; backdrop-filter: blur(8px); z-index: 5;
|
|
29
|
+
}
|
|
30
|
+
header.app .brand { font-weight: 700; font-size: 16px; color: #fff; letter-spacing: .3px; }
|
|
31
|
+
header.app .brand small { color: var(--accent); font-weight: 600; margin-left: 6px; }
|
|
32
|
+
header.app nav { display: flex; gap: 14px; flex-wrap: wrap; }
|
|
33
|
+
header.app nav a { color: var(--muted); font-size: 13px; padding: 4px 6px; border-radius: 6px; }
|
|
34
|
+
header.app nav a:hover, header.app nav a.active { color: #fff; background: var(--panel-2); }
|
|
35
|
+
main { max-width: 1080px; margin: 0 auto; padding: 28px 24px 80px; }
|
|
36
|
+
h1 { font-size: 22px; margin: 0 0 4px; }
|
|
37
|
+
.sub { color: var(--muted); font-size: 13px; margin: 0 0 24px; }
|
|
38
|
+
.grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
|
|
39
|
+
.card {
|
|
40
|
+
background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
|
|
41
|
+
padding: 16px 18px;
|
|
42
|
+
}
|
|
43
|
+
.card h3 { margin: 0 0 6px; font-size: 15px; }
|
|
44
|
+
.card .meta { color: var(--muted); font-size: 12px; }
|
|
45
|
+
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
46
|
+
.spacer { flex: 1; }
|
|
47
|
+
.badge {
|
|
48
|
+
display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 999px;
|
|
49
|
+
background: var(--panel-2); color: var(--muted); border: 1px solid var(--border);
|
|
50
|
+
}
|
|
51
|
+
.badge.ok { color: var(--ok); border-color: rgba(52,211,153,.4); }
|
|
52
|
+
.badge.warn { color: var(--warn); border-color: rgba(251,191,36,.4); }
|
|
53
|
+
.badge.err { color: var(--err); border-color: rgba(248,113,113,.4); }
|
|
54
|
+
button, .btn {
|
|
55
|
+
background: var(--accent); color: #fff; border: none; border-radius: 8px;
|
|
56
|
+
padding: 7px 14px; font-size: 13px; cursor: pointer; font-weight: 600;
|
|
57
|
+
}
|
|
58
|
+
button.ghost { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }
|
|
59
|
+
button:hover { filter: brightness(1.08); }
|
|
60
|
+
button:disabled { opacity: .5; cursor: not-allowed; }
|
|
61
|
+
textarea, input, select {
|
|
62
|
+
width: 100%; background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
|
|
63
|
+
border-radius: 8px; padding: 9px 11px; font-size: 13px; font-family: inherit;
|
|
64
|
+
}
|
|
65
|
+
textarea { min-height: 90px; resize: vertical; }
|
|
66
|
+
label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 4px; }
|
|
67
|
+
pre {
|
|
68
|
+
background: #0b0d11; border: 1px solid var(--border); border-radius: 10px;
|
|
69
|
+
padding: 12px; overflow: auto; font-size: 12px; color: #cbd5e1; max-height: 360px;
|
|
70
|
+
}
|
|
71
|
+
.empty { color: var(--muted); text-align: center; padding: 50px 0; }
|
|
72
|
+
.section { margin-top: 28px; }
|
|
73
|
+
.timeline-item { border-left: 2px solid var(--border); padding: 6px 0 6px 14px; margin-left: 6px; font-size: 13px; }
|
|
74
|
+
.timeline-item .t-meta { color: var(--muted); font-size: 11px; }
|
|
75
|
+
.toast { position: fixed; bottom: 20px; right: 20px; background: var(--panel-2); border: 1px solid var(--border); padding: 12px 16px; border-radius: 10px; font-size: 13px; max-width: 360px; }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Plugin SDK — Lattice AI</title>
|
|
7
|
+
<link rel="stylesheet" href="/static/platform.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Plugin SDK</h1>
|
|
12
|
+
<p class="sub" id="sub">Versioned, permissioned plugins that extend skills, tools, and workflows.</p>
|
|
13
|
+
<div id="list" class="grid"><div class="empty">Loading plugins…</div></div>
|
|
14
|
+
|
|
15
|
+
<div class="section">
|
|
16
|
+
<h3>Validate a manifest</h3>
|
|
17
|
+
<p class="sub">Paste a <code>plugin.json</code> to check it against the SDK schema and permission allow-list.</p>
|
|
18
|
+
<textarea id="manifest" spellcheck="false">{
|
|
19
|
+
"id": "my-plugin",
|
|
20
|
+
"name": "My Plugin",
|
|
21
|
+
"version": "1.0.0",
|
|
22
|
+
"lattice_version": ">=2.0.0",
|
|
23
|
+
"permissions": ["read_workspace"],
|
|
24
|
+
"provides": { "skills": [] }
|
|
25
|
+
}</textarea>
|
|
26
|
+
<div class="row" style="margin-top:10px"><button id="validate">Validate</button></div>
|
|
27
|
+
<pre id="validateOut" style="display:none"></pre>
|
|
28
|
+
</div>
|
|
29
|
+
</main>
|
|
30
|
+
|
|
31
|
+
<script type="module">
|
|
32
|
+
import { mountHeader, api, escapeHtml, badge, toast } from "/static/scripts/platform.js";
|
|
33
|
+
mountHeader("/plugins/sdk");
|
|
34
|
+
|
|
35
|
+
async function load() {
|
|
36
|
+
const data = await api("/plugins/registry");
|
|
37
|
+
document.getElementById("sub").textContent =
|
|
38
|
+
`SDK v${data.sdk_version} · ${data.total} plugin(s) discovered in ${data.plugins_dir}`;
|
|
39
|
+
const list = document.getElementById("list");
|
|
40
|
+
if (!data.plugins.length) { list.innerHTML = `<div class="empty">No plugins found.</div>`; return; }
|
|
41
|
+
list.innerHTML = data.plugins.map((p) => `
|
|
42
|
+
<div class="card">
|
|
43
|
+
<div class="row"><h3>${escapeHtml(p.name)}</h3><div class="spacer"></div>
|
|
44
|
+
${badge(p.installed ? (p.enabled ? "ready" : "available") : "available")}</div>
|
|
45
|
+
<div class="meta">v${escapeHtml(p.version)} · ${escapeHtml(p.author || "unknown")} · ${p.compatible ? "compatible" : "<span class='badge err'>incompatible</span>"}</div>
|
|
46
|
+
<p style="font-size:13px;color:#cbd5e1">${escapeHtml(p.description)}</p>
|
|
47
|
+
<div class="meta">Permissions: ${(p.permissions||[]).map(x=>`<span class="badge">${escapeHtml(x)}</span>`).join(" ") || "none"}</div>
|
|
48
|
+
<div class="meta" style="margin-top:6px">Provides: ${Object.entries(p.provides||{}).map(([k,v])=>`${k}(${(v||[]).length})`).join(", ") || "—"}</div>
|
|
49
|
+
<div class="row" style="margin-top:12px">
|
|
50
|
+
${p.installed
|
|
51
|
+
? `<button class="ghost" data-act="${p.enabled?"disable":"enable"}" data-id="${p.id}">${p.enabled?"Disable":"Enable"}</button>
|
|
52
|
+
<button class="ghost" data-act="uninstall" data-id="${p.id}">Uninstall</button>`
|
|
53
|
+
: `<button data-act="install" data-id="${p.id}" ${p.compatible?"":"disabled"}>Install</button>`}
|
|
54
|
+
</div>
|
|
55
|
+
</div>`).join("");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
document.getElementById("list").addEventListener("click", async (e) => {
|
|
59
|
+
const btn = e.target.closest("button[data-act]");
|
|
60
|
+
if (!btn) return;
|
|
61
|
+
btn.disabled = true;
|
|
62
|
+
try {
|
|
63
|
+
await api(`/plugins/${btn.dataset.act}`, { method: "POST", body: JSON.stringify({ plugin_id: btn.dataset.id }) });
|
|
64
|
+
toast(`${btn.dataset.act}: ${btn.dataset.id}`);
|
|
65
|
+
await load();
|
|
66
|
+
} catch (err) { toast(err.message); btn.disabled = false; }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
document.getElementById("validate").addEventListener("click", async () => {
|
|
70
|
+
const out = document.getElementById("validateOut");
|
|
71
|
+
out.style.display = "block";
|
|
72
|
+
try {
|
|
73
|
+
const manifest = JSON.parse(document.getElementById("manifest").value);
|
|
74
|
+
const res = await api("/plugins/validate", { method: "POST", body: JSON.stringify({ manifest }) });
|
|
75
|
+
out.textContent = JSON.stringify(res, null, 2);
|
|
76
|
+
} catch (err) { out.textContent = "Error: " + err.message; }
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
load().catch((e) => toast(e.message));
|
|
80
|
+
</script>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|