neoagent 1.4.1 → 1.4.3
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/docs/skills.md +4 -0
- package/package.json +3 -1
- package/server/db/database.js +76 -0
- package/server/public/app.html +124 -49
- package/server/public/assets/world-office-dark.png +0 -0
- package/server/public/assets/world-office-light.png +0 -0
- package/server/public/css/app.css +575 -242
- package/server/public/css/styles.css +445 -121
- package/server/public/js/app.js +1041 -423
- package/server/routes/memory.js +3 -1
- package/server/routes/settings.js +40 -2
- package/server/routes/skills.js +124 -85
- package/server/routes/store.js +100 -0
- package/server/services/ai/compaction.js +14 -30
- package/server/services/ai/engine.js +222 -200
- package/server/services/ai/history.js +188 -0
- package/server/services/ai/learning.js +143 -0
- package/server/services/ai/settings.js +80 -0
- package/server/services/ai/systemPrompt.js +57 -119
- package/server/services/ai/toolResult.js +151 -0
- package/server/services/ai/toolRunner.js +24 -6
- package/server/services/ai/toolSelector.js +140 -0
- package/server/services/ai/tools.js +71 -2
- package/server/services/manager.js +25 -2
- package/server/services/memory/embeddings.js +80 -14
- package/server/services/memory/manager.js +209 -16
- package/server/services/websocket.js +19 -6
package/server/public/js/app.js
CHANGED
|
@@ -5,6 +5,24 @@ if (window.mermaid) {
|
|
|
5
5
|
mermaid.initialize({ startOnLoad: false, theme: "dark" });
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
// ── Theme (follows OS preference automatically) ──
|
|
9
|
+
|
|
10
|
+
function applyTheme(isDark) {
|
|
11
|
+
document.documentElement.dataset.theme = isDark ? "dark" : "light";
|
|
12
|
+
if (window.mermaid) {
|
|
13
|
+
mermaid.initialize({ startOnLoad: false, theme: isDark ? "dark" : "default" });
|
|
14
|
+
}
|
|
15
|
+
if (window.pixelWorld) {
|
|
16
|
+
window.pixelWorld.syncTheme();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const _mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
21
|
+
applyTheme(_mq.matches);
|
|
22
|
+
_mq.addEventListener("change", (e) => applyTheme(e.matches));
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
8
26
|
// Global utility to re-run mermaid
|
|
9
27
|
function renderMermaids() {
|
|
10
28
|
if (window.mermaid) {
|
|
@@ -77,7 +95,32 @@ function formatTime(ts) {
|
|
|
77
95
|
|
|
78
96
|
// ── Navigation ──
|
|
79
97
|
|
|
80
|
-
|
|
98
|
+
const DEFAULT_PAGE = "chat";
|
|
99
|
+
const VALID_PAGES = new Set([
|
|
100
|
+
"chat",
|
|
101
|
+
"world",
|
|
102
|
+
"messaging",
|
|
103
|
+
"mcp",
|
|
104
|
+
"scheduler",
|
|
105
|
+
"memory",
|
|
106
|
+
"skills",
|
|
107
|
+
"protocols",
|
|
108
|
+
"logs",
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
function getPageFromLocation() {
|
|
112
|
+
const match = window.location.pathname.match(/^\/app\/([^/]+)$/);
|
|
113
|
+
const candidate = match?.[1] || (window.location.pathname === "/app" ? DEFAULT_PAGE : null);
|
|
114
|
+
return VALID_PAGES.has(candidate) ? candidate : DEFAULT_PAGE;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildPageUrl(page) {
|
|
118
|
+
return page === DEFAULT_PAGE ? "/app" : `/app/${page}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function navigateTo(page, { push = true } = {}) {
|
|
122
|
+
if (!VALID_PAGES.has(page)) page = DEFAULT_PAGE;
|
|
123
|
+
|
|
81
124
|
$$(".page").forEach((p) => p.classList.remove("active"));
|
|
82
125
|
$$(".sidebar-btn").forEach((b) => b.classList.remove("active"));
|
|
83
126
|
|
|
@@ -88,22 +131,25 @@ function navigateTo(page) {
|
|
|
88
131
|
if (btn) btn.classList.add("active");
|
|
89
132
|
}
|
|
90
133
|
|
|
134
|
+
if (push) {
|
|
135
|
+
const nextUrl = buildPageUrl(page);
|
|
136
|
+
if (window.location.pathname !== nextUrl) {
|
|
137
|
+
window.history.pushState({ page }, "", nextUrl);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
91
141
|
if (page === "memory") loadMemoryPage();
|
|
92
142
|
if (page === "skills") loadSkillsPage();
|
|
93
143
|
if (page === "mcp") loadMCPPage();
|
|
94
144
|
if (page === "scheduler") loadSchedulerPage();
|
|
95
145
|
if (page === "messaging") loadMessagingPage();
|
|
96
146
|
if (page === "protocols") loadProtocolsPage();
|
|
97
|
-
if (page === "
|
|
147
|
+
if (page === "world") {
|
|
98
148
|
requestAnimationFrame(() => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (data.runs && data.runs.length > 0) {
|
|
104
|
-
loadRunOnCanvas(data.runs[0].id, data.runs[0].title, data.runs[0].status);
|
|
105
|
-
}
|
|
106
|
-
}).catch(console.error);
|
|
149
|
+
ensureWorld();
|
|
150
|
+
if (pixelWorld) {
|
|
151
|
+
pixelWorld.resize();
|
|
152
|
+
pixelWorld.refreshSummary();
|
|
107
153
|
}
|
|
108
154
|
});
|
|
109
155
|
}
|
|
@@ -114,6 +160,10 @@ $$(".sidebar-btn[data-page]").forEach((btn) => {
|
|
|
114
160
|
btn.addEventListener("click", () => navigateTo(btn.dataset.page));
|
|
115
161
|
});
|
|
116
162
|
|
|
163
|
+
window.addEventListener("popstate", () => {
|
|
164
|
+
navigateTo(getPageFromLocation(), { push: false });
|
|
165
|
+
});
|
|
166
|
+
|
|
117
167
|
// ── Chat ──
|
|
118
168
|
|
|
119
169
|
const chatInput = $("#chatInput");
|
|
@@ -155,8 +205,8 @@ function sendMessage() {
|
|
|
155
205
|
chatMessages.appendChild(thinkingEl);
|
|
156
206
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
157
207
|
|
|
158
|
-
// Reset
|
|
159
|
-
|
|
208
|
+
// Reset world focus for the incoming run
|
|
209
|
+
resetWorldForNewRun();
|
|
160
210
|
|
|
161
211
|
socket.emit("agent:run", { task: text });
|
|
162
212
|
}
|
|
@@ -335,7 +385,7 @@ function renderMarkdown(text) {
|
|
|
335
385
|
return html;
|
|
336
386
|
}
|
|
337
387
|
|
|
338
|
-
// ──
|
|
388
|
+
// ── World Helpers ──
|
|
339
389
|
|
|
340
390
|
const TOOL_META = {
|
|
341
391
|
execute_command: { icon: "⚡", label: "Terminal", color: "cli" },
|
|
@@ -521,375 +571,860 @@ function describeResult(toolName, result) {
|
|
|
521
571
|
}
|
|
522
572
|
}
|
|
523
573
|
|
|
524
|
-
|
|
574
|
+
class PixelWorld {
|
|
575
|
+
constructor(canvas) {
|
|
576
|
+
this.canvas = canvas;
|
|
577
|
+
this.ctx = canvas.getContext("2d");
|
|
578
|
+
this.buffer = document.createElement("canvas");
|
|
579
|
+
this.buffer.width = 384;
|
|
580
|
+
this.buffer.height = 216;
|
|
581
|
+
this.bctx = this.buffer.getContext("2d");
|
|
582
|
+
this.dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
583
|
+
this.tick = 0;
|
|
584
|
+
this.runId = null;
|
|
585
|
+
this.runMode = "idle";
|
|
586
|
+
this.activeTool = null;
|
|
587
|
+
this.taskLabel = "No active run";
|
|
588
|
+
this.statusLabel = "Ambient systems nominal";
|
|
589
|
+
this.totalTools = 0;
|
|
590
|
+
this.totalMessages = 0;
|
|
591
|
+
this.helperCounter = 0;
|
|
592
|
+
this.scanFlash = 0;
|
|
593
|
+
this.socialPulse = 0;
|
|
594
|
+
this.errorFlash = 0;
|
|
595
|
+
this.recentEvents = [];
|
|
596
|
+
this.historyLoaded = false;
|
|
597
|
+
this.stepAssignments = new Map();
|
|
598
|
+
this.structures = [
|
|
599
|
+
{ key: "core", x: 138, y: 78, w: 104, h: 64, color: "#22c55e", glow: 0, label: "Lead Desk" },
|
|
600
|
+
{ key: "browser", x: 20, y: 16, w: 96, h: 80, color: "#3b82f6", glow: 0, label: "Research Corner" },
|
|
601
|
+
{ key: "memory", x: 286, y: 18, w: 82, h: 78, color: "#f59e0b", glow: 0, label: "Archive Wall" },
|
|
602
|
+
{ key: "cli", x: 16, y: 108, w: 108, h: 84, color: "#f97316", glow: 0, label: "Ops Bench" },
|
|
603
|
+
{ key: "social", x: 288, y: 116, w: 80, h: 74, color: "#ec4899", glow: 0, label: "Comms Desk" },
|
|
604
|
+
];
|
|
605
|
+
this.helperSlots = [
|
|
606
|
+
{ x: 134, y: 160 },
|
|
607
|
+
{ x: 250, y: 160 },
|
|
608
|
+
{ x: 164, y: 176 },
|
|
609
|
+
{ x: 222, y: 176 },
|
|
610
|
+
];
|
|
611
|
+
this.mainAgent = {
|
|
612
|
+
id: "lead-agent",
|
|
613
|
+
name: "NeoAgent",
|
|
614
|
+
type: "lead",
|
|
615
|
+
x: 192,
|
|
616
|
+
y: 104,
|
|
617
|
+
tint: "#9ef7dc",
|
|
618
|
+
phase: 0.8,
|
|
619
|
+
focus: "core",
|
|
620
|
+
specialty: "Orchestrating the whole task",
|
|
621
|
+
status: "Waiting for the next task",
|
|
622
|
+
lastActive: 0,
|
|
623
|
+
};
|
|
624
|
+
this.helpers = [];
|
|
625
|
+
this.packets = [];
|
|
626
|
+
this.palette = null;
|
|
627
|
+
this.officeImages = {
|
|
628
|
+
dark: this.loadImage("/assets/world-office-dark.png"),
|
|
629
|
+
light: this.loadImage("/assets/world-office-light.png"),
|
|
630
|
+
};
|
|
631
|
+
this.ui = {
|
|
632
|
+
modePill: $("#worldModePill"),
|
|
633
|
+
toolPill: $("#worldToolPill"),
|
|
634
|
+
task: $("#worldTaskValue"),
|
|
635
|
+
status: $("#worldStatusValue"),
|
|
636
|
+
mode: $("#worldModeValue"),
|
|
637
|
+
run: $("#worldRunValue"),
|
|
638
|
+
tools: $("#worldToolsValue"),
|
|
639
|
+
helpers: $("#worldHelpersValue"),
|
|
640
|
+
messages: $("#worldMessagesValue"),
|
|
641
|
+
agents: $("#worldAgentList"),
|
|
642
|
+
events: $("#worldEventList"),
|
|
643
|
+
badge: $("#worldBadge"),
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
this.resize = this.resize.bind(this);
|
|
647
|
+
this.loop = this.loop.bind(this);
|
|
648
|
+
window.addEventListener("resize", this.resize);
|
|
649
|
+
this.syncTheme();
|
|
650
|
+
this.resize();
|
|
651
|
+
this.renderAgents();
|
|
652
|
+
this.renderEventList();
|
|
653
|
+
this.renderHud();
|
|
654
|
+
requestAnimationFrame(this.loop);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
resize() {
|
|
658
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
659
|
+
const width = Math.max(320, Math.floor(rect.width || this.canvas.clientWidth || 640));
|
|
660
|
+
const height = Math.max(320, Math.floor(rect.height || this.canvas.clientHeight || 560));
|
|
661
|
+
this.canvas.width = Math.floor(width * this.dpr);
|
|
662
|
+
this.canvas.height = Math.floor(height * this.dpr);
|
|
663
|
+
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
664
|
+
this.ctx.imageSmoothingEnabled = false;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
syncTheme() {
|
|
668
|
+
const styles = getComputedStyle(document.documentElement);
|
|
669
|
+
const isDark = document.documentElement.dataset.theme !== "light";
|
|
670
|
+
this.palette = {
|
|
671
|
+
isDark,
|
|
672
|
+
bg0: styles.getPropertyValue("--bg-0").trim(),
|
|
673
|
+
bg1: styles.getPropertyValue("--bg-1").trim(),
|
|
674
|
+
bg2: styles.getPropertyValue("--bg-2").trim(),
|
|
675
|
+
bg3: styles.getPropertyValue("--bg-3").trim(),
|
|
676
|
+
text: styles.getPropertyValue("--text-primary").trim(),
|
|
677
|
+
muted: styles.getPropertyValue("--text-muted").trim(),
|
|
678
|
+
border: styles.getPropertyValue("--border").trim(),
|
|
679
|
+
accent: styles.getPropertyValue("--accent").trim(),
|
|
680
|
+
success: styles.getPropertyValue("--success").trim(),
|
|
681
|
+
info: styles.getPropertyValue("--info").trim(),
|
|
682
|
+
warning: styles.getPropertyValue("--warning").trim(),
|
|
683
|
+
error: styles.getPropertyValue("--error").trim(),
|
|
684
|
+
floor: isDark ? "#252b31" : "#eceff2",
|
|
685
|
+
floorAlt: isDark ? "#2e353d" : "#dfe5ea",
|
|
686
|
+
wall: isDark ? "#151a1f" : "#fcfdff",
|
|
687
|
+
trim: isDark ? "#0d1115" : "#cfd6de",
|
|
688
|
+
desk: isDark ? "#4b5563" : "#d1d8e0",
|
|
689
|
+
deskTop: isDark ? "#667181" : "#e0e5eb",
|
|
690
|
+
chair: isDark ? "#1b232c" : "#8f9bab",
|
|
691
|
+
screen: isDark ? "#111827" : "#eff6ff",
|
|
692
|
+
screenGlow: isDark ? "#60a5fa" : "#2563eb",
|
|
693
|
+
glass: isDark ? "rgba(120, 162, 219, 0.18)" : "rgba(134, 189, 255, 0.28)",
|
|
694
|
+
plant: isDark ? "#4ca56f" : "#5fba7f",
|
|
695
|
+
plantDark: isDark ? "#2a6d48" : "#438e5f",
|
|
696
|
+
shadow: isDark ? "rgba(0,0,0,0.26)" : "rgba(59,76,94,0.10)",
|
|
697
|
+
paper: isDark ? "#dbeafe" : "#ffffff",
|
|
698
|
+
rug: isDark ? "#2f3640" : "#dde4eb",
|
|
699
|
+
rugLine: isDark ? "#3d4754" : "#c7d1db",
|
|
700
|
+
wood: isDark ? "#705038" : "#c28d62",
|
|
701
|
+
coffee: isDark ? "#2b211d" : "#6b4b38",
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
loadImage(src) {
|
|
706
|
+
const img = new Image();
|
|
707
|
+
img.src = src;
|
|
708
|
+
return img;
|
|
709
|
+
}
|
|
525
710
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
711
|
+
refreshSummary() {
|
|
712
|
+
if (this.historyLoaded) return;
|
|
713
|
+
api("/agents?limit=6")
|
|
714
|
+
.then((data) => {
|
|
715
|
+
this.historyLoaded = true;
|
|
716
|
+
const runs = data.runs || [];
|
|
717
|
+
if (!runs.length || this.runMode !== "idle") return;
|
|
718
|
+
this.pushEvent("history", `${runs.length} recent runs archived in the background.`);
|
|
719
|
+
})
|
|
720
|
+
.catch(() => {});
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
resetForNewRun() {
|
|
724
|
+
this.runId = null;
|
|
725
|
+
this.runMode = "idle";
|
|
726
|
+
this.activeTool = null;
|
|
727
|
+
this.taskLabel = "No active run";
|
|
728
|
+
this.statusLabel = "Ambient systems nominal";
|
|
729
|
+
this.totalTools = 0;
|
|
730
|
+
this.stepAssignments.clear();
|
|
731
|
+
this.helpers = [];
|
|
732
|
+
this.packets.length = 0;
|
|
733
|
+
this.scanFlash = 0;
|
|
734
|
+
this.errorFlash = 0;
|
|
735
|
+
this.mainAgent.focus = "core";
|
|
736
|
+
this.mainAgent.status = "Waiting for the next task";
|
|
737
|
+
this.mainAgent.lastActive = this.tick;
|
|
738
|
+
this.renderAgents();
|
|
739
|
+
this.renderHud();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
onRunStart(data) {
|
|
743
|
+
this.runId = data.runId;
|
|
744
|
+
this.runMode = "running";
|
|
745
|
+
this.activeTool = "Boot sequence";
|
|
746
|
+
this.scanFlash = 1;
|
|
747
|
+
this.mainAgent.status = "Welcoming a new task";
|
|
748
|
+
this.mainAgent.lastActive = this.tick;
|
|
749
|
+
this.pushEvent("run", `${data.title || `Run ${data.runId}`} is now live.`);
|
|
750
|
+
this.flashStructure("core", 1.2);
|
|
751
|
+
this.renderAgents();
|
|
752
|
+
this.renderHud(data.title || `Run ${data.runId}`, "NeoAgent is getting everything set up");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
onThinking(data) {
|
|
756
|
+
this.runMode = "running";
|
|
757
|
+
this.activeTool = `Thinking step ${data.iteration}`;
|
|
758
|
+
this.scanFlash = Math.min(1.4, this.scanFlash + 0.18);
|
|
759
|
+
this.mainAgent.status = `Thinking through step ${data.iteration}`;
|
|
760
|
+
this.mainAgent.lastActive = this.tick;
|
|
761
|
+
this.pushEvent("think", `NeoAgent is thinking through step ${data.iteration}.`);
|
|
762
|
+
this.renderAgents();
|
|
763
|
+
this.renderHud(undefined, "NeoAgent is planning the next move");
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
onToolStart(data) {
|
|
767
|
+
this.runMode = "running";
|
|
768
|
+
this.activeTool = getToolMeta(data.toolName).label;
|
|
769
|
+
this.totalTools += 1;
|
|
770
|
+
const structureKey = this.getStructureForTool(data.toolName);
|
|
771
|
+
const actor = this.assignActor(data.stepId, data.toolName, data.toolArgs, structureKey);
|
|
772
|
+
const target = this.getStructure(structureKey);
|
|
773
|
+
this.spawnPacket(actor, target, target.color, data.toolName);
|
|
774
|
+
this.flashStructure(structureKey, 1.4);
|
|
775
|
+
this.pushEvent("tool", `${actor.name} is using ${getToolMeta(data.toolName).label.toLowerCase()} for ${this.getShortToolText(data.toolName, data.toolArgs)}.`);
|
|
776
|
+
this.renderAgents();
|
|
777
|
+
this.renderHud(undefined, `${actor.name} is working through ${target.label}`);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
onToolEnd(data) {
|
|
781
|
+
const structureKey = this.getStructureForTool(data.toolName);
|
|
782
|
+
const actor = this.resolveActorForStep(data.stepId);
|
|
783
|
+
this.flashStructure(structureKey, data.status === "failed" ? 1.8 : 0.9);
|
|
784
|
+
if (data.status === "failed") {
|
|
785
|
+
this.runMode = "failed";
|
|
786
|
+
this.errorFlash = 1;
|
|
787
|
+
actor.status = "Hit an issue and is regrouping";
|
|
788
|
+
actor.lastActive = this.tick;
|
|
789
|
+
this.pushEvent("fault", `${actor.name} hit a snag while using ${getToolMeta(data.toolName).label.toLowerCase()}.`);
|
|
790
|
+
this.renderHud(undefined, `${actor.name} ran into an error`);
|
|
791
|
+
} else {
|
|
792
|
+
actor.status = `Wrapped up ${getToolMeta(data.toolName).label.toLowerCase()}`;
|
|
793
|
+
actor.lastActive = this.tick;
|
|
794
|
+
this.pushEvent("sync", `${actor.name} finished ${getToolMeta(data.toolName).label.toLowerCase()} cleanly.`);
|
|
795
|
+
this.renderHud(undefined, `${actor.name} finished successfully`);
|
|
796
|
+
}
|
|
797
|
+
this.renderAgents();
|
|
798
|
+
this.stepAssignments.delete(data.stepId);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
onRunComplete(data) {
|
|
802
|
+
this.runMode = data.status === "failed" ? "failed" : "completed";
|
|
803
|
+
this.activeTool = data.status === "failed" ? "Recovery" : "Cooling down";
|
|
804
|
+
this.stepAssignments.clear();
|
|
805
|
+
this.mainAgent.status =
|
|
806
|
+
this.runMode === "failed" ? "Comforting the crew and recovering" : "Wrapping up with the crew";
|
|
807
|
+
this.mainAgent.lastActive = this.tick;
|
|
808
|
+
for (const helper of this.helpers) {
|
|
809
|
+
helper.status =
|
|
810
|
+
this.runMode === "failed" ? "Standing by for retry" : "Heading back after helping";
|
|
811
|
+
}
|
|
812
|
+
this.flashStructure("core", this.runMode === "failed" ? 1.6 : 1.2);
|
|
813
|
+
this.pushEvent(
|
|
814
|
+
this.runMode === "failed" ? "fault" : "done",
|
|
815
|
+
data.content ? data.content.slice(0, 90) : this.runMode === "failed" ? "The team is recovering from a failed run." : "The team finished the run."
|
|
816
|
+
);
|
|
817
|
+
this.renderAgents();
|
|
818
|
+
this.renderHud(undefined, this.runMode === "failed" ? "The crew is recovering from a rough run" : "The crew wrapped everything up nicely");
|
|
531
819
|
}
|
|
532
820
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
this.
|
|
536
|
-
|
|
821
|
+
onRunError(data) {
|
|
822
|
+
this.runMode = "failed";
|
|
823
|
+
this.activeTool = "Recovery";
|
|
824
|
+
this.errorFlash = 1.1;
|
|
825
|
+
this.mainAgent.status = "Helping the crew recover";
|
|
826
|
+
for (const helper of this.helpers) {
|
|
827
|
+
helper.status = "Waiting for new instructions";
|
|
828
|
+
}
|
|
829
|
+
this.flashStructure("core", 1.6);
|
|
830
|
+
this.pushEvent("fault", data.error || "Unknown run error");
|
|
831
|
+
this.renderAgents();
|
|
832
|
+
this.renderHud(undefined, data.error || "The crew hit an unexpected error");
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
onMessage(data) {
|
|
836
|
+
this.totalMessages += 1;
|
|
837
|
+
this.socialPulse = 1.2;
|
|
838
|
+
this.flashStructure("social", 1.5);
|
|
839
|
+
const actor = this.helpers.find((helper) => helper.focus === "social") || this.mainAgent;
|
|
840
|
+
actor.status = "Greeting a new incoming message";
|
|
841
|
+
actor.lastActive = this.tick;
|
|
842
|
+
const target = this.getStructure("social");
|
|
843
|
+
this.spawnPacket(actor, target, "#ff7db7", "message");
|
|
844
|
+
this.pushEvent("msg", `${actor.name} noticed a ${data.platform} message: ${String(data.content || "").slice(0, 72)}`);
|
|
845
|
+
this.renderAgents();
|
|
846
|
+
this.renderHud(undefined, "A friendly ping just reached the message port");
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
getShortToolText(toolName, toolArgs) {
|
|
537
850
|
const desc = describeArgs(toolName, toolArgs);
|
|
851
|
+
return desc?.headline ? desc.headline.slice(0, 58) : "signal received";
|
|
852
|
+
}
|
|
538
853
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
854
|
+
getStructureForTool(toolName) {
|
|
855
|
+
if (toolName.startsWith("browser_")) return "browser";
|
|
856
|
+
if (toolName.startsWith("memory_")) return "memory";
|
|
857
|
+
if (toolName === "execute_command") return "cli";
|
|
858
|
+
if (toolName === "send_message" || toolName === "make_call") return "social";
|
|
859
|
+
return "core";
|
|
860
|
+
}
|
|
543
861
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
862
|
+
getStructure(key) {
|
|
863
|
+
return this.structures.find((item) => item.key === key) || this.structures[0];
|
|
864
|
+
}
|
|
547
865
|
|
|
548
|
-
|
|
549
|
-
if (toolName === "
|
|
550
|
-
const
|
|
551
|
-
|
|
866
|
+
assignActor(stepId, toolName, toolArgs, structureKey) {
|
|
867
|
+
if (toolName === "spawn_subagent") {
|
|
868
|
+
const helper = this.spawnHelper(toolArgs, structureKey);
|
|
869
|
+
this.stepAssignments.set(stepId, helper.id);
|
|
870
|
+
this.mainAgent.status = `Delegating work to ${helper.name}`;
|
|
871
|
+
this.mainAgent.lastActive = this.tick;
|
|
872
|
+
return helper;
|
|
552
873
|
}
|
|
553
874
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
875
|
+
const specialist = this.helpers
|
|
876
|
+
.filter((helper) => helper.focus === structureKey)
|
|
877
|
+
.sort((a, b) => a.lastActive - b.lastActive)[0];
|
|
878
|
+
const actor = specialist || this.mainAgent;
|
|
879
|
+
actor.focus = structureKey;
|
|
880
|
+
actor.status = this.describeFriendlyAction(toolName, toolArgs);
|
|
881
|
+
actor.lastActive = this.tick;
|
|
882
|
+
this.stepAssignments.set(stepId, actor.id);
|
|
883
|
+
return actor;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
resolveActorForStep(stepId) {
|
|
887
|
+
const actorId = this.stepAssignments.get(stepId);
|
|
888
|
+
if (!actorId || actorId === this.mainAgent.id) return this.mainAgent;
|
|
889
|
+
return this.helpers.find((helper) => helper.id === actorId) || this.mainAgent;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
spawnHelper(toolArgs, structureKey) {
|
|
893
|
+
const slot = this.helperSlots[this.helpers.length % this.helperSlots.length];
|
|
894
|
+
this.helperCounter += 1;
|
|
895
|
+
const specialty = this.inferHelperSpecialty(toolArgs, structureKey);
|
|
896
|
+
const focus = this.inferHelperFocus(toolArgs, structureKey);
|
|
897
|
+
const helper = {
|
|
898
|
+
id: `helper-${this.helperCounter}`,
|
|
899
|
+
name: `Scout-${this.helperCounter}`,
|
|
900
|
+
type: "helper",
|
|
901
|
+
x: slot.x,
|
|
902
|
+
y: slot.y,
|
|
903
|
+
tint: ["#9fd6ff", "#ffd36b", "#ff9dce", "#cdb8ff"][this.helperCounter % 4],
|
|
904
|
+
phase: 0.5 + this.helperCounter,
|
|
905
|
+
focus,
|
|
906
|
+
specialty,
|
|
907
|
+
status: `Joining the task to help with ${specialty.toLowerCase()}`,
|
|
908
|
+
lastActive: this.tick,
|
|
909
|
+
};
|
|
910
|
+
this.helpers.push(helper);
|
|
911
|
+
this.spawnPacket(this.mainAgent, helper, helper.tint, "delegate");
|
|
912
|
+
this.pushEvent("team", `NeoAgent spawned ${helper.name} to help with ${specialty.toLowerCase()}.`);
|
|
913
|
+
return helper;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
inferHelperSpecialty(toolArgs, structureKey) {
|
|
917
|
+
const headline =
|
|
918
|
+
toolArgs?.task ||
|
|
919
|
+
toolArgs?.prompt ||
|
|
920
|
+
toolArgs?.description ||
|
|
921
|
+
toolArgs?.content ||
|
|
922
|
+
this.getStructure(structureKey).label;
|
|
923
|
+
return String(headline).slice(0, 36);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
inferHelperFocus(toolArgs, fallback) {
|
|
927
|
+
const text = JSON.stringify(toolArgs || {}).toLowerCase();
|
|
928
|
+
if (text.includes("browser") || text.includes("web") || text.includes("search") || text.includes("page")) return "browser";
|
|
929
|
+
if (text.includes("memory") || text.includes("recall") || text.includes("history")) return "memory";
|
|
930
|
+
if (text.includes("command") || text.includes("shell") || text.includes("terminal") || text.includes("file")) return "cli";
|
|
931
|
+
if (text.includes("message") || text.includes("call") || text.includes("email")) return "social";
|
|
932
|
+
return fallback;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
describeFriendlyAction(toolName, toolArgs) {
|
|
936
|
+
const headline = this.getShortToolText(toolName, toolArgs);
|
|
937
|
+
if (toolName === "execute_command") return `Checking the command forge for ${headline}`;
|
|
938
|
+
if (toolName.startsWith("browser_")) return `Exploring the web for ${headline}`;
|
|
939
|
+
if (toolName.startsWith("memory_")) return `Digging through memory for ${headline}`;
|
|
940
|
+
if (toolName === "send_message" || toolName === "make_call") return `Reaching out about ${headline}`;
|
|
941
|
+
return `Working on ${headline}`;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
spawnPacket(bot, structure, color, label) {
|
|
945
|
+
const targetX = structure.x + Math.floor((structure.w || 2) / 2);
|
|
946
|
+
const targetY = structure.y + ((structure.h || 2) > 2 ? 4 : 0);
|
|
947
|
+
this.packets.push({
|
|
948
|
+
x: bot.x,
|
|
949
|
+
y: bot.y - 4,
|
|
950
|
+
fromX: bot.x,
|
|
951
|
+
fromY: bot.y - 4,
|
|
952
|
+
toX: targetX,
|
|
953
|
+
toY: targetY,
|
|
954
|
+
color,
|
|
955
|
+
label,
|
|
956
|
+
progress: 0,
|
|
957
|
+
speed: 0.018 + Math.random() * 0.018,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
flashStructure(key, amount) {
|
|
962
|
+
const structure = this.getStructure(key);
|
|
963
|
+
structure.glow = Math.max(structure.glow, amount);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
pushEvent(tag, text) {
|
|
967
|
+
this.recentEvents.unshift({
|
|
968
|
+
tag,
|
|
969
|
+
text,
|
|
970
|
+
time: new Date(),
|
|
971
|
+
});
|
|
972
|
+
this.recentEvents = this.recentEvents.slice(0, 6);
|
|
973
|
+
if (this.ui.badge) this.ui.badge.classList.remove("hidden");
|
|
974
|
+
this.renderEventList();
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
renderAgents() {
|
|
978
|
+
const list = this.ui.agents;
|
|
979
|
+
if (!list) return;
|
|
980
|
+
const roster = [this.mainAgent, ...this.helpers];
|
|
981
|
+
list.innerHTML = roster
|
|
982
|
+
.map((agent) => `
|
|
983
|
+
<div class="world-agent-card">
|
|
984
|
+
<div class="world-agent-topline">
|
|
985
|
+
<span class="world-agent-title">${escapeHtml(agent.name)}</span>
|
|
986
|
+
<span class="world-agent-chip ${agent.type === "lead" ? "lead" : "helper"}">${agent.type === "lead" ? "Lead" : "Helper"}</span>
|
|
987
|
+
</div>
|
|
988
|
+
<div class="world-agent-meta">${escapeHtml(agent.specialty)}</div>
|
|
989
|
+
<div class="world-agent-status">${escapeHtml(agent.status)}</div>
|
|
565
990
|
</div>
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
991
|
+
`)
|
|
992
|
+
.join("");
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
renderEventList() {
|
|
996
|
+
const list = this.ui.events;
|
|
997
|
+
if (!list) return;
|
|
998
|
+
if (!this.recentEvents.length) {
|
|
999
|
+
list.innerHTML = '<div class="world-empty-state">The world is idling. Start a task in chat to wake everything up.</div>';
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
list.innerHTML = this.recentEvents
|
|
1003
|
+
.map((event) => `
|
|
1004
|
+
<div class="world-event-entry">
|
|
1005
|
+
<div class="world-event-topline">
|
|
1006
|
+
<span class="world-event-tag">${escapeHtml(event.tag)}</span>
|
|
1007
|
+
<span class="world-event-time">${escapeHtml(formatTime(event.time))}</span>
|
|
1008
|
+
</div>
|
|
1009
|
+
<div class="world-event-text">${escapeHtml(event.text)}</div>
|
|
571
1010
|
</div>
|
|
572
|
-
|
|
1011
|
+
`)
|
|
1012
|
+
.join("");
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
renderHud(taskText, statusText) {
|
|
1016
|
+
if (taskText) this.taskLabel = taskText;
|
|
1017
|
+
if (statusText) this.statusLabel = statusText;
|
|
1018
|
+
const modeText =
|
|
1019
|
+
this.runMode === "running"
|
|
1020
|
+
? "Running"
|
|
1021
|
+
: this.runMode === "completed"
|
|
1022
|
+
? "Complete"
|
|
1023
|
+
: this.runMode === "failed"
|
|
1024
|
+
? "Fault"
|
|
1025
|
+
: "Idle";
|
|
1026
|
+
if (this.ui.modePill) this.ui.modePill.textContent = modeText;
|
|
1027
|
+
if (this.ui.toolPill) this.ui.toolPill.textContent = this.activeTool || "Awaiting signal";
|
|
1028
|
+
if (this.ui.task) this.ui.task.textContent = this.taskLabel || (this.runId ? `Run ${this.runId}` : "No active run");
|
|
1029
|
+
if (this.ui.status) this.ui.status.textContent = this.statusLabel || this.getAmbientStatus();
|
|
1030
|
+
if (this.ui.mode) this.ui.mode.textContent = modeText;
|
|
1031
|
+
if (this.ui.run) this.ui.run.textContent = this.runId ? String(this.runId) : "None";
|
|
1032
|
+
if (this.ui.tools) this.ui.tools.textContent = String(this.totalTools);
|
|
1033
|
+
if (this.ui.helpers) this.ui.helpers.textContent = String(this.helpers.length);
|
|
1034
|
+
if (this.ui.messages) this.ui.messages.textContent = String(this.totalMessages);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
getAmbientStatus() {
|
|
1038
|
+
if (this.runMode === "running") return "The office is busy and everyone is moving work forward";
|
|
1039
|
+
if (this.runMode === "completed") return "The office settled down after a clean handoff";
|
|
1040
|
+
if (this.runMode === "failed") return "The team is regrouping after a rough patch";
|
|
1041
|
+
return "The office is quiet and ready for the next task";
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
loop() {
|
|
1045
|
+
this.tick += 1;
|
|
1046
|
+
this.updateSimulation();
|
|
1047
|
+
this.draw();
|
|
1048
|
+
requestAnimationFrame(this.loop);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
updateSimulation() {
|
|
1052
|
+
this.scanFlash *= 0.97;
|
|
1053
|
+
this.socialPulse *= 0.94;
|
|
1054
|
+
this.errorFlash *= 0.93;
|
|
1055
|
+
|
|
1056
|
+
for (const structure of this.structures) {
|
|
1057
|
+
structure.glow *= 0.94;
|
|
1058
|
+
}
|
|
573
1059
|
|
|
574
|
-
this.
|
|
575
|
-
this.
|
|
576
|
-
el: stepEl,
|
|
577
|
-
cardEl: stepEl.querySelector(`#atl-card-${stepId}`),
|
|
578
|
-
isResponse: false,
|
|
579
|
-
});
|
|
580
|
-
this.stepCount++;
|
|
1060
|
+
this.mainAgent.phase += 0.025;
|
|
1061
|
+
for (const helper of this.helpers) helper.phase += 0.025;
|
|
581
1062
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
1063
|
+
this.packets = this.packets.filter((packet) => {
|
|
1064
|
+
packet.progress += packet.speed;
|
|
1065
|
+
packet.x = packet.fromX + (packet.toX - packet.fromX) * packet.progress;
|
|
1066
|
+
packet.y = packet.fromY + (packet.toY - packet.fromY) * packet.progress - Math.sin(packet.progress * Math.PI) * 10;
|
|
1067
|
+
return packet.progress < 1;
|
|
585
1068
|
});
|
|
586
|
-
|
|
587
|
-
stepEl.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
588
|
-
return stepEl;
|
|
589
1069
|
}
|
|
590
1070
|
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
1071
|
+
draw() {
|
|
1072
|
+
const ctx = this.bctx;
|
|
1073
|
+
const t = this.tick;
|
|
1074
|
+
ctx.clearRect(0, 0, 384, 216);
|
|
1075
|
+
this.drawOffice(ctx);
|
|
1076
|
+
this.drawStructures(ctx);
|
|
1077
|
+
this.drawWalkways(ctx);
|
|
1078
|
+
this.drawBots(ctx, t);
|
|
1079
|
+
this.drawPackets(ctx);
|
|
1080
|
+
this.drawStatusEffects(ctx, t);
|
|
1081
|
+
this.drawScanlines(ctx);
|
|
1082
|
+
|
|
1083
|
+
this.ctx.save();
|
|
1084
|
+
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1085
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
1086
|
+
this.ctx.imageSmoothingEnabled = false;
|
|
1087
|
+
this.ctx.drawImage(this.buffer, 0, 0, this.canvas.width, this.canvas.height);
|
|
1088
|
+
this.ctx.restore();
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
drawOffice(ctx) {
|
|
1092
|
+
const ref = this.palette.isDark ? this.officeImages.dark : this.officeImages.light;
|
|
1093
|
+
if (ref && ref.complete && ref.naturalWidth > 0) {
|
|
1094
|
+
this.drawReferenceImage(ctx, ref, 384, 216);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const p = this.palette;
|
|
1099
|
+
ctx.fillStyle = p.floor;
|
|
1100
|
+
ctx.fillRect(0, 0, 384, 216);
|
|
594
1101
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
chip.textContent = "done";
|
|
600
|
-
} else if (status === "failed") {
|
|
601
|
-
chip.textContent = "failed";
|
|
602
|
-
} else {
|
|
603
|
-
chip.textContent = status; // Keep "running" or "pending"
|
|
1102
|
+
ctx.fillStyle = p.floorAlt;
|
|
1103
|
+
for (let y = 0; y < 216; y += 24) {
|
|
1104
|
+
for (let x = (y / 24) % 2 === 0 ? 0 : 12; x < 384; x += 24) {
|
|
1105
|
+
ctx.fillRect(x, y, 12, 12);
|
|
604
1106
|
}
|
|
605
1107
|
}
|
|
606
1108
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1109
|
+
ctx.fillStyle = p.wall;
|
|
1110
|
+
ctx.fillRect(12, 12, 360, 10);
|
|
1111
|
+
ctx.fillRect(12, 194, 360, 10);
|
|
1112
|
+
ctx.fillRect(12, 22, 10, 172);
|
|
1113
|
+
ctx.fillRect(362, 22, 10, 172);
|
|
1114
|
+
ctx.fillStyle = p.trim;
|
|
1115
|
+
ctx.fillRect(22, 22, 340, 2);
|
|
1116
|
+
ctx.fillRect(22, 192, 340, 2);
|
|
1117
|
+
ctx.fillRect(22, 22, 2, 170);
|
|
1118
|
+
ctx.fillRect(360, 22, 2, 170);
|
|
1119
|
+
|
|
1120
|
+
ctx.fillStyle = p.glass;
|
|
1121
|
+
this.drawWindow(ctx, 118, 22, 62, 10);
|
|
1122
|
+
this.drawWindow(ctx, 190, 22, 62, 10);
|
|
1123
|
+
|
|
1124
|
+
ctx.fillStyle = p.rug;
|
|
1125
|
+
ctx.fillRect(134, 68, 116, 74);
|
|
1126
|
+
ctx.fillStyle = p.rugLine;
|
|
1127
|
+
for (let x = 140; x < 240; x += 12) ctx.fillRect(x, 74, 2, 62);
|
|
1128
|
+
|
|
1129
|
+
this.drawMeetingTable(ctx, 146, 80);
|
|
1130
|
+
this.drawShelfWall(ctx, 34, 34);
|
|
1131
|
+
this.drawShelfWall(ctx, 318, 34);
|
|
1132
|
+
this.drawCoffeeBar(ctx, 300, 150);
|
|
1133
|
+
this.drawPrinterNook(ctx, 34, 150);
|
|
1134
|
+
this.drawLounge(ctx, 168, 150);
|
|
1135
|
+
|
|
1136
|
+
this.drawPlant(ctx, 30, 30);
|
|
1137
|
+
this.drawPlant(ctx, 346, 30);
|
|
1138
|
+
this.drawPlant(ctx, 30, 178);
|
|
1139
|
+
this.drawPlant(ctx, 346, 178);
|
|
1140
|
+
|
|
1141
|
+
this.drawDeskCluster(ctx, 154, 80, "core");
|
|
1142
|
+
this.drawDeskCluster(ctx, 270, 42, "browser");
|
|
1143
|
+
this.drawDeskCluster(ctx, 56, 42, "memory");
|
|
1144
|
+
this.drawDeskCluster(ctx, 56, 146, "cli");
|
|
1145
|
+
this.drawDeskCluster(ctx, 270, 146, "social");
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
drawReferenceImage(ctx, image, targetWidth, targetHeight) {
|
|
1149
|
+
const imageRatio = image.naturalWidth / image.naturalHeight;
|
|
1150
|
+
const targetRatio = targetWidth / targetHeight;
|
|
1151
|
+
|
|
1152
|
+
let drawWidth = targetWidth;
|
|
1153
|
+
let drawHeight = targetHeight;
|
|
1154
|
+
let offsetX = 0;
|
|
1155
|
+
let offsetY = 0;
|
|
1156
|
+
|
|
1157
|
+
if (imageRatio > targetRatio) {
|
|
1158
|
+
drawWidth = targetWidth;
|
|
1159
|
+
drawHeight = Math.round(targetWidth / imageRatio);
|
|
1160
|
+
offsetY = Math.floor((targetHeight - drawHeight) / 2);
|
|
1161
|
+
} else {
|
|
1162
|
+
drawHeight = targetHeight;
|
|
1163
|
+
drawWidth = Math.round(targetHeight * imageRatio);
|
|
1164
|
+
offsetX = Math.floor((targetWidth - drawWidth) / 2);
|
|
656
1165
|
}
|
|
657
1166
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1167
|
+
ctx.fillStyle = this.palette.floor;
|
|
1168
|
+
ctx.fillRect(0, 0, targetWidth, targetHeight);
|
|
1169
|
+
ctx.drawImage(image, offsetX, offsetY, drawWidth, drawHeight);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
drawPlant(ctx, x, y) {
|
|
1173
|
+
const p = this.palette;
|
|
1174
|
+
ctx.fillStyle = p.wood;
|
|
1175
|
+
ctx.fillRect(x, y + 8, 8, 7);
|
|
1176
|
+
ctx.fillStyle = p.plantDark;
|
|
1177
|
+
ctx.fillRect(x - 2, y + 2, 12, 8);
|
|
1178
|
+
ctx.fillStyle = p.plant;
|
|
1179
|
+
ctx.fillRect(x - 4, y, 16, 7);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
drawWindow(ctx, x, y, w, h) {
|
|
1183
|
+
const p = this.palette;
|
|
1184
|
+
ctx.fillStyle = p.trim;
|
|
1185
|
+
ctx.fillRect(x, y, w, h);
|
|
1186
|
+
ctx.fillStyle = p.glass;
|
|
1187
|
+
ctx.fillRect(x + 2, y + 2, w - 4, h - 4);
|
|
1188
|
+
ctx.fillStyle = this.hexToRgba("#ffffff", p.isDark ? 0.12 : 0.3);
|
|
1189
|
+
ctx.fillRect(x + 8, y + 2, 2, h - 4);
|
|
1190
|
+
ctx.fillRect(x + 24, y + 2, 2, h - 4);
|
|
1191
|
+
ctx.fillRect(x + 40, y + 2, 2, h - 4);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
drawMeetingTable(ctx, x, y) {
|
|
1195
|
+
const p = this.palette;
|
|
1196
|
+
ctx.fillStyle = p.shadow;
|
|
1197
|
+
ctx.fillRect(x + 4, y + 38, 92, 4);
|
|
1198
|
+
ctx.fillStyle = p.wood;
|
|
1199
|
+
ctx.fillRect(x, y, 100, 38);
|
|
1200
|
+
ctx.fillStyle = p.paper;
|
|
1201
|
+
ctx.fillRect(x + 12, y + 10, 18, 10);
|
|
1202
|
+
ctx.fillRect(x + 68, y + 10, 18, 10);
|
|
1203
|
+
ctx.fillStyle = p.chair;
|
|
1204
|
+
ctx.fillRect(x - 8, y + 6, 8, 10);
|
|
1205
|
+
ctx.fillRect(x - 8, y + 22, 8, 10);
|
|
1206
|
+
ctx.fillRect(x + 100, y + 6, 8, 10);
|
|
1207
|
+
ctx.fillRect(x + 100, y + 22, 8, 10);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
drawShelfWall(ctx, x, y) {
|
|
1211
|
+
const p = this.palette;
|
|
1212
|
+
ctx.fillStyle = p.trim;
|
|
1213
|
+
ctx.fillRect(x, y, 34, 44);
|
|
1214
|
+
ctx.fillStyle = p.wood;
|
|
1215
|
+
for (let y0 = y + 4; y0 < y + 40; y0 += 12) {
|
|
1216
|
+
ctx.fillRect(x + 2, y0, 30, 2);
|
|
1217
|
+
}
|
|
1218
|
+
ctx.fillStyle = p.warning;
|
|
1219
|
+
ctx.fillRect(x + 5, y + 7, 5, 7);
|
|
1220
|
+
ctx.fillRect(x + 12, y + 7, 4, 7);
|
|
1221
|
+
ctx.fillRect(x + 18, y + 7, 6, 7);
|
|
1222
|
+
ctx.fillRect(x + 8, y + 19, 6, 7);
|
|
1223
|
+
ctx.fillRect(x + 18, y + 19, 4, 7);
|
|
1224
|
+
ctx.fillRect(x + 12, y + 31, 5, 7);
|
|
1225
|
+
ctx.fillRect(x + 20, y + 31, 7, 7);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
drawCoffeeBar(ctx, x, y) {
|
|
1229
|
+
const p = this.palette;
|
|
1230
|
+
ctx.fillStyle = p.deskTop;
|
|
1231
|
+
ctx.fillRect(x, y, 44, 24);
|
|
1232
|
+
ctx.fillStyle = p.coffee;
|
|
1233
|
+
ctx.fillRect(x + 4, y + 4, 14, 12);
|
|
1234
|
+
ctx.fillStyle = p.paper;
|
|
1235
|
+
ctx.fillRect(x + 24, y + 5, 6, 8);
|
|
1236
|
+
ctx.fillRect(x + 32, y + 7, 5, 6);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
drawPrinterNook(ctx, x, y) {
|
|
1240
|
+
const p = this.palette;
|
|
1241
|
+
ctx.fillStyle = p.deskTop;
|
|
1242
|
+
ctx.fillRect(x, y, 42, 24);
|
|
1243
|
+
ctx.fillStyle = p.screen;
|
|
1244
|
+
ctx.fillRect(x + 7, y + 5, 20, 10);
|
|
1245
|
+
ctx.fillStyle = p.paper;
|
|
1246
|
+
ctx.fillRect(x + 12, y + 2, 10, 5);
|
|
1247
|
+
ctx.fillRect(x + 30, y + 8, 7, 5);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
drawLounge(ctx, x, y) {
|
|
1251
|
+
const p = this.palette;
|
|
1252
|
+
ctx.fillStyle = p.rug;
|
|
1253
|
+
ctx.fillRect(x, y, 48, 30);
|
|
1254
|
+
ctx.fillStyle = p.chair;
|
|
1255
|
+
ctx.fillRect(x + 4, y + 8, 14, 14);
|
|
1256
|
+
ctx.fillRect(x + 30, y + 8, 14, 14);
|
|
1257
|
+
ctx.fillStyle = p.wood;
|
|
1258
|
+
ctx.fillRect(x + 20, y + 11, 8, 8);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
drawDeskCluster(ctx, x, y, key) {
|
|
1262
|
+
const p = this.palette;
|
|
1263
|
+
const structure = this.getStructure(key);
|
|
1264
|
+
ctx.fillStyle = p.shadow;
|
|
1265
|
+
ctx.fillRect(x - 4, y + structure.h + 2, structure.w + 8, 4);
|
|
1266
|
+
ctx.fillStyle = p.deskTop;
|
|
1267
|
+
ctx.fillRect(x, y, structure.w, structure.h);
|
|
1268
|
+
ctx.fillStyle = p.desk;
|
|
1269
|
+
ctx.fillRect(x + 4, y + 4, structure.w - 8, structure.h - 8);
|
|
1270
|
+
ctx.fillStyle = p.screen;
|
|
1271
|
+
ctx.fillRect(x + 8, y + 8, structure.w - 16, 10);
|
|
1272
|
+
ctx.fillStyle = p.screenGlow;
|
|
1273
|
+
ctx.fillRect(x + 10, y + 10, structure.w - 20, 4);
|
|
1274
|
+
ctx.fillStyle = p.paper;
|
|
1275
|
+
ctx.fillRect(x + 10, y + structure.h - 12, 10, 6);
|
|
1276
|
+
ctx.fillStyle = p.chair;
|
|
1277
|
+
ctx.fillRect(x + Math.floor(structure.w / 2) - 8, y + structure.h + 2, 16, 8);
|
|
1278
|
+
if (key === "memory") {
|
|
1279
|
+
ctx.fillStyle = p.warning;
|
|
1280
|
+
ctx.fillRect(x + structure.w - 18, y + 22, 10, 6);
|
|
1281
|
+
} else if (key === "browser") {
|
|
1282
|
+
ctx.fillStyle = p.info;
|
|
1283
|
+
ctx.fillRect(x + structure.w - 18, y + 22, 10, 6);
|
|
1284
|
+
} else if (key === "social") {
|
|
1285
|
+
ctx.fillStyle = "#ec4899";
|
|
1286
|
+
ctx.fillRect(x + structure.w - 18, y + 22, 10, 6);
|
|
1287
|
+
} else if (key === "cli") {
|
|
1288
|
+
ctx.fillStyle = "#f97316";
|
|
1289
|
+
ctx.fillRect(x + structure.w - 18, y + 22, 10, 6);
|
|
1290
|
+
} else {
|
|
1291
|
+
ctx.fillStyle = p.success;
|
|
1292
|
+
ctx.fillRect(x + structure.w - 18, y + 22, 10, 6);
|
|
663
1293
|
}
|
|
664
1294
|
}
|
|
665
1295
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
</div>
|
|
679
|
-
<div class="atl-card open" id="atl-card-${fakeId}">
|
|
680
|
-
<div class="atl-card-head" data-step="${fakeId}">
|
|
681
|
-
<span class="atl-card-label">Response</span>
|
|
682
|
-
<span class="atl-card-summary" style="font-style:italic;color:var(--text-muted);">final answer</span>
|
|
683
|
-
<span class="atl-toggle">▾</span>
|
|
684
|
-
</div>
|
|
685
|
-
<div class="atl-card-body">
|
|
686
|
-
<div class="atl-response-body md-content">${renderMarkdown(content)}</div>
|
|
687
|
-
</div>
|
|
688
|
-
</div>`;
|
|
689
|
-
|
|
690
|
-
this.feed.appendChild(stepEl);
|
|
691
|
-
this.steps.set(fakeId, {
|
|
692
|
-
el: stepEl,
|
|
693
|
-
cardEl: stepEl.querySelector(`#atl-card-${fakeId}`),
|
|
694
|
-
isResponse: true,
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
stepEl.querySelector(".atl-card-head").addEventListener("click", () => {
|
|
698
|
-
stepEl.querySelector(`#atl-card-${fakeId}`).classList.toggle("open");
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
stepEl.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1296
|
+
drawStructures(ctx) {
|
|
1297
|
+
for (const structure of this.structures) {
|
|
1298
|
+
const glowSize = Math.floor(structure.glow * 7);
|
|
1299
|
+
if (glowSize > 0) {
|
|
1300
|
+
ctx.fillStyle = this.hexToRgba(structure.color, 0.18);
|
|
1301
|
+
ctx.fillRect(structure.x - glowSize, structure.y - glowSize, structure.w + glowSize * 2, structure.h + glowSize * 2);
|
|
1302
|
+
}
|
|
1303
|
+
if (structure.glow > 0.08) {
|
|
1304
|
+
ctx.fillStyle = this.hexToRgba(structure.color, 0.18 + structure.glow * 0.08);
|
|
1305
|
+
ctx.fillRect(structure.x, structure.y, structure.w, 3);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
702
1308
|
}
|
|
703
1309
|
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
1310
|
+
drawWalkways(ctx) {
|
|
1311
|
+
const core = this.getStructure("core");
|
|
1312
|
+
for (const structure of this.structures) {
|
|
1313
|
+
if (structure.key === "core") continue;
|
|
1314
|
+
const x1 = core.x + Math.floor(core.w / 2);
|
|
1315
|
+
const y1 = core.y + core.h - 2;
|
|
1316
|
+
const x2 = structure.x + Math.floor(structure.w / 2);
|
|
1317
|
+
const y2 = structure.y + Math.floor(structure.h / 2);
|
|
1318
|
+
ctx.fillStyle = this.hexToRgba(structure.color, 0.06 + structure.glow * 0.08);
|
|
1319
|
+
for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x += 6) {
|
|
1320
|
+
ctx.fillRect(x, y1, 2, 2);
|
|
1321
|
+
}
|
|
1322
|
+
for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y += 6) {
|
|
1323
|
+
ctx.fillRect(x2, y, 2, 2);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
707
1326
|
}
|
|
708
1327
|
|
|
709
|
-
|
|
710
|
-
this.
|
|
711
|
-
this.
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (empty) {
|
|
716
|
-
this.feed.appendChild(empty);
|
|
1328
|
+
drawBots(ctx, t) {
|
|
1329
|
+
const leadBounce = Math.sin(t * 0.08 + this.mainAgent.phase) > 0 ? 0 : 1;
|
|
1330
|
+
this.drawBot(ctx, this.mainAgent.x, this.mainAgent.y - leadBounce, this.mainAgent.tint, true, true);
|
|
1331
|
+
for (const helper of this.helpers) {
|
|
1332
|
+
const bounce = Math.sin(t * 0.08 + helper.phase) > 0 ? 0 : 1;
|
|
1333
|
+
this.drawBot(ctx, helper.x, helper.y - bounce, helper.tint, helper.lastActive + 80 > this.tick, false);
|
|
717
1334
|
}
|
|
718
1335
|
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
let activityTimeline = null;
|
|
722
|
-
let currentActivityRunId = null;
|
|
723
|
-
let currentActivityTimer = null;
|
|
724
|
-
let currentActivityStartTs = null;
|
|
725
|
-
|
|
726
|
-
function startRunTimer() {
|
|
727
|
-
clearInterval(currentActivityTimer);
|
|
728
|
-
currentActivityStartTs = Date.now();
|
|
729
|
-
const el = $("#atlTimer");
|
|
730
|
-
if (!el) return;
|
|
731
|
-
el.style.display = "inline-block";
|
|
732
|
-
el.textContent = "0s";
|
|
733
|
-
currentActivityTimer = setInterval(() => {
|
|
734
|
-
const s = Math.round((Date.now() - currentActivityStartTs) / 1000);
|
|
735
|
-
el.textContent = s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
|
|
736
|
-
}, 1000);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function stopRunTimer() {
|
|
740
|
-
clearInterval(currentActivityTimer);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function ensureTimeline() {
|
|
744
|
-
if (activityTimeline) return;
|
|
745
|
-
const feed = document.getElementById("activityFeed");
|
|
746
|
-
if (!feed) return;
|
|
747
|
-
activityTimeline = new ActivityTimeline(feed);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function addActivityNode(stepId, toolName, toolArgs, runId) {
|
|
751
|
-
if (runId && currentActivityRunId !== runId) return;
|
|
752
|
-
ensureTimeline();
|
|
753
|
-
activityTimeline.addNode(stepId, toolName, toolArgs);
|
|
754
|
-
const badge = $("#activityBadge");
|
|
755
|
-
if (badge) badge.classList.remove("hidden");
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function updateActivityNode(stepId, toolName, result, screenshotPath, status, runId) {
|
|
759
|
-
if (runId && currentActivityRunId !== runId) return;
|
|
760
|
-
if (activityTimeline)
|
|
761
|
-
activityTimeline.updateNode(
|
|
762
|
-
stepId,
|
|
763
|
-
toolName,
|
|
764
|
-
result,
|
|
765
|
-
screenshotPath,
|
|
766
|
-
status,
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function addActivityResponse(content, runId) {
|
|
771
|
-
if (runId && currentActivityRunId !== runId) return;
|
|
772
|
-
ensureTimeline();
|
|
773
|
-
if (content) activityTimeline.addResponse(content);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function clearActivity() {
|
|
777
|
-
if (activityTimeline) activityTimeline.clear();
|
|
778
1336
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
if (
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1337
|
+
drawBot(ctx, x, y, tint, active, isLead = false) {
|
|
1338
|
+
const p = this.palette;
|
|
1339
|
+
if (isLead) {
|
|
1340
|
+
ctx.fillStyle = this.hexToRgba(p.success, 0.18);
|
|
1341
|
+
ctx.fillRect(x - 12, y - 15, 24, 20);
|
|
1342
|
+
}
|
|
1343
|
+
ctx.fillStyle = p.shadow;
|
|
1344
|
+
ctx.fillRect(x - 8, y + 8, 16, 4);
|
|
1345
|
+
ctx.fillStyle = "#111318";
|
|
1346
|
+
ctx.fillRect(x - 7, y - 6, 14, 12);
|
|
1347
|
+
ctx.fillStyle = tint;
|
|
1348
|
+
ctx.fillRect(x - 6, y - 5, 12, 10);
|
|
1349
|
+
ctx.fillStyle = this.hexToRgba("#ffffff", 0.18);
|
|
1350
|
+
ctx.fillRect(x - 5, y - 4, 10, 2);
|
|
1351
|
+
ctx.fillStyle = p.paper;
|
|
1352
|
+
ctx.fillRect(x - 3, y - 1, 2, 2);
|
|
1353
|
+
ctx.fillRect(x + 1, y - 1, 2, 2);
|
|
1354
|
+
ctx.fillStyle = active ? p.success : p.chair;
|
|
1355
|
+
ctx.fillRect(x - 4, y + 5, 8, 3);
|
|
1356
|
+
ctx.fillRect(x - 7, y + 1, 3, 2);
|
|
1357
|
+
ctx.fillRect(x + 4, y + 1, 3, 2);
|
|
1358
|
+
ctx.fillStyle = "#111318";
|
|
1359
|
+
ctx.fillRect(x - 4, y + 8, 2, 3);
|
|
1360
|
+
ctx.fillRect(x + 2, y + 8, 2, 3);
|
|
1361
|
+
if (isLead) {
|
|
1362
|
+
ctx.fillStyle = p.success;
|
|
1363
|
+
ctx.fillRect(x - 2, y - 9, 4, 3);
|
|
785
1364
|
}
|
|
786
1365
|
}
|
|
787
|
-
}
|
|
788
1366
|
|
|
789
|
-
|
|
1367
|
+
drawPackets(ctx) {
|
|
1368
|
+
for (const packet of this.packets) {
|
|
1369
|
+
ctx.fillStyle = packet.color;
|
|
1370
|
+
ctx.fillRect(Math.round(packet.x), Math.round(packet.y), 4, 4);
|
|
1371
|
+
ctx.fillStyle = this.palette.paper;
|
|
1372
|
+
ctx.fillRect(Math.round(packet.x) + 1, Math.round(packet.y) + 1, 2, 2);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
790
1375
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
if (!data.runs || data.runs.length === 0) {
|
|
798
|
-
list.innerHTML = '<div class="activity-empty-text">No past runs</div>';
|
|
799
|
-
return;
|
|
1376
|
+
drawStatusEffects(ctx, t) {
|
|
1377
|
+
const p = this.palette;
|
|
1378
|
+
if (this.scanFlash > 0.02) {
|
|
1379
|
+
ctx.fillStyle = this.hexToRgba(p.info, Math.min(0.08, this.scanFlash * 0.06));
|
|
1380
|
+
const x = 24 + ((t * 2) % 320);
|
|
1381
|
+
ctx.fillRect(x, 20, 8, 176);
|
|
800
1382
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const dateStr =
|
|
809
|
-
d.toLocaleDateString("en-US", { month: "short", day: "numeric" }) +
|
|
810
|
-
" " +
|
|
811
|
-
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
812
|
-
|
|
813
|
-
let badgeHtml = '';
|
|
814
|
-
if (run.status === 'running') badgeHtml = '<span class="ahp-run-status running">running</span>';
|
|
815
|
-
else if (run.status === 'failed') badgeHtml = '<span class="ahp-run-status failed">failed</span>';
|
|
816
|
-
else badgeHtml = '<span class="ahp-run-status completed">done</span>';
|
|
817
|
-
|
|
818
|
-
card.innerHTML = `<div class="ahp-run-title">${escapeHtml(run.title || "Untitled")}</div><div class="ahp-run-meta">${badgeHtml}<span>${dateStr}</span></div>`;
|
|
819
|
-
card.addEventListener("click", () => {
|
|
820
|
-
$$(".ahp-run-card.active").forEach((c) => c.classList.remove("active"));
|
|
821
|
-
card.classList.add("active");
|
|
822
|
-
loadRunOnCanvas(run.id, run.title, run.status);
|
|
823
|
-
});
|
|
824
|
-
list.appendChild(card);
|
|
1383
|
+
if (this.socialPulse > 0.02) {
|
|
1384
|
+
ctx.fillStyle = this.hexToRgba("#ec4899", Math.min(0.1, this.socialPulse * 0.08));
|
|
1385
|
+
ctx.fillRect(284, 122, 72, 62);
|
|
1386
|
+
}
|
|
1387
|
+
if (this.errorFlash > 0.02) {
|
|
1388
|
+
ctx.fillStyle = this.hexToRgba(p.error, Math.min(0.1, this.errorFlash * 0.1));
|
|
1389
|
+
ctx.fillRect(0, 0, 384, 216);
|
|
825
1390
|
}
|
|
826
|
-
} catch {
|
|
827
|
-
list.innerHTML = '<div class="activity-empty-text">Failed to load</div>';
|
|
828
1391
|
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const refreshBtn = $("#activityRefreshBtn");
|
|
832
|
-
if (refreshBtn) refreshBtn.addEventListener("click", loadActivityHistory);
|
|
833
1392
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (titleEl) titleEl.textContent = runTitle || `Run ${runId}`;
|
|
839
|
-
|
|
840
|
-
// reset badge
|
|
841
|
-
const badgeEl = $("#atlRunStatus");
|
|
842
|
-
if (badgeEl) {
|
|
843
|
-
badgeEl.style.display = "inline-block";
|
|
844
|
-
badgeEl.className = "atl-run-badge " + (runStatus === "running" ? "running" : "completed");
|
|
845
|
-
badgeEl.textContent = runStatus || "completed";
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// reset timer view
|
|
849
|
-
stopRunTimer();
|
|
850
|
-
const timerEl = $("#atlTimer");
|
|
851
|
-
if (timerEl) {
|
|
852
|
-
if (runStatus === "running") {
|
|
853
|
-
// If it was already running in the background, we might not have a perfect start time,
|
|
854
|
-
// but we can just start a fresh timer from now or fetch true duration later.
|
|
855
|
-
startRunTimer();
|
|
856
|
-
} else {
|
|
857
|
-
timerEl.style.display = "none";
|
|
858
|
-
}
|
|
1393
|
+
drawScanlines(ctx) {
|
|
1394
|
+
ctx.fillStyle = this.palette.isDark ? "rgba(4, 8, 14, 0.015)" : "rgba(255, 255, 255, 0.015)";
|
|
1395
|
+
for (let y = 0; y < 216; y += 4) {
|
|
1396
|
+
ctx.fillRect(0, y, 384, 1);
|
|
859
1397
|
}
|
|
1398
|
+
}
|
|
860
1399
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1400
|
+
hexToRgba(hex, alpha) {
|
|
1401
|
+
const clean = hex.replace("#", "");
|
|
1402
|
+
const value = parseInt(clean, 16);
|
|
1403
|
+
const r = (value >> 16) & 255;
|
|
1404
|
+
const g = (value >> 8) & 255;
|
|
1405
|
+
const b = value & 255;
|
|
1406
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
864
1409
|
|
|
865
|
-
|
|
866
|
-
const empty = $("#activityEmpty");
|
|
867
|
-
if (empty) empty.style.display = "none";
|
|
1410
|
+
let pixelWorld = null;
|
|
868
1411
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
try {
|
|
876
|
-
result = step.result ? JSON.parse(step.result) : null;
|
|
877
|
-
} catch { }
|
|
878
|
-
activityTimeline.addNode(step.id, step.tool_name, toolInput);
|
|
879
|
-
activityTimeline.updateNode(
|
|
880
|
-
step.id,
|
|
881
|
-
step.tool_name,
|
|
882
|
-
result,
|
|
883
|
-
step.screenshot_path || null,
|
|
884
|
-
step.status,
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
if (data.response) activityTimeline.addResponse(data.response);
|
|
888
|
-
} catch (err) {
|
|
889
|
-
toast("Failed to load run", "error");
|
|
890
|
-
}
|
|
1412
|
+
function ensureWorld() {
|
|
1413
|
+
if (pixelWorld) return;
|
|
1414
|
+
const canvas = document.getElementById("worldCanvas");
|
|
1415
|
+
if (!canvas) return;
|
|
1416
|
+
pixelWorld = new PixelWorld(canvas);
|
|
1417
|
+
window.pixelWorld = pixelWorld;
|
|
891
1418
|
}
|
|
892
1419
|
|
|
1420
|
+
function resetWorldForNewRun() {
|
|
1421
|
+
ensureWorld();
|
|
1422
|
+
if (pixelWorld) pixelWorld.resetForNewRun();
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
ensureWorld();
|
|
1426
|
+
navigateTo(getPageFromLocation(), { push: false });
|
|
1427
|
+
|
|
893
1428
|
// ── Socket Events ──
|
|
894
1429
|
|
|
895
1430
|
socket.on("run:start", (data) => {
|
|
@@ -900,46 +1435,30 @@ socket.on("run:start", (data) => {
|
|
|
900
1435
|
backgroundRunIds.add(data.runId);
|
|
901
1436
|
return;
|
|
902
1437
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const titleEl = $("#activityRunTitle");
|
|
906
|
-
if (titleEl) titleEl.textContent = data.title || `Run ${data.runId}`;
|
|
907
|
-
|
|
908
|
-
const badgeEl = $("#atlRunStatus");
|
|
909
|
-
if (badgeEl) {
|
|
910
|
-
badgeEl.style.display = "inline-block";
|
|
911
|
-
badgeEl.className = "atl-run-badge running";
|
|
912
|
-
badgeEl.textContent = "running";
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
clearActivity();
|
|
916
|
-
ensureTimeline();
|
|
917
|
-
startRunTimer();
|
|
1438
|
+
ensureWorld();
|
|
1439
|
+
if (pixelWorld) pixelWorld.onRunStart(data);
|
|
918
1440
|
});
|
|
919
1441
|
|
|
920
1442
|
socket.on("run:thinking", (data) => {
|
|
921
1443
|
if (backgroundRunIds.has(data.runId)) return;
|
|
922
1444
|
const textEl = $("#thinkingText");
|
|
923
1445
|
if (textEl) textEl.textContent = `Thinking… (step ${data.iteration})`;
|
|
1446
|
+
ensureWorld();
|
|
1447
|
+
if (pixelWorld) pixelWorld.onThinking(data);
|
|
924
1448
|
});
|
|
925
1449
|
|
|
926
1450
|
socket.on("run:tool_start", (data) => {
|
|
927
1451
|
if (backgroundRunIds.has(data.runId)) return;
|
|
928
|
-
|
|
1452
|
+
ensureWorld();
|
|
1453
|
+
if (pixelWorld) pixelWorld.onToolStart(data);
|
|
929
1454
|
const textEl = $("#thinkingText");
|
|
930
1455
|
if (textEl) textEl.textContent = `${data.toolName}…`;
|
|
931
1456
|
});
|
|
932
1457
|
|
|
933
1458
|
socket.on("run:tool_end", (data) => {
|
|
934
1459
|
if (backgroundRunIds.has(data.runId)) return;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
data.toolName,
|
|
938
|
-
data.result,
|
|
939
|
-
data.screenshotPath,
|
|
940
|
-
data.status,
|
|
941
|
-
data.runId
|
|
942
|
-
);
|
|
1460
|
+
ensureWorld();
|
|
1461
|
+
if (pixelWorld) pixelWorld.onToolEnd(data);
|
|
943
1462
|
});
|
|
944
1463
|
|
|
945
1464
|
socket.on("run:stream", (data) => {
|
|
@@ -1007,31 +1526,12 @@ socket.on("run:complete", (data) => {
|
|
|
1007
1526
|
appendMessage("assistant", data.content);
|
|
1008
1527
|
}
|
|
1009
1528
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (currentActivityRunId === data.runId) {
|
|
1013
|
-
const badgeEl = $("#atlRunStatus");
|
|
1014
|
-
if (badgeEl) {
|
|
1015
|
-
badgeEl.className = "atl-run-badge " + (data.status || "completed");
|
|
1016
|
-
badgeEl.textContent = data.status || "completed";
|
|
1017
|
-
}
|
|
1018
|
-
stopRunTimer();
|
|
1019
|
-
|
|
1020
|
-
// Collapse old steps for cleaner view
|
|
1021
|
-
if (activityTimeline) {
|
|
1022
|
-
for (const [, info] of activityTimeline.steps) {
|
|
1023
|
-
if (!info.isResponse && info.cardEl && info.cardEl.classList.contains("open")) {
|
|
1024
|
-
const hadError = info.cardEl.querySelector(".atl-text.error");
|
|
1025
|
-
if (!hadError) info.cardEl.classList.remove("open");
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1529
|
+
ensureWorld();
|
|
1530
|
+
if (pixelWorld) pixelWorld.onRunComplete(data);
|
|
1030
1531
|
|
|
1031
1532
|
isStreaming = false;
|
|
1032
1533
|
sendBtn.disabled = false;
|
|
1033
1534
|
}
|
|
1034
|
-
setTimeout(loadActivityHistory, 100);
|
|
1035
1535
|
});
|
|
1036
1536
|
|
|
1037
1537
|
socket.on("chat:cleared", () => {
|
|
@@ -1044,8 +1544,8 @@ socket.on("run:error", (data) => {
|
|
|
1044
1544
|
if (thinking) thinking.remove();
|
|
1045
1545
|
const errMsg = data.error || "Unknown error";
|
|
1046
1546
|
appendMessage("assistant", `❌ ${errMsg}`);
|
|
1047
|
-
|
|
1048
|
-
if (
|
|
1547
|
+
ensureWorld();
|
|
1548
|
+
if (pixelWorld) pixelWorld.onRunError(data);
|
|
1049
1549
|
isStreaming = false;
|
|
1050
1550
|
sendBtn.disabled = false;
|
|
1051
1551
|
toast(errMsg, "error");
|
|
@@ -1056,27 +1556,22 @@ socket.on("run:interim", (data) => {
|
|
|
1056
1556
|
const textEl = $("#thinkingText");
|
|
1057
1557
|
if (textEl) textEl.textContent = data.message;
|
|
1058
1558
|
appendInterimMessage(data.message);
|
|
1559
|
+
ensureWorld();
|
|
1560
|
+
if (pixelWorld) pixelWorld.pushEvent("note", data.message);
|
|
1059
1561
|
});
|
|
1060
1562
|
|
|
1061
|
-
// Incoming social message → show in chat +
|
|
1563
|
+
// Incoming social message → show in chat + world visualization
|
|
1062
1564
|
socket.on("messaging:message", (data) => {
|
|
1063
1565
|
appendSocialMessage(data.platform, "user", data.content, data.senderName);
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
"send_message",
|
|
1074
|
-
{ received: true, from: data.senderName },
|
|
1075
|
-
null,
|
|
1076
|
-
"completed",
|
|
1077
|
-
);
|
|
1078
|
-
const badge = $("#activityBadge");
|
|
1079
|
-
if (badge) badge.classList.remove("hidden");
|
|
1566
|
+
ensureWorld();
|
|
1567
|
+
if (pixelWorld) pixelWorld.onMessage(data);
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
socket.on("skill:draft_created", (data) => {
|
|
1571
|
+
toast(`Draft skill created: ${data.name}`, "success");
|
|
1572
|
+
if (!$("#skillList")?.classList.contains("hidden")) {
|
|
1573
|
+
loadSkillsPage();
|
|
1574
|
+
}
|
|
1080
1575
|
});
|
|
1081
1576
|
|
|
1082
1577
|
// ── Logs Tab ──
|
|
@@ -1158,12 +1653,12 @@ if (copyLogsBtn) {
|
|
|
1158
1653
|
debugText += `[${sender}]\\n${content.trim()}\\n\\n`;
|
|
1159
1654
|
});
|
|
1160
1655
|
|
|
1161
|
-
debugText += "---
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
const title =
|
|
1656
|
+
debugText += "--- WORLD EVENT FEED ---\\n";
|
|
1657
|
+
const entries = document.querySelectorAll(".world-event-entry");
|
|
1658
|
+
entries.forEach((entry) => {
|
|
1659
|
+
const title = entry.querySelector(".world-event-tag")?.innerText || "EVENT";
|
|
1165
1660
|
const details =
|
|
1166
|
-
|
|
1661
|
+
entry.querySelector(".world-event-text")?.innerText || "No details";
|
|
1167
1662
|
debugText += `${title}\\n${details}\\n\\n`;
|
|
1168
1663
|
});
|
|
1169
1664
|
|
|
@@ -1329,6 +1824,10 @@ $("#settingsBtn").addEventListener("click", async () => {
|
|
|
1329
1824
|
try {
|
|
1330
1825
|
const backendVersion = await api("/version");
|
|
1331
1826
|
backendVersionLabel = `${backendVersion?.version || "unknown"}${backendVersion?.gitSha ? ` (${backendVersion.gitSha})` : ""}`;
|
|
1827
|
+
const vEl = $("#settingsAppVersion");
|
|
1828
|
+
if (vEl && backendVersionLabel !== "unknown") {
|
|
1829
|
+
vEl.textContent = `v${backendVersionLabel}`;
|
|
1830
|
+
}
|
|
1332
1831
|
} catch {
|
|
1333
1832
|
backendVersionLabel = "unavailable";
|
|
1334
1833
|
}
|
|
@@ -1347,6 +1846,9 @@ $("#settingsBtn").addEventListener("click", async () => {
|
|
|
1347
1846
|
$("#settingHeadlessBrowser").checked =
|
|
1348
1847
|
settings.headless_browser !== false &&
|
|
1349
1848
|
settings.headless_browser !== "false";
|
|
1849
|
+
$("#settingAutoSkillLearning").checked =
|
|
1850
|
+
settings.auto_skill_learning !== false &&
|
|
1851
|
+
settings.auto_skill_learning !== "false";
|
|
1350
1852
|
|
|
1351
1853
|
const enabledModels = Array.isArray(settings.enabled_models) ? settings.enabled_models : (meta.models || []).map(m => m.id);
|
|
1352
1854
|
|
|
@@ -1396,6 +1898,8 @@ $("#settingsBtn").addEventListener("click", async () => {
|
|
|
1396
1898
|
checkbox.type = "checkbox";
|
|
1397
1899
|
checkbox.className = "dynamic-model-checkbox";
|
|
1398
1900
|
checkbox.dataset.modelId = modelDef.id;
|
|
1901
|
+
checkbox.autocomplete = "off";
|
|
1902
|
+
checkbox.setAttribute("data-bwignore", "true");
|
|
1399
1903
|
checkbox.checked = enabledModels.includes(modelDef.id);
|
|
1400
1904
|
|
|
1401
1905
|
const span = document.createElement("span");
|
|
@@ -1442,6 +1946,7 @@ $("#saveSettings").addEventListener("click", async () => {
|
|
|
1442
1946
|
body: {
|
|
1443
1947
|
heartbeat_enabled: $("#settingHeartbeat").checked,
|
|
1444
1948
|
headless_browser: $("#settingHeadlessBrowser").checked,
|
|
1949
|
+
auto_skill_learning: $("#settingAutoSkillLearning").checked,
|
|
1445
1950
|
enabled_models: enabledModels,
|
|
1446
1951
|
default_chat_model: defaultChatModel,
|
|
1447
1952
|
default_subagent_model: defaultSubagentModel
|
|
@@ -1561,11 +2066,76 @@ async function loadMemoryPage() {
|
|
|
1561
2066
|
|
|
1562
2067
|
// Memories list
|
|
1563
2068
|
await _loadMemoriesTab(_memActiveCategory);
|
|
2069
|
+
await loadSessionRecall();
|
|
1564
2070
|
} catch (err) {
|
|
1565
2071
|
toast("Failed to load memory", "error");
|
|
1566
2072
|
}
|
|
1567
2073
|
}
|
|
1568
2074
|
|
|
2075
|
+
async function loadSessionRecall(query = "") {
|
|
2076
|
+
const container = $("#sessionRecallList");
|
|
2077
|
+
if (!container) return;
|
|
2078
|
+
container.innerHTML =
|
|
2079
|
+
'<div class="empty-state"><p>Loading session recall…</p></div>';
|
|
2080
|
+
try {
|
|
2081
|
+
const results = query
|
|
2082
|
+
? await api("/memory/conversations/search", {
|
|
2083
|
+
method: "POST",
|
|
2084
|
+
body: { query, limit: 8 },
|
|
2085
|
+
})
|
|
2086
|
+
: await api("/memory/conversations?limit=12");
|
|
2087
|
+
|
|
2088
|
+
if (!results.length) {
|
|
2089
|
+
container.innerHTML =
|
|
2090
|
+
'<div class="empty-state"><p>No matching sessions yet.</p></div>';
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
container.innerHTML = "";
|
|
2095
|
+
for (const item of results) {
|
|
2096
|
+
const card = document.createElement("div");
|
|
2097
|
+
card.className = "item-card";
|
|
2098
|
+
|
|
2099
|
+
if (item.matches) {
|
|
2100
|
+
const matches = item.matches
|
|
2101
|
+
.map(
|
|
2102
|
+
(match) => `<div style="margin-top:8px;padding:8px 10px;border:1px solid var(--border);border-radius:10px;">
|
|
2103
|
+
<div style="font-size:0.72rem;text-transform:uppercase;color:var(--text-muted);margin-bottom:4px;">${escapeHtml(match.role || "message")}</div>
|
|
2104
|
+
<div style="font-size:0.9rem;line-height:1.45;">${escapeHtml(match.excerpt || "")}</div>
|
|
2105
|
+
</div>`
|
|
2106
|
+
)
|
|
2107
|
+
.join("");
|
|
2108
|
+
|
|
2109
|
+
card.innerHTML = `
|
|
2110
|
+
<div class="item-card-header">
|
|
2111
|
+
<div>
|
|
2112
|
+
<div class="item-card-title">${escapeHtml(item.title || "Session")}</div>
|
|
2113
|
+
<div class="item-card-meta">${escapeHtml(item.source || "session")} · ${escapeHtml(item.createdAt || "")}</div>
|
|
2114
|
+
</div>
|
|
2115
|
+
<span class="badge badge-neutral">${item.matchCount || item.matches.length} match${(item.matchCount || item.matches.length) === 1 ? "" : "es"}</span>
|
|
2116
|
+
</div>
|
|
2117
|
+
${matches}
|
|
2118
|
+
`;
|
|
2119
|
+
} else {
|
|
2120
|
+
card.innerHTML = `
|
|
2121
|
+
<div class="item-card-header">
|
|
2122
|
+
<div>
|
|
2123
|
+
<div class="item-card-title">${escapeHtml(item.title || "Session")}</div>
|
|
2124
|
+
<div class="item-card-meta">${escapeHtml(item.status || "completed")} · ${escapeHtml(item.completedAt || item.createdAt || "")}</div>
|
|
2125
|
+
</div>
|
|
2126
|
+
</div>
|
|
2127
|
+
<div style="font-size:0.9rem;line-height:1.45;color:var(--text);">${escapeHtml(item.excerpt || "No excerpt available.")}</div>
|
|
2128
|
+
`;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
container.appendChild(card);
|
|
2132
|
+
}
|
|
2133
|
+
} catch {
|
|
2134
|
+
container.innerHTML =
|
|
2135
|
+
'<div class="empty-state"><p>Session recall failed to load.</p></div>';
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
|
|
1569
2139
|
async function _loadMemoriesTab(category = "") {
|
|
1570
2140
|
const container = $("#memoryList");
|
|
1571
2141
|
if (!container) return;
|
|
@@ -1700,6 +2270,15 @@ $("#memorySearchInput")?.addEventListener("keydown", (e) => {
|
|
|
1700
2270
|
if (e.key === "Enter") $("#memorySearchBtn")?.click();
|
|
1701
2271
|
});
|
|
1702
2272
|
|
|
2273
|
+
$("#sessionSearchBtn")?.addEventListener("click", async () => {
|
|
2274
|
+
const query = $("#sessionSearchInput")?.value?.trim() || "";
|
|
2275
|
+
await loadSessionRecall(query);
|
|
2276
|
+
});
|
|
2277
|
+
|
|
2278
|
+
$("#sessionSearchInput")?.addEventListener("keydown", (e) => {
|
|
2279
|
+
if (e.key === "Enter") $("#sessionSearchBtn")?.click();
|
|
2280
|
+
});
|
|
2281
|
+
|
|
1703
2282
|
// Soul save
|
|
1704
2283
|
$("#saveSoulBtn")?.addEventListener("click", async () => {
|
|
1705
2284
|
try {
|
|
@@ -1850,8 +2429,16 @@ document.querySelectorAll("[data-skills-tab]").forEach((tab) => {
|
|
|
1850
2429
|
});
|
|
1851
2430
|
});
|
|
1852
2431
|
|
|
1853
|
-
async function loadSkillStore() {
|
|
2432
|
+
async function loadSkillStore(options = {}) {
|
|
1854
2433
|
const wrap = $("#skillStore");
|
|
2434
|
+
const pageBody = wrap.closest(".page-body");
|
|
2435
|
+
const shouldPreserveState = !!options.preserveState;
|
|
2436
|
+
const previousState = {
|
|
2437
|
+
filter: wrap.dataset.storeFilter || "",
|
|
2438
|
+
pageScrollTop: pageBody ? pageBody.scrollTop : 0,
|
|
2439
|
+
panelScrollTop: wrap.scrollTop || 0,
|
|
2440
|
+
};
|
|
2441
|
+
|
|
1855
2442
|
wrap.innerHTML = '<div class="empty-state"><p>Loading store…</p></div>';
|
|
1856
2443
|
try {
|
|
1857
2444
|
const items = await api("/store");
|
|
@@ -1882,6 +2469,7 @@ async function loadSkillStore() {
|
|
|
1882
2469
|
searchInp.type = "text";
|
|
1883
2470
|
searchInp.className = "input";
|
|
1884
2471
|
searchInp.placeholder = "Search skills…";
|
|
2472
|
+
searchInp.value = previousState.filter;
|
|
1885
2473
|
searchRow.appendChild(searchInp);
|
|
1886
2474
|
wrap.appendChild(searchRow);
|
|
1887
2475
|
|
|
@@ -1939,11 +2527,12 @@ async function loadSkillStore() {
|
|
|
1939
2527
|
'<div class="empty-state"><p>No matching skills</p></div>';
|
|
1940
2528
|
}
|
|
1941
2529
|
|
|
1942
|
-
renderStore(
|
|
2530
|
+
renderStore(previousState.filter.trim().toLowerCase());
|
|
1943
2531
|
|
|
1944
|
-
searchInp.addEventListener("input", () =>
|
|
1945
|
-
|
|
1946
|
-
|
|
2532
|
+
searchInp.addEventListener("input", () => {
|
|
2533
|
+
wrap.dataset.storeFilter = searchInp.value;
|
|
2534
|
+
renderStore(searchInp.value.trim().toLowerCase());
|
|
2535
|
+
});
|
|
1947
2536
|
|
|
1948
2537
|
cardsWrap.addEventListener("click", async (e) => {
|
|
1949
2538
|
const btn = e.target.closest("[data-store-action]");
|
|
@@ -1959,12 +2548,19 @@ async function loadSkillStore() {
|
|
|
1959
2548
|
await api(`/store/${storeId}/uninstall`, { method: "DELETE" });
|
|
1960
2549
|
toast("Skill removed", "info");
|
|
1961
2550
|
}
|
|
1962
|
-
await loadSkillStore(); // refresh
|
|
2551
|
+
await loadSkillStore({ preserveState: true }); // refresh without jumping back to top
|
|
1963
2552
|
} catch (err) {
|
|
1964
2553
|
toast("Error: " + err.message, "error");
|
|
1965
2554
|
btn.disabled = false;
|
|
1966
2555
|
}
|
|
1967
2556
|
});
|
|
2557
|
+
|
|
2558
|
+
if (shouldPreserveState) {
|
|
2559
|
+
requestAnimationFrame(() => {
|
|
2560
|
+
if (pageBody) pageBody.scrollTop = previousState.pageScrollTop;
|
|
2561
|
+
wrap.scrollTop = previousState.panelScrollTop;
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
1968
2564
|
} catch (err) {
|
|
1969
2565
|
wrap.innerHTML =
|
|
1970
2566
|
'<div class="empty-state"><p>Failed to load store</p></div>';
|
|
@@ -1991,6 +2587,11 @@ async function loadSkillsPage() {
|
|
|
1991
2587
|
for (const skill of skills) {
|
|
1992
2588
|
const card = document.createElement("div");
|
|
1993
2589
|
card.className = "item-card";
|
|
2590
|
+
const badges = [
|
|
2591
|
+
`<span class="badge ${skill.enabled ? "badge-success" : "badge-neutral"}">${skill.enabled ? "Active" : "Disabled"}</span>`,
|
|
2592
|
+
];
|
|
2593
|
+
if (skill.draft) badges.push('<span class="badge badge-warning">Draft</span>');
|
|
2594
|
+
if (skill.autoCreated) badges.push('<span class="badge badge-info">Auto-learned</span>');
|
|
1994
2595
|
card.innerHTML = `
|
|
1995
2596
|
<div class="item-card-header">
|
|
1996
2597
|
<div>
|
|
@@ -1998,12 +2599,14 @@ async function loadSkillsPage() {
|
|
|
1998
2599
|
<div class="item-card-meta">${escapeHtml(skill.description)}</div>
|
|
1999
2600
|
</div>
|
|
2000
2601
|
<div class="item-card-actions">
|
|
2001
|
-
|
|
2002
|
-
<button class="btn btn-sm btn-secondary" data-action="
|
|
2003
|
-
<button class="btn btn-sm btn-
|
|
2602
|
+
${badges.join("")}
|
|
2603
|
+
<button class="btn btn-sm btn-secondary" data-action="toggleSkill" data-name="${escapeHtml(skill.name)}" data-enabled="${skill.enabled ? "true" : "false"}">${skill.enabled ? "Disable" : "Enable"}</button>
|
|
2604
|
+
<button class="btn btn-sm btn-secondary" data-action="editSkill" data-name="${escapeHtml(skill.name)}">Edit</button>
|
|
2605
|
+
<button class="btn btn-sm btn-danger" data-action="deleteSkill" data-name="${escapeHtml(skill.name)}">×</button>
|
|
2004
2606
|
</div>
|
|
2005
2607
|
</div>
|
|
2006
|
-
<div class="item-card-meta">Trigger: ${escapeHtml(skill.trigger || "N/A")} | Category: ${escapeHtml(skill.category)}</div>
|
|
2608
|
+
<div class="item-card-meta">Trigger: ${escapeHtml(skill.trigger || "N/A")} | Category: ${escapeHtml(skill.category)} | Source: ${escapeHtml(skill.source || "local")}</div>
|
|
2609
|
+
<div class="item-card-meta" style="margin-top:6px;">${escapeHtml(skill.filePath || "")}</div>
|
|
2007
2610
|
`;
|
|
2008
2611
|
container.appendChild(card);
|
|
2009
2612
|
}
|
|
@@ -2012,12 +2615,12 @@ async function loadSkillsPage() {
|
|
|
2012
2615
|
}
|
|
2013
2616
|
}
|
|
2014
2617
|
|
|
2015
|
-
window.editSkill = async (
|
|
2618
|
+
window.editSkill = async (name) => {
|
|
2016
2619
|
try {
|
|
2017
|
-
const data = await api(`/skills/${
|
|
2620
|
+
const data = await api(`/skills/${name}`);
|
|
2018
2621
|
const content = prompt("Edit skill content:", data.content);
|
|
2019
2622
|
if (content !== null) {
|
|
2020
|
-
await api(`/skills/${
|
|
2623
|
+
await api(`/skills/${name}`, { method: "PUT", body: { content } });
|
|
2021
2624
|
loadSkillsPage();
|
|
2022
2625
|
toast("Skill updated", "success");
|
|
2023
2626
|
}
|
|
@@ -2026,10 +2629,10 @@ window.editSkill = async (filename) => {
|
|
|
2026
2629
|
}
|
|
2027
2630
|
};
|
|
2028
2631
|
|
|
2029
|
-
window.deleteSkill = async (
|
|
2030
|
-
if (!confirm(`Delete skill ${
|
|
2632
|
+
window.deleteSkill = async (name) => {
|
|
2633
|
+
if (!confirm(`Delete skill ${name}?`)) return;
|
|
2031
2634
|
try {
|
|
2032
|
-
await api(`/skills/${
|
|
2635
|
+
await api(`/skills/${name}`, { method: "DELETE" });
|
|
2033
2636
|
loadSkillsPage();
|
|
2034
2637
|
toast("Skill deleted", "success");
|
|
2035
2638
|
} catch (err) {
|
|
@@ -2037,13 +2640,28 @@ window.deleteSkill = async (filename) => {
|
|
|
2037
2640
|
}
|
|
2038
2641
|
};
|
|
2039
2642
|
|
|
2643
|
+
window.toggleSkill = async (name, enabled) => {
|
|
2644
|
+
try {
|
|
2645
|
+
await api(`/skills/${name}`, {
|
|
2646
|
+
method: "PUT",
|
|
2647
|
+
body: { enabled },
|
|
2648
|
+
});
|
|
2649
|
+
loadSkillsPage();
|
|
2650
|
+
toast(enabled ? "Skill enabled" : "Skill disabled", "success");
|
|
2651
|
+
} catch (err) {
|
|
2652
|
+
toast("Failed to update skill", "error");
|
|
2653
|
+
}
|
|
2654
|
+
};
|
|
2655
|
+
|
|
2040
2656
|
// Skills event delegation
|
|
2041
2657
|
$("#skillList").addEventListener("click", (e) => {
|
|
2042
2658
|
const btn = e.target.closest("[data-action]");
|
|
2043
2659
|
if (!btn) return;
|
|
2044
2660
|
const action = btn.dataset.action;
|
|
2045
|
-
if (action === "editSkill") window.editSkill(btn.dataset.
|
|
2046
|
-
else if (action === "deleteSkill") window.deleteSkill(btn.dataset.
|
|
2661
|
+
if (action === "editSkill") window.editSkill(btn.dataset.name);
|
|
2662
|
+
else if (action === "deleteSkill") window.deleteSkill(btn.dataset.name);
|
|
2663
|
+
else if (action === "toggleSkill")
|
|
2664
|
+
window.toggleSkill(btn.dataset.name, btn.dataset.enabled !== "true");
|
|
2047
2665
|
});
|
|
2048
2666
|
|
|
2049
2667
|
$("#addSkillBtn").addEventListener("click", () => {
|