ltcai 3.1.0 → 3.3.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 +35 -8
- package/docs/CHANGELOG.md +53 -0
- package/docs/V3_2_AUDIT.md +82 -0
- package/docs/V3_FRONTEND.md +1 -1
- package/docs/architecture.md +6 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_registry.py +103 -0
- package/latticeai/api/hooks.py +113 -0
- package/latticeai/api/marketplace.py +13 -0
- package/latticeai/api/memory.py +109 -0
- package/latticeai/core/agent_registry.py +234 -0
- package/latticeai/core/hooks.py +284 -0
- package/latticeai/core/marketplace.py +87 -2
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +41 -0
- package/latticeai/services/memory_service.py +324 -0
- package/package.json +2 -2
- package/scripts/build_v3_assets.mjs +7 -1
- package/static/css/{tokens.5a595671.css → tokens.8b8e31bd.css} +1 -1
- package/static/css/tokens.css +1 -1
- package/static/v3/asset-manifest.json +22 -14
- package/static/v3/css/{lattice.views.3ee19d4e.css → lattice.views.1d326beb.css} +5 -0
- package/static/v3/css/lattice.views.css +5 -0
- package/static/v3/js/{app.46fb61d9.js → app.cf5bb712.js} +1 -1
- package/static/v3/js/core/{api.22a41d42.js → api.113660c5.js} +123 -4
- package/static/v3/js/core/api.js +123 -4
- package/static/v3/js/core/{routes.f935dd50.js → routes.07ad6696.js} +11 -0
- package/static/v3/js/core/routes.js +11 -0
- package/static/v3/js/core/{shell.1b6199d6.js → shell.9e707234.js} +2 -2
- package/static/v3/js/views/{agents.14e48bdd.js → agents.c373d48c.js} +100 -0
- package/static/v3/js/views/agents.js +100 -0
- package/static/v3/js/views/{chat.718144ce.js → chat.c48fd9e2.js} +1 -1
- package/static/v3/js/views/chat.js +1 -1
- package/static/v3/js/views/{files.4935197e.js → files.8464634a.js} +64 -18
- package/static/v3/js/views/files.js +64 -18
- package/static/v3/js/views/hooks.f3edebca.js +99 -0
- package/static/v3/js/views/hooks.js +99 -0
- package/static/v3/js/views/marketplace.ab0583d4.js +141 -0
- package/static/v3/js/views/marketplace.js +141 -0
- package/static/v3/js/views/mcp.99b5c6a7.js +114 -0
- package/static/v3/js/views/mcp.js +114 -0
- package/static/v3/js/views/memory.4ebdf474.js +147 -0
- package/static/v3/js/views/memory.js +147 -0
- package/static/v3/js/views/planning.9ac3e313.js +153 -0
- package/static/v3/js/views/planning.js +153 -0
- package/static/v3/js/views/{settings.4f777210.js → settings.c7b0cc05.js} +16 -2
- package/static/v3/js/views/settings.js +16 -2
- package/static/v3/js/views/skills.c6c2f965.js +109 -0
- package/static/v3/js/views/skills.js +109 -0
- package/static/v3/js/views/tools.e4f11276.js +108 -0
- package/static/v3/js/views/tools.js +108 -0
- package/static/v3/js/views/workflows.26c57290.js +128 -0
- package/static/v3/js/views/workflows.js +128 -0
|
@@ -82,9 +82,35 @@ export const api = {
|
|
|
82
82
|
|
|
83
83
|
/* ── Documented future surfaces ─────────────────────────────────────── */
|
|
84
84
|
|
|
85
|
-
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
+
* The backend endpoint is vector-centric (status/storage/source_items/…); the
|
|
87
|
+
* home pillars + topbar chip want a `pipelines` view keyed by
|
|
88
|
+
* knowledge_graph / vector_index / hybrid. Synthesize that shape from the real
|
|
89
|
+
* index status (vectors) plus the KG stats endpoint (entities). Nothing is
|
|
90
|
+
* fabricated: if the index endpoint is unavailable we report unavailable (so
|
|
91
|
+
* the UI shows the honest empty state), and a missing graph-stats count yields
|
|
92
|
+
* an "unavailable" graph pillar rather than a fake number. */
|
|
93
|
+
async indexStatus() {
|
|
94
|
+
const res = await raw("/api/index/status");
|
|
95
|
+
if (!(res.ok && res.data && !res.data.raw)) {
|
|
96
|
+
return { ok: false, status: res.status, data: EMPTY_INDEX_STATUS, source: "unavailable", error: res.error };
|
|
97
|
+
}
|
|
98
|
+
const idx = res.data;
|
|
99
|
+
let entities = null;
|
|
100
|
+
const gs = await raw("/knowledge-graph/stats");
|
|
101
|
+
if (gs.ok && gs.data && !gs.data.raw) {
|
|
102
|
+
const g = gs.data;
|
|
103
|
+
const n = g.total_nodes ?? g.nodes_total ?? (g.nodes && (g.nodes.total ?? g.nodes.count));
|
|
104
|
+
if (n !== undefined && n !== null) entities = Number(n) || 0;
|
|
105
|
+
}
|
|
106
|
+
const vectors = Number(idx.indexed_items ?? idx.ready_items) || 0;
|
|
107
|
+
const vstate = idx.status === "ready" ? "ready" : "pending";
|
|
108
|
+
const pipelines = {
|
|
109
|
+
knowledge_graph: { state: entities === null ? "unavailable" : "ready", entities: entities ?? 0 },
|
|
110
|
+
vector_index: { state: vstate, vectors },
|
|
111
|
+
hybrid: { state: vstate, strategy: vstate === "ready" ? "fused" : "pending" },
|
|
112
|
+
};
|
|
113
|
+
return { ok: true, status: res.status, data: { ...idx, pipelines }, source: "live" };
|
|
88
114
|
},
|
|
89
115
|
|
|
90
116
|
/** POST /api/index/rebuild — rebuild the derived vector index (real run). */
|
|
@@ -176,6 +202,31 @@ export const api = {
|
|
|
176
202
|
},
|
|
177
203
|
sysinfo() { return withFallback("/local/sysinfo", {}, EMPTY_SYSINFO); },
|
|
178
204
|
|
|
205
|
+
/** POST /upload/document — manual document ingest (multipart/form-data).
|
|
206
|
+
* Real backend path: parse → chunk → embed → knowledge-graph ingest
|
|
207
|
+
* (latticeai/api/tools.py:/upload/document). Returns { ok, status, data,
|
|
208
|
+
* source }; never throws. FormData must NOT carry a JSON Content-Type — the
|
|
209
|
+
* browser sets the multipart boundary itself. */
|
|
210
|
+
async uploadDocument(file) {
|
|
211
|
+
const ws = store.get().workspaceId;
|
|
212
|
+
const form = new FormData();
|
|
213
|
+
form.append("file", file);
|
|
214
|
+
try {
|
|
215
|
+
const res = await fetch("/upload/document", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
credentials: "same-origin",
|
|
218
|
+
headers: { "Accept": "application/json", ...(ws ? { "X-Workspace-Id": ws } : {}) },
|
|
219
|
+
body: form,
|
|
220
|
+
});
|
|
221
|
+
let data = null;
|
|
222
|
+
const text = await res.text();
|
|
223
|
+
if (text) { try { data = JSON.parse(text); } catch { data = { raw: text }; } }
|
|
224
|
+
return { ok: res.ok, status: res.status, data, source: res.ok ? "live" : "unavailable" };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return { ok: false, status: 0, data: null, source: "unavailable", error: String(err) };
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
179
230
|
adminSummary() { return withFallback("/admin/summary", {}, EMPTY_ADMIN.summary); },
|
|
180
231
|
adminUsers() { return withFallback("/admin/users", {}, EMPTY_ADMIN.users); },
|
|
181
232
|
adminAudit() { return withFallback("/admin/audit", {}, EMPTY_ADMIN.audit); },
|
|
@@ -310,7 +361,11 @@ export const api = {
|
|
|
310
361
|
const rawData = line.slice(5).trim();
|
|
311
362
|
if (rawData === "[DONE]") return { source: "live", text, trace, model };
|
|
312
363
|
let data; try { data = JSON.parse(rawData); } catch { continue; }
|
|
313
|
-
|
|
364
|
+
// Standard chat streams `chunk`; the document-generation path streams
|
|
365
|
+
// `text` (report body + footnotes). Accept both so doc requests render
|
|
366
|
+
// instead of falsely reporting the backend as unreachable.
|
|
367
|
+
const delta = data.chunk || data.text;
|
|
368
|
+
if (delta) { text += delta; onChunk && onChunk(delta, text); }
|
|
314
369
|
if (data.model) model = data.model;
|
|
315
370
|
if (data.trace) { trace = data.trace; onTrace && onTrace(trace); }
|
|
316
371
|
}
|
|
@@ -321,6 +376,70 @@ export const api = {
|
|
|
321
376
|
}
|
|
322
377
|
return { source: "live", text, trace, model };
|
|
323
378
|
},
|
|
379
|
+
|
|
380
|
+
/* ── v3.2 platform surfaces (all fallback-safe; never fabricate) ─────── */
|
|
381
|
+
|
|
382
|
+
// Agent Registry (Part 2)
|
|
383
|
+
agentRegistry(type) { return withFallback(`/agents/api/registry${type ? "?type=" + encodeURIComponent(type) : ""}`, {}, { agents: [], counts: {}, types: [] }); },
|
|
384
|
+
agentCapabilities() { return withFallback("/agents/api/registry/capabilities", {}, { capabilities: {} }); },
|
|
385
|
+
registerAgent(body) { return raw("/agents/api/registry", { method: "POST", body }); },
|
|
386
|
+
updateAgent(id, body) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "PATCH", body }); },
|
|
387
|
+
removeAgent(id) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "DELETE" }); },
|
|
388
|
+
agentRunDetail(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}`); },
|
|
389
|
+
agentRunReplay(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/replay`); },
|
|
390
|
+
stopAgentRun(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/stop`, { method: "POST" }); },
|
|
391
|
+
|
|
392
|
+
// Marketplace + Templates (Parts 3, 4)
|
|
393
|
+
templates(kind) { return withFallback(`/marketplace/templates${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { templates: [], kinds: [] }); },
|
|
394
|
+
templateRegistry() { return withFallback("/marketplace/templates/registry", {}, { registry: [] }); },
|
|
395
|
+
exportTemplate(kind, id) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/export`); },
|
|
396
|
+
importTemplate(data) { return raw("/marketplace/templates/import", { method: "POST", body: { data } }); },
|
|
397
|
+
installTemplate(data) { return raw("/marketplace/templates/install", { method: "POST", body: { data } }); },
|
|
398
|
+
cloneTemplate(kind, id, name) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/clone`, { method: "POST", body: { name } }); },
|
|
399
|
+
pluginsRegistry() { return withFallback("/plugins/registry", {}, { plugins: [] }); },
|
|
400
|
+
pluginsDirectory() { return withFallback("/plugins/directory", {}, { plugins: [], categories: [] }); },
|
|
401
|
+
|
|
402
|
+
// Workflow Agents (Part 5)
|
|
403
|
+
workflowDefinitions() { return withFallback("/workflows/api/definitions", {}, { workflows: [] }); },
|
|
404
|
+
createWorkflow(body) { return raw("/workflows/api/definitions", { method: "POST", body }); },
|
|
405
|
+
runWorkflow(id, body = {}) { return raw(`/workflows/api/definitions/${encodeURIComponent(id)}/run`, { method: "POST", body }); },
|
|
406
|
+
workflowRuns() { return withFallback("/workflows/api/runs", {}, { runs: [] }); },
|
|
407
|
+
workflowReplay(runId) { return raw(`/workflows/api/runs/${encodeURIComponent(runId)}/replay`); },
|
|
408
|
+
|
|
409
|
+
// Long-Term Memory + Memory Manager (Parts 7, 8)
|
|
410
|
+
memoryManager() { return withFallback("/api/memory/manager", {}, { sources: [], tiers: [], usage: {} }); },
|
|
411
|
+
memoryTiers() { return withFallback("/api/memory/tiers", {}, { tiers: [], workspace_kinds: [] }); },
|
|
412
|
+
memoryInspect(source, limit = 50) { return withFallback(`/api/memory/inspect?source=${encodeURIComponent(source)}&limit=${limit}`, {}, { items: [] }); },
|
|
413
|
+
memoryRecall(query, limit = 20) { return raw("/api/memory/recall", { method: "POST", body: { query, limit } }); },
|
|
414
|
+
memoryPrune(body) { return raw("/api/memory/prune", { method: "POST", body }); },
|
|
415
|
+
memoryCompact() { return raw("/api/memory/compact", { method: "POST", body: {} }); },
|
|
416
|
+
memoryRebuild(target = "vector") { return raw("/api/memory/rebuild", { method: "POST", body: { target } }); },
|
|
417
|
+
memoryClear(scope, confirm = true) { return raw("/api/memory/clear", { method: "POST", body: { scope, confirm } }); },
|
|
418
|
+
workspaceMemories(kind) { return withFallback(`/workspace/memories${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { memories: [] }); },
|
|
419
|
+
|
|
420
|
+
// Skills Registry (Part 9)
|
|
421
|
+
skills() { return withFallback("/workspace/skills", {}, { skills: [] }); },
|
|
422
|
+
skillEnable(skill) { return raw("/workspace/skills/enable", { method: "POST", body: { skill } }); },
|
|
423
|
+
skillDisable(skill) { return raw("/workspace/skills/disable", { method: "POST", body: { skill } }); },
|
|
424
|
+
skillInstall(skill, plugin) { return raw("/workspace/skills/install", { method: "POST", body: { skill, plugin: plugin || "" } }); },
|
|
425
|
+
skillUninstall(skill) { return raw("/workspace/skills/uninstall", { method: "POST", body: { skill } }); },
|
|
426
|
+
skillsMarketplace() { return withFallback("/skills/marketplace", {}, { skills: [], categories: [] }); },
|
|
427
|
+
|
|
428
|
+
// Hooks Registry (Part 10)
|
|
429
|
+
hooks(kind) { return withFallback(`/api/hooks${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { hooks: [], kinds: [], counts: {} }); },
|
|
430
|
+
hookEnable(hook_id, enabled = true) { return raw("/api/hooks/enable", { method: "POST", body: { hook_id, enabled } }); },
|
|
431
|
+
hookDisable(hook_id) { return raw("/api/hooks/disable", { method: "POST", body: { hook_id, enabled: false } }); },
|
|
432
|
+
hookReorder(kind, ordered_ids) { return raw("/api/hooks/reorder", { method: "POST", body: { kind, ordered_ids } }); },
|
|
433
|
+
hookRegister(body) { return raw("/api/hooks/register", { method: "POST", body }); },
|
|
434
|
+
hookRemove(hook_id) { return raw(`/api/hooks/${encodeURIComponent(hook_id)}`, { method: "DELETE" }); },
|
|
435
|
+
|
|
436
|
+
// Tool Registry + MCP (Parts 11, 12)
|
|
437
|
+
toolPermissions() { return withFallback("/tools/permissions", {}, { permissions: [] }); },
|
|
438
|
+
mcpTools() { return withFallback("/mcp/tools", {}, { tools: [], installed_mcps: [] }); },
|
|
439
|
+
mcpInstalled() { return withFallback("/mcp/installed", {}, { installed: [] }); },
|
|
440
|
+
mcpClaudeServers() { return withFallback("/mcp/claude-code-servers", {}, { servers: [] }); },
|
|
441
|
+
mcpCustom() { return withFallback("/mcp/custom", {}, { custom: [] }); },
|
|
442
|
+
mcpRecommend(query, limit = 6) { return raw("/mcp/recommend", { method: "POST", body: { query, limit } }); },
|
|
324
443
|
};
|
|
325
444
|
|
|
326
445
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
package/static/v3/js/core/api.js
CHANGED
|
@@ -82,9 +82,35 @@ export const api = {
|
|
|
82
82
|
|
|
83
83
|
/* ── Documented future surfaces ─────────────────────────────────────── */
|
|
84
84
|
|
|
85
|
-
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
+
* The backend endpoint is vector-centric (status/storage/source_items/…); the
|
|
87
|
+
* home pillars + topbar chip want a `pipelines` view keyed by
|
|
88
|
+
* knowledge_graph / vector_index / hybrid. Synthesize that shape from the real
|
|
89
|
+
* index status (vectors) plus the KG stats endpoint (entities). Nothing is
|
|
90
|
+
* fabricated: if the index endpoint is unavailable we report unavailable (so
|
|
91
|
+
* the UI shows the honest empty state), and a missing graph-stats count yields
|
|
92
|
+
* an "unavailable" graph pillar rather than a fake number. */
|
|
93
|
+
async indexStatus() {
|
|
94
|
+
const res = await raw("/api/index/status");
|
|
95
|
+
if (!(res.ok && res.data && !res.data.raw)) {
|
|
96
|
+
return { ok: false, status: res.status, data: EMPTY_INDEX_STATUS, source: "unavailable", error: res.error };
|
|
97
|
+
}
|
|
98
|
+
const idx = res.data;
|
|
99
|
+
let entities = null;
|
|
100
|
+
const gs = await raw("/knowledge-graph/stats");
|
|
101
|
+
if (gs.ok && gs.data && !gs.data.raw) {
|
|
102
|
+
const g = gs.data;
|
|
103
|
+
const n = g.total_nodes ?? g.nodes_total ?? (g.nodes && (g.nodes.total ?? g.nodes.count));
|
|
104
|
+
if (n !== undefined && n !== null) entities = Number(n) || 0;
|
|
105
|
+
}
|
|
106
|
+
const vectors = Number(idx.indexed_items ?? idx.ready_items) || 0;
|
|
107
|
+
const vstate = idx.status === "ready" ? "ready" : "pending";
|
|
108
|
+
const pipelines = {
|
|
109
|
+
knowledge_graph: { state: entities === null ? "unavailable" : "ready", entities: entities ?? 0 },
|
|
110
|
+
vector_index: { state: vstate, vectors },
|
|
111
|
+
hybrid: { state: vstate, strategy: vstate === "ready" ? "fused" : "pending" },
|
|
112
|
+
};
|
|
113
|
+
return { ok: true, status: res.status, data: { ...idx, pipelines }, source: "live" };
|
|
88
114
|
},
|
|
89
115
|
|
|
90
116
|
/** POST /api/index/rebuild — rebuild the derived vector index (real run). */
|
|
@@ -176,6 +202,31 @@ export const api = {
|
|
|
176
202
|
},
|
|
177
203
|
sysinfo() { return withFallback("/local/sysinfo", {}, EMPTY_SYSINFO); },
|
|
178
204
|
|
|
205
|
+
/** POST /upload/document — manual document ingest (multipart/form-data).
|
|
206
|
+
* Real backend path: parse → chunk → embed → knowledge-graph ingest
|
|
207
|
+
* (latticeai/api/tools.py:/upload/document). Returns { ok, status, data,
|
|
208
|
+
* source }; never throws. FormData must NOT carry a JSON Content-Type — the
|
|
209
|
+
* browser sets the multipart boundary itself. */
|
|
210
|
+
async uploadDocument(file) {
|
|
211
|
+
const ws = store.get().workspaceId;
|
|
212
|
+
const form = new FormData();
|
|
213
|
+
form.append("file", file);
|
|
214
|
+
try {
|
|
215
|
+
const res = await fetch("/upload/document", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
credentials: "same-origin",
|
|
218
|
+
headers: { "Accept": "application/json", ...(ws ? { "X-Workspace-Id": ws } : {}) },
|
|
219
|
+
body: form,
|
|
220
|
+
});
|
|
221
|
+
let data = null;
|
|
222
|
+
const text = await res.text();
|
|
223
|
+
if (text) { try { data = JSON.parse(text); } catch { data = { raw: text }; } }
|
|
224
|
+
return { ok: res.ok, status: res.status, data, source: res.ok ? "live" : "unavailable" };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return { ok: false, status: 0, data: null, source: "unavailable", error: String(err) };
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
179
230
|
adminSummary() { return withFallback("/admin/summary", {}, EMPTY_ADMIN.summary); },
|
|
180
231
|
adminUsers() { return withFallback("/admin/users", {}, EMPTY_ADMIN.users); },
|
|
181
232
|
adminAudit() { return withFallback("/admin/audit", {}, EMPTY_ADMIN.audit); },
|
|
@@ -310,7 +361,11 @@ export const api = {
|
|
|
310
361
|
const rawData = line.slice(5).trim();
|
|
311
362
|
if (rawData === "[DONE]") return { source: "live", text, trace, model };
|
|
312
363
|
let data; try { data = JSON.parse(rawData); } catch { continue; }
|
|
313
|
-
|
|
364
|
+
// Standard chat streams `chunk`; the document-generation path streams
|
|
365
|
+
// `text` (report body + footnotes). Accept both so doc requests render
|
|
366
|
+
// instead of falsely reporting the backend as unreachable.
|
|
367
|
+
const delta = data.chunk || data.text;
|
|
368
|
+
if (delta) { text += delta; onChunk && onChunk(delta, text); }
|
|
314
369
|
if (data.model) model = data.model;
|
|
315
370
|
if (data.trace) { trace = data.trace; onTrace && onTrace(trace); }
|
|
316
371
|
}
|
|
@@ -321,6 +376,70 @@ export const api = {
|
|
|
321
376
|
}
|
|
322
377
|
return { source: "live", text, trace, model };
|
|
323
378
|
},
|
|
379
|
+
|
|
380
|
+
/* ── v3.2 platform surfaces (all fallback-safe; never fabricate) ─────── */
|
|
381
|
+
|
|
382
|
+
// Agent Registry (Part 2)
|
|
383
|
+
agentRegistry(type) { return withFallback(`/agents/api/registry${type ? "?type=" + encodeURIComponent(type) : ""}`, {}, { agents: [], counts: {}, types: [] }); },
|
|
384
|
+
agentCapabilities() { return withFallback("/agents/api/registry/capabilities", {}, { capabilities: {} }); },
|
|
385
|
+
registerAgent(body) { return raw("/agents/api/registry", { method: "POST", body }); },
|
|
386
|
+
updateAgent(id, body) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "PATCH", body }); },
|
|
387
|
+
removeAgent(id) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "DELETE" }); },
|
|
388
|
+
agentRunDetail(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}`); },
|
|
389
|
+
agentRunReplay(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/replay`); },
|
|
390
|
+
stopAgentRun(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/stop`, { method: "POST" }); },
|
|
391
|
+
|
|
392
|
+
// Marketplace + Templates (Parts 3, 4)
|
|
393
|
+
templates(kind) { return withFallback(`/marketplace/templates${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { templates: [], kinds: [] }); },
|
|
394
|
+
templateRegistry() { return withFallback("/marketplace/templates/registry", {}, { registry: [] }); },
|
|
395
|
+
exportTemplate(kind, id) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/export`); },
|
|
396
|
+
importTemplate(data) { return raw("/marketplace/templates/import", { method: "POST", body: { data } }); },
|
|
397
|
+
installTemplate(data) { return raw("/marketplace/templates/install", { method: "POST", body: { data } }); },
|
|
398
|
+
cloneTemplate(kind, id, name) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/clone`, { method: "POST", body: { name } }); },
|
|
399
|
+
pluginsRegistry() { return withFallback("/plugins/registry", {}, { plugins: [] }); },
|
|
400
|
+
pluginsDirectory() { return withFallback("/plugins/directory", {}, { plugins: [], categories: [] }); },
|
|
401
|
+
|
|
402
|
+
// Workflow Agents (Part 5)
|
|
403
|
+
workflowDefinitions() { return withFallback("/workflows/api/definitions", {}, { workflows: [] }); },
|
|
404
|
+
createWorkflow(body) { return raw("/workflows/api/definitions", { method: "POST", body }); },
|
|
405
|
+
runWorkflow(id, body = {}) { return raw(`/workflows/api/definitions/${encodeURIComponent(id)}/run`, { method: "POST", body }); },
|
|
406
|
+
workflowRuns() { return withFallback("/workflows/api/runs", {}, { runs: [] }); },
|
|
407
|
+
workflowReplay(runId) { return raw(`/workflows/api/runs/${encodeURIComponent(runId)}/replay`); },
|
|
408
|
+
|
|
409
|
+
// Long-Term Memory + Memory Manager (Parts 7, 8)
|
|
410
|
+
memoryManager() { return withFallback("/api/memory/manager", {}, { sources: [], tiers: [], usage: {} }); },
|
|
411
|
+
memoryTiers() { return withFallback("/api/memory/tiers", {}, { tiers: [], workspace_kinds: [] }); },
|
|
412
|
+
memoryInspect(source, limit = 50) { return withFallback(`/api/memory/inspect?source=${encodeURIComponent(source)}&limit=${limit}`, {}, { items: [] }); },
|
|
413
|
+
memoryRecall(query, limit = 20) { return raw("/api/memory/recall", { method: "POST", body: { query, limit } }); },
|
|
414
|
+
memoryPrune(body) { return raw("/api/memory/prune", { method: "POST", body }); },
|
|
415
|
+
memoryCompact() { return raw("/api/memory/compact", { method: "POST", body: {} }); },
|
|
416
|
+
memoryRebuild(target = "vector") { return raw("/api/memory/rebuild", { method: "POST", body: { target } }); },
|
|
417
|
+
memoryClear(scope, confirm = true) { return raw("/api/memory/clear", { method: "POST", body: { scope, confirm } }); },
|
|
418
|
+
workspaceMemories(kind) { return withFallback(`/workspace/memories${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { memories: [] }); },
|
|
419
|
+
|
|
420
|
+
// Skills Registry (Part 9)
|
|
421
|
+
skills() { return withFallback("/workspace/skills", {}, { skills: [] }); },
|
|
422
|
+
skillEnable(skill) { return raw("/workspace/skills/enable", { method: "POST", body: { skill } }); },
|
|
423
|
+
skillDisable(skill) { return raw("/workspace/skills/disable", { method: "POST", body: { skill } }); },
|
|
424
|
+
skillInstall(skill, plugin) { return raw("/workspace/skills/install", { method: "POST", body: { skill, plugin: plugin || "" } }); },
|
|
425
|
+
skillUninstall(skill) { return raw("/workspace/skills/uninstall", { method: "POST", body: { skill } }); },
|
|
426
|
+
skillsMarketplace() { return withFallback("/skills/marketplace", {}, { skills: [], categories: [] }); },
|
|
427
|
+
|
|
428
|
+
// Hooks Registry (Part 10)
|
|
429
|
+
hooks(kind) { return withFallback(`/api/hooks${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { hooks: [], kinds: [], counts: {} }); },
|
|
430
|
+
hookEnable(hook_id, enabled = true) { return raw("/api/hooks/enable", { method: "POST", body: { hook_id, enabled } }); },
|
|
431
|
+
hookDisable(hook_id) { return raw("/api/hooks/disable", { method: "POST", body: { hook_id, enabled: false } }); },
|
|
432
|
+
hookReorder(kind, ordered_ids) { return raw("/api/hooks/reorder", { method: "POST", body: { kind, ordered_ids } }); },
|
|
433
|
+
hookRegister(body) { return raw("/api/hooks/register", { method: "POST", body }); },
|
|
434
|
+
hookRemove(hook_id) { return raw(`/api/hooks/${encodeURIComponent(hook_id)}`, { method: "DELETE" }); },
|
|
435
|
+
|
|
436
|
+
// Tool Registry + MCP (Parts 11, 12)
|
|
437
|
+
toolPermissions() { return withFallback("/tools/permissions", {}, { permissions: [] }); },
|
|
438
|
+
mcpTools() { return withFallback("/mcp/tools", {}, { tools: [], installed_mcps: [] }); },
|
|
439
|
+
mcpInstalled() { return withFallback("/mcp/installed", {}, { installed: [] }); },
|
|
440
|
+
mcpClaudeServers() { return withFallback("/mcp/claude-code-servers", {}, { servers: [] }); },
|
|
441
|
+
mcpCustom() { return withFallback("/mcp/custom", {}, { custom: [] }); },
|
|
442
|
+
mcpRecommend(query, limit = 6) { return raw("/mcp/recommend", { method: "POST", body: { query, limit } }); },
|
|
324
443
|
};
|
|
325
444
|
|
|
326
445
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -14,6 +14,7 @@ export const GROUPS = [
|
|
|
14
14
|
{ id: "retrieval", label: "Retrieval" },
|
|
15
15
|
{ id: "data", label: "Data" },
|
|
16
16
|
{ id: "compute", label: "Compute" },
|
|
17
|
+
{ id: "platform", label: "Platform" },
|
|
17
18
|
{ id: "system", label: "System" },
|
|
18
19
|
{ id: "admin", label: "Administration", adminOnly: true },
|
|
19
20
|
];
|
|
@@ -30,6 +31,7 @@ export const ROUTES = [
|
|
|
30
31
|
// Retrieval (the product identity)
|
|
31
32
|
{ key: "knowledge-graph", label: "Knowledge Graph", icon: "chart-dots-3", group: "retrieval", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Entities and relations extracted from your workspace." },
|
|
32
33
|
{ key: "hybrid-search", label: "Hybrid Search", icon: "arrows-join", group: "retrieval", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
|
|
34
|
+
{ key: "memory", label: "Memory", icon: "brain", group: "retrieval", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
|
|
33
35
|
|
|
34
36
|
// Data
|
|
35
37
|
{ key: "files", label: "Files", icon: "folders", group: "data", minMode: "basic", view: "files", title: "Files", desc: "Connected sources and indexed documents." },
|
|
@@ -37,9 +39,18 @@ export const ROUTES = [
|
|
|
37
39
|
|
|
38
40
|
// Compute
|
|
39
41
|
{ key: "agents", label: "Agents", icon: "robot", group: "compute", minMode: "advanced", view: "agents", title: "Agents", desc: "Multi-agent roles, runs, and handoffs." },
|
|
42
|
+
{ key: "workflows", label: "Workflows", icon: "sitemap", group: "compute", minMode: "advanced", view: "workflows", title: "Workflow Agents", desc: "Trigger → agent chain → tools → memory → result." },
|
|
43
|
+
{ key: "planning", label: "Planning", icon: "target-arrow", group: "compute", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan." },
|
|
40
44
|
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
41
45
|
{ key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "compute", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime." },
|
|
42
46
|
|
|
47
|
+
// Platform (the agent ecosystem)
|
|
48
|
+
{ key: "marketplace", label: "Marketplace", icon: "building-store", group: "platform", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills." },
|
|
49
|
+
{ key: "skills", label: "Skills", icon: "puzzle", group: "platform", minMode: "advanced", view: "skills", title: "Skills", desc: "Install, enable, and manage skills." },
|
|
50
|
+
{ key: "hooks", label: "Hooks", icon: "webhook", group: "platform", minMode: "advanced", view: "hooks", title: "Hooks", desc: "Lifecycle hooks across runs, tools, and workflows." },
|
|
51
|
+
{ key: "tools", label: "Tools", icon: "tools", group: "platform", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance." },
|
|
52
|
+
{ key: "mcp", label: "MCP", icon: "plug-connected", group: "platform", minMode: "advanced", view: "mcp", title: "MCP Manager", desc: "Connected MCP servers, available tools, and health." },
|
|
53
|
+
|
|
43
54
|
// System
|
|
44
55
|
{ key: "settings", label: "Settings", icon: "settings", group: "system", minMode: "basic", view: "settings", title: "Settings", desc: "Appearance, workspace, and integrations." },
|
|
45
56
|
|
|
@@ -14,6 +14,7 @@ export const GROUPS = [
|
|
|
14
14
|
{ id: "retrieval", label: "Retrieval" },
|
|
15
15
|
{ id: "data", label: "Data" },
|
|
16
16
|
{ id: "compute", label: "Compute" },
|
|
17
|
+
{ id: "platform", label: "Platform" },
|
|
17
18
|
{ id: "system", label: "System" },
|
|
18
19
|
{ id: "admin", label: "Administration", adminOnly: true },
|
|
19
20
|
];
|
|
@@ -30,6 +31,7 @@ export const ROUTES = [
|
|
|
30
31
|
// Retrieval (the product identity)
|
|
31
32
|
{ key: "knowledge-graph", label: "Knowledge Graph", icon: "chart-dots-3", group: "retrieval", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Entities and relations extracted from your workspace." },
|
|
32
33
|
{ key: "hybrid-search", label: "Hybrid Search", icon: "arrows-join", group: "retrieval", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
|
|
34
|
+
{ key: "memory", label: "Memory", icon: "brain", group: "retrieval", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
|
|
33
35
|
|
|
34
36
|
// Data
|
|
35
37
|
{ key: "files", label: "Files", icon: "folders", group: "data", minMode: "basic", view: "files", title: "Files", desc: "Connected sources and indexed documents." },
|
|
@@ -37,9 +39,18 @@ export const ROUTES = [
|
|
|
37
39
|
|
|
38
40
|
// Compute
|
|
39
41
|
{ key: "agents", label: "Agents", icon: "robot", group: "compute", minMode: "advanced", view: "agents", title: "Agents", desc: "Multi-agent roles, runs, and handoffs." },
|
|
42
|
+
{ key: "workflows", label: "Workflows", icon: "sitemap", group: "compute", minMode: "advanced", view: "workflows", title: "Workflow Agents", desc: "Trigger → agent chain → tools → memory → result." },
|
|
43
|
+
{ key: "planning", label: "Planning", icon: "target-arrow", group: "compute", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan." },
|
|
40
44
|
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
41
45
|
{ key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "compute", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime." },
|
|
42
46
|
|
|
47
|
+
// Platform (the agent ecosystem)
|
|
48
|
+
{ key: "marketplace", label: "Marketplace", icon: "building-store", group: "platform", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills." },
|
|
49
|
+
{ key: "skills", label: "Skills", icon: "puzzle", group: "platform", minMode: "advanced", view: "skills", title: "Skills", desc: "Install, enable, and manage skills." },
|
|
50
|
+
{ key: "hooks", label: "Hooks", icon: "webhook", group: "platform", minMode: "advanced", view: "hooks", title: "Hooks", desc: "Lifecycle hooks across runs, tools, and workflows." },
|
|
51
|
+
{ key: "tools", label: "Tools", icon: "tools", group: "platform", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance." },
|
|
52
|
+
{ key: "mcp", label: "MCP", icon: "plug-connected", group: "platform", minMode: "advanced", view: "mcp", title: "MCP Manager", desc: "Connected MCP servers, available tools, and health." },
|
|
53
|
+
|
|
43
54
|
// System
|
|
44
55
|
{ key: "settings", label: "Settings", icon: "settings", group: "system", minMode: "basic", view: "settings", title: "Settings", desc: "Appearance, workspace, and integrations." },
|
|
45
56
|
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { h, icon, $, $$ } from "./dom.a2773eb0.js";
|
|
9
9
|
import { store } from "./store.34ebd5e6.js";
|
|
10
|
-
import { api } from "./api.
|
|
10
|
+
import { api } from "./api.113660c5.js";
|
|
11
11
|
import * as c from "./components.4c83e0a9.js";
|
|
12
12
|
import { createRouter } from "./router.584570f2.js";
|
|
13
|
-
import { GROUPS, ROUTES, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView } from "./routes.
|
|
13
|
+
import { GROUPS, ROUTES, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView } from "./routes.07ad6696.js";
|
|
14
14
|
|
|
15
15
|
const MODES = [
|
|
16
16
|
{ key: "basic", label: "Basic", icon: "circle" },
|
|
@@ -9,8 +9,10 @@ export async function render(ctx) {
|
|
|
9
9
|
const { h, icon, c } = ctx;
|
|
10
10
|
|
|
11
11
|
const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
|
|
12
|
+
const registryHost = h("div", c.loading({ lines: 3, block: true }));
|
|
12
13
|
const rosterHost = h("div", c.loading({ lines: 2, block: true }));
|
|
13
14
|
const runsHost = h("div", c.loading({ lines: 4 }));
|
|
15
|
+
const registrySrc = h("span", c.sourceBadge("pending"));
|
|
14
16
|
const rosterSrc = h("span", c.sourceBadge("pending"));
|
|
15
17
|
const runsSrc = h("span", c.sourceBadge("pending"));
|
|
16
18
|
const healthSlot = h("span", c.sourceBadge("pending"));
|
|
@@ -23,6 +25,10 @@ export async function render(ctx) {
|
|
|
23
25
|
actions: [healthSlot],
|
|
24
26
|
}),
|
|
25
27
|
statHost,
|
|
28
|
+
h("section",
|
|
29
|
+
c.sectionHead("Agent Registry", registrySrc),
|
|
30
|
+
registryHost,
|
|
31
|
+
),
|
|
26
32
|
h("section",
|
|
27
33
|
c.sectionHead("Agent roster", rosterSrc),
|
|
28
34
|
rosterHost,
|
|
@@ -40,6 +46,7 @@ export async function render(ctx) {
|
|
|
40
46
|
);
|
|
41
47
|
|
|
42
48
|
hydrate(ctx, { statHost, rosterHost, runsHost, rosterSrc, runsSrc, healthSlot });
|
|
49
|
+
loadRegistry(ctx, { registryHost, registrySrc });
|
|
43
50
|
return root;
|
|
44
51
|
}
|
|
45
52
|
|
|
@@ -109,6 +116,83 @@ async function hydrate(ctx, hosts) {
|
|
|
109
116
|
);
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
async function loadRegistry(ctx, hosts) {
|
|
120
|
+
const { h, c } = ctx;
|
|
121
|
+
const { registryHost, registrySrc } = hosts;
|
|
122
|
+
const [registryRes, capsRes] = await Promise.all([ctx.api.agentRegistry(), ctx.api.agentCapabilities()]);
|
|
123
|
+
const agents = normalizeRegistry(registryRes.data);
|
|
124
|
+
const caps = (capsRes.data && capsRes.data.capabilities) || {};
|
|
125
|
+
registrySrc.replaceChildren(c.sourceBadge(registryRes.source === "live" || capsRes.source === "live" ? "live" : "unavailable"));
|
|
126
|
+
|
|
127
|
+
const nameInput = h("input.lt3-input", { type: "text", placeholder: "Custom agent name", "aria-label": "Custom agent name" });
|
|
128
|
+
const capsInput = h("input.lt3-input", { type: "text", placeholder: "capability-a, capability-b", "aria-label": "Custom agent capabilities" });
|
|
129
|
+
const registerBtn = h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: register } }, c.icon("plus"), "Register");
|
|
130
|
+
|
|
131
|
+
const capList = Object.keys(caps).sort();
|
|
132
|
+
const body = h("div.lt3-stack-4",
|
|
133
|
+
h("div.lt3-grid-2",
|
|
134
|
+
h("div.lt3-field", h("label", "Name"), nameInput),
|
|
135
|
+
h("div.lt3-field", h("label", "Capabilities"), capsInput),
|
|
136
|
+
),
|
|
137
|
+
h("div.lt3-row-2", registerBtn,
|
|
138
|
+
h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, "Custom agents persist in the local registry.")),
|
|
139
|
+
capList.length
|
|
140
|
+
? h("div.lt3-cluster", capList.slice(0, 18).map((cap) => h("span.lt3-chip", c.icon("sparkles"), `${cap} (${caps[cap].length})`)))
|
|
141
|
+
: h("p.lt3-faint", { style: { margin: 0 } }, "Capabilities appear here when the registry is live."),
|
|
142
|
+
agents.length
|
|
143
|
+
? h("div.lt3-grid-auto", agents.map((agent) => registryCard(ctx, agent)))
|
|
144
|
+
: c.emptyState({ icon: "robot-off", title: "Agent registry unavailable", body: "Start the local server to register and configure agents." }),
|
|
145
|
+
);
|
|
146
|
+
registryHost.replaceChildren(c.panel({ title: "Registry controls", sub: "Register, discover, and configure built-in or custom agents.", children: body }));
|
|
147
|
+
|
|
148
|
+
async function register() {
|
|
149
|
+
const name = nameInput.value.trim();
|
|
150
|
+
if (!name) { ctx.toast("Enter an agent name", "info"); return; }
|
|
151
|
+
const capabilities = capsInput.value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
152
|
+
registerBtn.disabled = true;
|
|
153
|
+
const res = await ctx.api.registerAgent({ name, type: "custom", capabilities });
|
|
154
|
+
registerBtn.disabled = false;
|
|
155
|
+
if (res && res.ok) {
|
|
156
|
+
ctx.toast(`Registered ${name}`, "ok");
|
|
157
|
+
loadRegistry(ctx, hosts);
|
|
158
|
+
} else {
|
|
159
|
+
ctx.toast("Register unavailable", "err");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function registryCard(ctx, agent) {
|
|
165
|
+
const { h, c } = ctx;
|
|
166
|
+
return c.card(h("div.lt3-stack-3",
|
|
167
|
+
h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "flex-start" } },
|
|
168
|
+
h("div",
|
|
169
|
+
h("b", agent.name),
|
|
170
|
+
h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)", "font-family": "var(--lt3-font-mono)" } }, agent.id),
|
|
171
|
+
),
|
|
172
|
+
c.pill(agent.source === "builtin" ? "built-in" : "custom", agent.source === "builtin" ? "info" : "warn"),
|
|
173
|
+
),
|
|
174
|
+
h("p.lt3-muted", { style: { "font-size": "var(--lt3-text-sm)", margin: 0 } }, agent.description || "No description."),
|
|
175
|
+
h("div.lt3-cluster", [c.statePill(agent.enabled ? "ready" : "idle"), c.pill(agent.type), c.pill(`v${agent.version || "1.0.0"}`)]),
|
|
176
|
+
agent.capabilities.length ? h("div.lt3-cluster", agent.capabilities.slice(0, 8).map((cap) => h("span.lt3-chip", cap))) : null,
|
|
177
|
+
h("div.lt3-row-2",
|
|
178
|
+
h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => toggleAgent(ctx, agent) } }, c.icon(agent.enabled ? "toggle-right" : "toggle-left"), agent.enabled ? "Disable" : "Enable"),
|
|
179
|
+
agent.removable ? h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => removeAgent(ctx, agent) } }, c.icon("trash"), "Remove") : null,
|
|
180
|
+
),
|
|
181
|
+
), { interactive: false });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function toggleAgent(ctx, agent) {
|
|
185
|
+
const res = await ctx.api.updateAgent(agent.id, { config: agent.config || {}, enabled: !agent.enabled });
|
|
186
|
+
ctx.toast(res && res.ok ? `${agent.name}: ${agent.enabled ? "disabled" : "enabled"}` : "Agent update unavailable", res && res.ok ? "ok" : "err");
|
|
187
|
+
if (res && res.ok) ctx.navigate("agents");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function removeAgent(ctx, agent) {
|
|
191
|
+
const res = await ctx.api.removeAgent(agent.id);
|
|
192
|
+
ctx.toast(res && res.ok ? `Removed ${agent.name}` : "Agent remove unavailable", res && res.ok ? "ok" : "err");
|
|
193
|
+
if (res && res.ok) ctx.navigate("agents");
|
|
194
|
+
}
|
|
195
|
+
|
|
112
196
|
/* ── Agent card ──────────────────────────────────────────────────────────── */
|
|
113
197
|
function agentCard(ctx, agent, byId) {
|
|
114
198
|
const { h, icon, c } = ctx;
|
|
@@ -158,6 +242,22 @@ function normalize(data) {
|
|
|
158
242
|
}));
|
|
159
243
|
}
|
|
160
244
|
|
|
245
|
+
function normalizeRegistry(data) {
|
|
246
|
+
const list = Array.isArray(data) ? data : (data && Array.isArray(data.agents) ? data.agents : []);
|
|
247
|
+
return list.map((agent, i) => ({
|
|
248
|
+
id: agent.id || `agent:${i}`,
|
|
249
|
+
name: agent.name || agent.id || `Agent ${i + 1}`,
|
|
250
|
+
type: agent.type || "custom",
|
|
251
|
+
version: agent.version || "1.0.0",
|
|
252
|
+
description: agent.description || "",
|
|
253
|
+
capabilities: Array.isArray(agent.capabilities) ? agent.capabilities : [],
|
|
254
|
+
source: agent.source || "user",
|
|
255
|
+
enabled: agent.enabled !== false,
|
|
256
|
+
removable: !!agent.removable,
|
|
257
|
+
config: agent.config || {},
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
|
|
161
261
|
const AVAILABLE_STATES = new Set(["available", "ready", "active", "ok", "idle"]);
|
|
162
262
|
function isAvailable(state) {
|
|
163
263
|
return AVAILABLE_STATES.has(String(state).toLowerCase());
|