ltcai 2.0.0 → 2.1.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.
@@ -9,7 +9,7 @@
9
9
  <body>
10
10
  <main>
11
11
  <h1>Multi-Agent Runtime</h1>
12
- <p class="sub">Planner · Executor · Reviewer · Researcher · Release — with handoff, retry, and an observable timeline.</p>
12
+ <p class="sub">Planner · Executor · Reviewer · Researcher · Release — with handoff, context packets, memory, retry, and replay.</p>
13
13
 
14
14
  <div class="section">
15
15
  <label>Goal</label>
@@ -28,6 +28,11 @@
28
28
  <h3>Recent agent runs</h3>
29
29
  <div id="runs"><div class="empty">Loading…</div></div>
30
30
  </div>
31
+
32
+ <div class="section">
33
+ <h3>Replay viewer</h3>
34
+ <div id="replay"><div class="empty">Select a recent run.</div></div>
35
+ </div>
31
36
  </main>
32
37
 
33
38
  <script type="module">
@@ -47,13 +52,33 @@
47
52
 
48
53
  function renderTimeline(timeline) {
49
54
  return (timeline || []).map((t) => {
50
- const label = t.event === "handoff" ? `↪ handoff ${escapeHtml(t.from)} → ${escapeHtml(t.to)}`
55
+ const label = (t.event || "").startsWith("handoff_") ? `↪ ${escapeHtml(t.event)} ${escapeHtml(t.from||"")} → ${escapeHtml(t.to||"")}`
56
+ : t.event === "handoff" ? `↪ handoff ${escapeHtml(t.from)} → ${escapeHtml(t.to)}`
51
57
  : t.event === "role" ? `● ${escapeHtml(t.role)} ${badge(t.status)}`
58
+ : t.event === "retry_requested" ? `↻ retry ${escapeHtml(t.reason||"")}`
59
+ : t.event === "review_approved" ? `✓ review approved`
52
60
  : `· ${escapeHtml(t.event)}`;
53
61
  return `<div class="timeline-item">${label}<div class="t-meta">${escapeHtml(t.note||t.timestamp||"")}</div></div>`;
54
62
  }).join("");
55
63
  }
56
64
 
65
+ function renderHandoffs(handoffs) {
66
+ if (!handoffs?.length) return `<div class="empty">No handoffs recorded.</div>`;
67
+ return handoffs.map((h) => `<div class="timeline-item">
68
+ <strong>${escapeHtml(h.handoff_id)}</strong> ${badge(h.status)}
69
+ <div class="t-meta">${escapeHtml(h.source_agent)} → ${escapeHtml(h.target_agent)} · ${escapeHtml(h.reason||"")}</div>
70
+ </div>`).join("");
71
+ }
72
+
73
+ function renderReview(result) {
74
+ const reviews = result.review_history || [];
75
+ const retries = result.retry_history || [];
76
+ return `<div class="grid two">
77
+ <div>${reviews.length ? reviews.map((r) => `<div class="timeline-item">${badge(r.outcome)} ${escapeHtml(r.reason||"")}<div class="t-meta">retry ${r.retry_count}</div></div>`).join("") : `<div class="empty">No review history.</div>`}</div>
78
+ <div>${retries.length ? retries.map((r) => `<div class="timeline-item">${badge("retry " + r.retry)} ${escapeHtml(r.reason||"")}<div class="t-meta">limit ${r.limit}</div></div>`).join("") : `<div class="empty">No retries.</div>`}</div>
79
+ </div>`;
80
+ }
81
+
57
82
  document.getElementById("runBtn").addEventListener("click", async () => {
58
83
  const btn = document.getElementById("runBtn");
59
84
  btn.disabled = true;
@@ -66,7 +91,9 @@
66
91
  <div class="card">
67
92
  <div class="row"><h3>${escapeHtml(r.output)}</h3><div class="spacer"></div>${badge(r.status)}</div>
68
93
  <div class="meta">retries: ${r.retries} · roles: ${(r.roles_run||[]).join(" → ")}</div>
69
- <div class="section">${renderTimeline(r.timeline)}</div>
94
+ <div class="section"><h3>Handoff chain</h3>${renderHandoffs(r.handoffs)}</div>
95
+ <div class="section"><h3>Review panel</h3>${renderReview(r)}</div>
96
+ <div class="section"><h3>Timeline</h3>${renderTimeline(r.timeline)}</div>
70
97
  </div>`;
71
98
  toast(`Agent run: ${r.status}`);
72
99
  await loadRuns();
@@ -82,9 +109,26 @@
82
109
  <div class="card" style="margin-bottom:10px">
83
110
  <div class="row"><h3>${escapeHtml((r.input||"").slice(0,80))}</h3><div class="spacer"></div>${badge(r.status)}</div>
84
111
  <div class="meta">${escapeHtml(r.agent_id)} · ${escapeHtml(r.created_at)} · ${(r.timeline||[]).length} timeline events</div>
112
+ <div class="row" style="margin-top:10px"><button class="ghost" data-replay="${r.id}">Replay</button></div>
85
113
  </div>`).join("");
86
114
  }
87
115
 
116
+ document.getElementById("runs").addEventListener("click", async (e) => {
117
+ const btn = e.target.closest("button[data-replay]");
118
+ if (!btn) return;
119
+ const out = document.getElementById("replay");
120
+ out.innerHTML = `<div class="empty">Loading replay…</div>`;
121
+ try {
122
+ const data = await api(`/agents/api/runs/${btn.dataset.replay}/replay`);
123
+ const frames = data.replay.frames || [];
124
+ out.innerHTML = frames.map((f) => `<div class="timeline-item">
125
+ <div class="row"><strong>${escapeHtml(f.event)}</strong><div class="spacer"></div>${badge(f.decision || "event")}</div>
126
+ <div class="t-meta">${escapeHtml(String(f.actor||""))} · ${escapeHtml(f.when||"")}</div>
127
+ <pre>${escapeHtml(JSON.stringify({ why: f.why, input: f.input, output: f.output }, null, 2))}</pre>
128
+ </div>`).join("") || `<div class="empty">No replay frames.</div>`;
129
+ } catch (err) { out.innerHTML = `<div class="empty">${escapeHtml(err.message)}</div>`; }
130
+ });
131
+
88
132
  loadRoles().catch((e) => toast(e.message));
89
133
  loadRuns().catch(() => {});
90
134
  </script>
@@ -12,6 +12,16 @@
12
12
  <p class="sub" id="sub">Versioned, permissioned plugins that extend skills, tools, and workflows.</p>
13
13
  <div id="list" class="grid"><div class="empty">Loading plugins…</div></div>
14
14
 
15
+ <div class="section">
16
+ <h3>Template foundation</h3>
17
+ <div id="templates" class="grid"><div class="empty">Loading templates…</div></div>
18
+ </div>
19
+
20
+ <div class="section">
21
+ <h3>Plugin execution viewer</h3>
22
+ <div id="pluginEvents"><div class="empty">Loading plugin events…</div></div>
23
+ </div>
24
+
15
25
  <div class="section">
16
26
  <h3>Validate a manifest</h3>
17
27
  <p class="sub">Paste a <code>plugin.json</code> to check it against the SDK schema and permission allow-list.</p>
@@ -55,6 +65,45 @@
55
65
  </div>`).join("");
56
66
  }
57
67
 
68
+ async function loadTemplates() {
69
+ const data = await api("/marketplace/templates");
70
+ const box = document.getElementById("templates");
71
+ if (!data.templates.length) { box.innerHTML = `<div class="empty">No templates available.</div>`; return; }
72
+ box.innerHTML = data.templates.map((t) => `
73
+ <div class="card">
74
+ <div class="row"><h3>${escapeHtml(t.name)}</h3><div class="spacer"></div>${badge(t.kind)}</div>
75
+ <div class="meta">v${escapeHtml(t.version)} · ${escapeHtml((t.metadata||{}).category || "foundation")}</div>
76
+ <p style="font-size:13px;color:#cbd5e1">${escapeHtml(t.description || "")}</p>
77
+ <div class="row" style="margin-top:12px">
78
+ <a class="btn ghost" target="_blank" href="/marketplace/templates/${t.kind}/${t.id}/export">Export</a>
79
+ <button data-template="${t.kind}:${t.id}">Install</button>
80
+ </div>
81
+ </div>`).join("");
82
+ }
83
+
84
+ async function loadPluginEvents() {
85
+ const data = await api("/realtime/feed?limit=80");
86
+ const events = (data.events || []).filter((e) => e.area === "plugins" || e.event_type?.startsWith("plugin_"));
87
+ const box = document.getElementById("pluginEvents");
88
+ if (!events.length) { box.innerHTML = `<div class="empty">No plugin executions yet.</div>`; return; }
89
+ box.innerHTML = events.slice(0, 20).map((ev) => `<div class="timeline-item">
90
+ <div class="row"><strong>${escapeHtml(ev.event_type)}</strong><div class="spacer"></div>${badge((ev.payload||{}).status || ev.area)}</div>
91
+ <div class="t-meta">${escapeHtml((ev.payload||{}).plugin_id || (ev.payload||{}).plugin || "")} · ${escapeHtml(ev.received_at || ev.timestamp || "")}</div>
92
+ </div>`).join("");
93
+ }
94
+
95
+ document.getElementById("templates").addEventListener("click", async (e) => {
96
+ const btn = e.target.closest("button[data-template]");
97
+ if (!btn) return;
98
+ btn.disabled = true;
99
+ const [kind, id] = btn.dataset.template.split(":");
100
+ try {
101
+ const exported = await api(`/marketplace/templates/${kind}/${id}/export`);
102
+ await api("/marketplace/templates/install", { method: "POST", body: JSON.stringify({ data: exported }) });
103
+ toast(`Installed template: ${id}`);
104
+ } catch (err) { toast(err.message); } finally { btn.disabled = false; }
105
+ });
106
+
58
107
  document.getElementById("list").addEventListener("click", async (e) => {
59
108
  const btn = e.target.closest("button[data-act]");
60
109
  if (!btn) return;
@@ -77,6 +126,8 @@
77
126
  });
78
127
 
79
128
  load().catch((e) => toast(e.message));
129
+ loadTemplates().catch((e) => toast(e.message));
130
+ loadPluginEvents().catch(() => {});
80
131
  </script>
81
132
  </body>
82
133
  </html>
@@ -35,6 +35,11 @@
35
35
  <h3>Run history</h3>
36
36
  <div id="runs"><div class="empty">No runs yet.</div></div>
37
37
  </div>
38
+
39
+ <div class="section">
40
+ <h3>Replay viewer</h3>
41
+ <div id="replay"><div class="empty">Select a workflow run.</div></div>
42
+ </div>
38
43
  </main>
39
44
 
40
45
  <script type="module">
@@ -109,10 +114,27 @@
109
114
  <div class="card" style="margin-bottom:10px">
110
115
  <div class="row"><h3>${escapeHtml(r.name)}</h3><div class="spacer"></div>${badge(r.status)}</div>
111
116
  <div class="meta">${escapeHtml(r.created_at)} · ${ (r.timeline||[]).length } steps</div>
117
+ <div class="row" style="margin-top:10px"><button class="ghost" data-replay="${r.id}">Replay</button></div>
112
118
  <pre>${escapeHtml(JSON.stringify(r.timeline, null, 2))}</pre>
113
119
  </div>`).join("");
114
120
  }
115
121
 
122
+ document.getElementById("runs").addEventListener("click", async (e) => {
123
+ const btn = e.target.closest("button[data-replay]");
124
+ if (!btn) return;
125
+ const out = document.getElementById("replay");
126
+ out.innerHTML = `<div class="empty">Loading replay…</div>`;
127
+ try {
128
+ const data = await api(`/workflows/api/runs/${btn.dataset.replay}/replay`);
129
+ const frames = data.replay.frames || [];
130
+ out.innerHTML = frames.map((f) => `<div class="timeline-item">
131
+ <div class="row"><strong>${escapeHtml(f.event)}</strong><div class="spacer"></div>${badge(f.decision || "event")}</div>
132
+ <div class="t-meta">${escapeHtml(String(f.actor||""))} · ${escapeHtml(f.when||"")}</div>
133
+ <pre>${escapeHtml(JSON.stringify({ why: f.why, input: f.input, output: f.output }, null, 2))}</pre>
134
+ </div>`).join("") || `<div class="empty">No replay frames.</div>`;
135
+ } catch (err) { out.innerHTML = `<div class="empty">${escapeHtml(err.message)}</div>`; }
136
+ });
137
+
116
138
  loadTemplate();
117
139
  loadList().catch((e) => toast(e.message));
118
140
  loadRuns().catch(() => {});