@loompad/cli 1.2.2 → 1.2.4
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/CHANGELOG.md +42 -0
- package/README.md +10 -0
- package/dist/cli/index.js +59 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/core/crew.d.ts +16 -1
- package/dist/core/crew.js +26 -2
- package/dist/core/crew.js.map +1 -1
- package/dist/core/orchestra.d.ts +36 -0
- package/dist/core/orchestra.js +154 -82
- package/dist/core/orchestra.js.map +1 -1
- package/dist/core/team-hub.d.ts +2 -1
- package/dist/core/team-hub.js +1 -1
- package/dist/core/team-hub.js.map +1 -1
- package/dist/core/team-leases.d.ts +7 -0
- package/dist/core/team-leases.js +19 -0
- package/dist/core/team-leases.js.map +1 -1
- package/dist/daemon/client.d.ts +12 -1
- package/dist/daemon/client.js +5 -2
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/routes/brain.js +2 -2
- package/dist/daemon/routes/brain.js.map +1 -1
- package/dist/daemon/routes/orchestra.js +1 -0
- package/dist/daemon/routes/orchestra.js.map +1 -1
- package/dist/daemon/routes/project-team.js +17 -0
- package/dist/daemon/routes/project-team.js.map +1 -1
- package/dist/daemon/runner.d.ts +11 -0
- package/dist/daemon/runner.js +111 -5
- package/dist/daemon/runner.js.map +1 -1
- package/dist/daemon/runtime/briefings.js +12 -1
- package/dist/daemon/runtime/briefings.js.map +1 -1
- package/dist/daemon/runtime.d.ts +9 -2
- package/dist/daemon/runtime.js +8 -4
- package/dist/daemon/runtime.js.map +1 -1
- package/dist/daemon/team-coordinator.d.ts +27 -3
- package/dist/daemon/team-coordinator.js +54 -2
- package/dist/daemon/team-coordinator.js.map +1 -1
- package/dist/daemon/team.d.ts +42 -1
- package/dist/daemon/team.js +92 -1
- package/dist/daemon/team.js.map +1 -1
- package/dist/hub/supabase-client.d.ts +4 -2
- package/dist/hub/supabase-client.js +21 -12
- package/dist/hub/supabase-client.js.map +1 -1
- package/dist/web/app.css +12 -0
- package/dist/web/app.js +140 -28
- package/dist/web/app.js.map +3 -3
- package/docs/release-notes/v1.2.3.md +29 -0
- package/docs/release-notes/v1.2.4.md +13 -0
- package/docs/teams.md +31 -0
- package/package.json +1 -1
package/dist/web/app.js
CHANGED
|
@@ -875,9 +875,36 @@
|
|
|
875
875
|
}
|
|
876
876
|
__name(copyText, "copyText");
|
|
877
877
|
|
|
878
|
+
// src/web/mode.js
|
|
879
|
+
var KEY = "loomDevMode";
|
|
880
|
+
function devMode() {
|
|
881
|
+
try {
|
|
882
|
+
return localStorage.getItem(KEY) === "1";
|
|
883
|
+
} catch (e) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
__name(devMode, "devMode");
|
|
888
|
+
function applyMode() {
|
|
889
|
+
var dev = devMode();
|
|
890
|
+
document.documentElement.classList.toggle("simple", !dev);
|
|
891
|
+
document.documentElement.classList.toggle("devmode", dev);
|
|
892
|
+
}
|
|
893
|
+
__name(applyMode, "applyMode");
|
|
894
|
+
function setDevMode(on) {
|
|
895
|
+
try {
|
|
896
|
+
localStorage.setItem(KEY, on ? "1" : "0");
|
|
897
|
+
} catch (e) {
|
|
898
|
+
}
|
|
899
|
+
applyMode();
|
|
900
|
+
}
|
|
901
|
+
__name(setDevMode, "setDevMode");
|
|
902
|
+
var SIMPLE_TABS = ["thread", "team", "fleet", "board", "brain"];
|
|
903
|
+
|
|
878
904
|
// src/web/layout.js
|
|
879
905
|
var RAIL_KEY = "loomRail";
|
|
880
906
|
function railOpen() {
|
|
907
|
+
if (document.documentElement.classList.contains("simple")) return false;
|
|
881
908
|
var v = localStorage.getItem(RAIL_KEY);
|
|
882
909
|
return v === null ? (window.innerWidth || 0) >= 1440 : v === "1";
|
|
883
910
|
}
|
|
@@ -1853,6 +1880,8 @@
|
|
|
1853
1880
|
__name(patchCfg, "patchCfg");
|
|
1854
1881
|
function renderPrefs() {
|
|
1855
1882
|
var h = '<div class="setphead">Preferences</div>';
|
|
1883
|
+
h += '<div class="sgrouph">Mode</div>';
|
|
1884
|
+
h += '<div class="prow"><div class="pl"><div class="pt">Developer mode</div><div class="pd">' + (devMode() ? "On: Orchestra, Crew, Insights and its metrics, the terminal, console, browser, the right-hand panel, MCPs and skills, and the cost meter." : "Off: Loom shows chat, your team, your agents, the board and memory. Turn it on for Orchestra, Crew, metrics, the terminal and the rest.") + '</div></div><div class="pc">' + seg("devmode", [{ v: "simple", l: "Simple" }, { v: "dev", l: "Developer" }], devMode() ? "dev" : "simple") + "</div></div>";
|
|
1856
1885
|
h += '<div class="sgrouph">Appearance</div>';
|
|
1857
1886
|
h += '<div class="prow"><div class="pl"><div class="pt">Theme</div><div class="pd">Light or dark. Open terminals repaint to match.</div></div><div class="pc">' + seg("theme", [{ v: "light", l: "Light" }, { v: "dark", l: "Dark" }, { v: "system", l: "System" }], themePref()) + "</div></div>";
|
|
1858
1887
|
h += '<div class="prow"><div class="pl"><div class="pt">Text size</div><div class="pd">Scales the whole app on this device.</div></div><div class="pc">' + seg("textsize", [{ v: "s", l: "S" }, { v: "m", l: "M" }, { v: "l", l: "L" }, { v: "xl", l: "XL" }], appearancePref("textsize", "m")) + "</div></div>";
|
|
@@ -1866,6 +1895,12 @@
|
|
|
1866
1895
|
h += '<div class="prow"><div class="pl"><div class="pt">Chime</div><div class="pd">A soft two-note sound with it.</div></div><div class="pc">' + seg("chime", [{ v: "on", l: "On" }, { v: "off", l: "Off" }], devicePref("chime", false) ? "on" : "off") + "</div></div>";
|
|
1867
1896
|
h += '<div id="projprefs"></div>';
|
|
1868
1897
|
pane.innerHTML = h;
|
|
1898
|
+
bindSeg("devmode", function(v) {
|
|
1899
|
+
setDevMode(v === "dev");
|
|
1900
|
+
toast(v === "dev" ? "Developer mode on \u2014 every tool is back" : "Simple mode \u2014 just what you need");
|
|
1901
|
+
if (state.rerender) state.rerender();
|
|
1902
|
+
renderPrefs();
|
|
1903
|
+
});
|
|
1869
1904
|
bindSeg("notifydone", function(v) {
|
|
1870
1905
|
setDevicePref("notifyDone", v === "on");
|
|
1871
1906
|
if (v === "on" && !window.loomNative && window.Notification && Notification.permission === "default") {
|
|
@@ -3373,6 +3408,10 @@
|
|
|
3373
3408
|
}
|
|
3374
3409
|
return true;
|
|
3375
3410
|
}, "needProject");
|
|
3411
|
+
if (!devMode() && devOnly(item)) {
|
|
3412
|
+
toast("That\u2019s a Developer mode tool \u2014 turn it on in Settings \u2192 Preferences");
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3376
3415
|
if (item.indexOf("tab:") === 0) {
|
|
3377
3416
|
if (needProject()) state.showTab(item.slice(4));
|
|
3378
3417
|
return;
|
|
@@ -3614,6 +3653,12 @@
|
|
|
3614
3653
|
].map(function(c) {
|
|
3615
3654
|
return { id: c[0], label: c[1], keys: c[2], icon: c[3], needsProject: c[4] };
|
|
3616
3655
|
});
|
|
3656
|
+
var DEV_COMMANDS = { orchestrate: 1, crew: 1, prompts: 1, tools: 1, "toggle-rail": 1, "toggle-terminal": 1, "toggle-browser": 1, console: 1 };
|
|
3657
|
+
function devOnly(id) {
|
|
3658
|
+
if (id.indexOf("tab:") === 0) return SIMPLE_TABS.indexOf(id.slice(4)) < 0;
|
|
3659
|
+
return !!DEV_COMMANDS[id];
|
|
3660
|
+
}
|
|
3661
|
+
__name(devOnly, "devOnly");
|
|
3617
3662
|
|
|
3618
3663
|
// src/web/palette.js
|
|
3619
3664
|
function openPalette() {
|
|
@@ -3694,14 +3739,15 @@
|
|
|
3694
3739
|
__name(shq, "shq");
|
|
3695
3740
|
var CMDS = buildCommands();
|
|
3696
3741
|
function buildCommands() {
|
|
3742
|
+
var dev = devMode();
|
|
3697
3743
|
var C = COMMANDS.filter(function(c) {
|
|
3698
|
-
return !c.needsProject || pid;
|
|
3744
|
+
return (!c.needsProject || pid) && (dev || !devOnly(c.id));
|
|
3699
3745
|
}).map(function(c) {
|
|
3700
3746
|
return { icon: c.icon, label: c.label, sub: c.keys || "", run: /* @__PURE__ */ __name(function() {
|
|
3701
3747
|
runCommand(c.id);
|
|
3702
3748
|
}, "run") };
|
|
3703
3749
|
});
|
|
3704
|
-
if (pid && state.showRail) {
|
|
3750
|
+
if (pid && state.showRail && dev) {
|
|
3705
3751
|
C.push({ icon: ICONS.files, label: "Explorer", sub: "panel", run: /* @__PURE__ */ __name(function() {
|
|
3706
3752
|
state.showRail("explorer");
|
|
3707
3753
|
}, "run") });
|
|
@@ -3714,10 +3760,10 @@
|
|
|
3714
3760
|
if (state.openSubagents) C.push({ icon: ICONS.agents, label: "Subagents", sub: "dock", run: /* @__PURE__ */ __name(function() {
|
|
3715
3761
|
state.openSubagents();
|
|
3716
3762
|
}, "run") });
|
|
3717
|
-
if (state.pid) C.push({ icon: ICONS.download, label: "Import chats from your agents", sub: "Claude, Codex, OpenCode", run: /* @__PURE__ */ __name(function() {
|
|
3718
|
-
openImportChats(state.pid);
|
|
3719
|
-
}, "run") });
|
|
3720
3763
|
}
|
|
3764
|
+
if (pid) C.push({ icon: ICONS.download, label: "Import chats from your agents", sub: "Claude, Codex, OpenCode", run: /* @__PURE__ */ __name(function() {
|
|
3765
|
+
openImportChats(pid);
|
|
3766
|
+
}, "run") });
|
|
3721
3767
|
C.push({ icon: ICONS.console, label: "Diagnostics", sub: "loom doctor", run: /* @__PURE__ */ __name(function() {
|
|
3722
3768
|
openSettingsModal("diagnostics");
|
|
3723
3769
|
}, "run") });
|
|
@@ -4973,7 +5019,7 @@
|
|
|
4973
5019
|
if (!n && view.brainKind !== k) return;
|
|
4974
5020
|
chips += '<button class="bkind bk-' + k + (view.brainKind === k ? " on" : "") + '" data-kind="' + k + '">' + k + ' <span class="kn">' + n + "</span></button>";
|
|
4975
5021
|
});
|
|
4976
|
-
var head = '<div class="bhead">' + brainSwitchHtml() + '<div class="bkinds">' + chips + '</div><div class="bio"><button type="button" class="btn xs ghost" id="bexport" title="download what this project knows, as JSON">' + ICONS.download + 'Export</button><button type="button" class="btn xs ghost" id="bimport" title="bring in a brain exported from Loom (duplicates are skipped)">' + ICONS.plus + 'Import</button><input type="file" id="bimportf" accept="application/json,.json" hidden></div></div><div class="bsort"><span>Sort</span><button type="button" data-bsort="new" class="' + (state.brainSort !== "used" ? "on" : "") + '">Newest</button><button type="button" data-bsort="used" class="' + (state.brainSort === "used" ? "on" : "") + '" title="how often each memory reached an agent\u2019s prompt">Most used</button><span
|
|
5022
|
+
var head = '<div class="bhead">' + brainSwitchHtml() + '<div class="bkinds">' + chips + '</div><div class="bio"><button type="button" class="btn xs ghost" id="bexport" title="download what this project knows, as JSON">' + ICONS.download + 'Export</button><button type="button" class="btn xs ghost" id="bimport" title="bring in a brain exported from Loom (duplicates are skipped)">' + ICONS.plus + 'Import</button><input type="file" id="bimportf" accept="application/json,.json" hidden></div></div><div class="bsort"><span>Sort</span><button type="button" data-bsort="new" class="' + (state.brainSort !== "used" ? "on" : "") + '">Newest</button><button type="button" data-bsort="used" class="' + (state.brainSort === "used" ? "on" : "") + '" title="how often each memory reached an agent\u2019s prompt">Most used</button><span class="bview devonly"><span>View</span><button type="button" data-bgv="list" class="' + (!state.brainGraph ? "on" : "") + '">List</button><button type="button" data-bgv="graph" class="' + (state.brainGraph ? "on" : "") + '" title="memories joined by the files and symbols they share">Graph</button></span></div>';
|
|
4977
5023
|
if (continuity.enabled) head += '<div class="bsec">Native continuity <button class="lnk" id="continuity-inspect">Inspect packets and delivery</button></div><div class="bsec">Legacy memories below are retained for compatibility and are not injected by native continuity.</div>';
|
|
4978
5024
|
var shown = view.brainKind ? memories.filter(function(x) {
|
|
4979
5025
|
return x.kind === view.brainKind;
|
|
@@ -4986,7 +5032,7 @@
|
|
|
4986
5032
|
var list;
|
|
4987
5033
|
if (!shown.length) {
|
|
4988
5034
|
list = '<div class="bempty">' + (memories.length ? "" : emptyArt("brain")) + (memories.length ? "No " + esc(view.brainKind) + " memories yet." : continuity.enabled ? "Your original user messages are protected automatically. Review packets and source-backed checkpoints through native continuity diagnostics." : "Nothing learned yet. As agents finish turns, Loom reads each one and records what's worth keeping \u2014 constraints, decisions, and the failures worth not repeating. Add a decision below to seed it, or let an agent take a turn.") + "</div>";
|
|
4989
|
-
} else if (state.brainGraph) {
|
|
5035
|
+
} else if (state.brainGraph && devMode()) {
|
|
4990
5036
|
list = '<div class="bgraph" id="bgraph"></div>';
|
|
4991
5037
|
} else {
|
|
4992
5038
|
list = '<div class="bmems">' + shown.map(function(x) {
|
|
@@ -5007,7 +5053,7 @@
|
|
|
5007
5053
|
}).join("") + "</div>" : '<div class="bempty sm">No native ADE memory found (CLAUDE.md, AGENTS.md, .kiro/steering). Loom reads these but never writes to them.</div>';
|
|
5008
5054
|
el.innerHTML = '<div class="pane-inner brain">' + head + seed + '<div id="bconflicts"></div>' + list + src + "</div>";
|
|
5009
5055
|
wireBrainSwitch(el);
|
|
5010
|
-
if (state.brainGraph && shown.length) view.drawMemGraph(document.getElementById("bgraph"), shown, usage);
|
|
5056
|
+
if (state.brainGraph && devMode() && shown.length) view.drawMemGraph(document.getElementById("bgraph"), shown, usage);
|
|
5011
5057
|
Array.prototype.forEach.call(el.querySelectorAll("[data-bgv]"), function(b) {
|
|
5012
5058
|
b.onclick = function() {
|
|
5013
5059
|
state.brainGraph = b.getAttribute("data-bgv") === "graph";
|
|
@@ -5913,7 +5959,7 @@
|
|
|
5913
5959
|
var lastSend = Promise.resolve();
|
|
5914
5960
|
var pendingSubmission;
|
|
5915
5961
|
function send() {
|
|
5916
|
-
if (state.cmode === "orch") return view.sendOrchestra();
|
|
5962
|
+
if (state.cmode === "orch" && devMode()) return view.sendOrchestra();
|
|
5917
5963
|
var box = document.getElementById("box");
|
|
5918
5964
|
var text = (box.value || "").trim();
|
|
5919
5965
|
if (!text && !view.attach.length) return;
|
|
@@ -7076,6 +7122,7 @@
|
|
|
7076
7122
|
}
|
|
7077
7123
|
} catch (e) {
|
|
7078
7124
|
}
|
|
7125
|
+
if (!devMode()) state.cmode = "chat";
|
|
7079
7126
|
view.setComposerMode(state.cmode || "chat");
|
|
7080
7127
|
refreshSkillCount();
|
|
7081
7128
|
var micB = document.getElementById("micbtn");
|
|
@@ -7242,7 +7289,7 @@
|
|
|
7242
7289
|
}
|
|
7243
7290
|
var mode = permOf(cur), cell = permProfile(cur.kind).modes[mode] || {};
|
|
7244
7291
|
pc.style.display = "";
|
|
7245
|
-
pc.className = "cperm " + mode;
|
|
7292
|
+
pc.className = "cperm devonly " + mode;
|
|
7246
7293
|
pc.setAttribute("data-mode", mode);
|
|
7247
7294
|
pc.title = "permissions \xB7 " + (cell.label || PERM_NAMES[mode]) + " \u2014 click to change";
|
|
7248
7295
|
pc.setAttribute("aria-label", "permissions: " + (PERM_NAMES[mode] || mode));
|
|
@@ -9104,7 +9151,7 @@
|
|
|
9104
9151
|
return a ? a.kind : null;
|
|
9105
9152
|
}, "kindIn");
|
|
9106
9153
|
var h = hue(String(info.id || info.name || "?"));
|
|
9107
|
-
var out = '<div class="fproj" data-fproj="' + esc(info.id) + '"><div class="fph"><span class="fpg" style="background:color-mix(in srgb, hsl(' + h + ",60%,50%) 18%, transparent);color:hsl(" + h + ',60%,var(--agent-l))">' + esc(String(info.name || info.id || "?").slice(0, 1)) + '</span><span class="fpn">' + esc(info.name || info.id) + "</span>" + (info.id === view.pid ? '<span class="fcur">this project</span>' : "") + '<span class="fpm">' + (busy ? busy + " working" : "idle") + (holder ? " \xB7 baton
|
|
9154
|
+
var out = '<div class="fproj" data-fproj="' + esc(info.id) + '"><div class="fph"><span class="fpg" style="background:color-mix(in srgb, hsl(' + h + ",60%,50%) 18%, transparent);color:hsl(" + h + ',60%,var(--agent-l))">' + esc(String(info.name || info.id || "?").slice(0, 1)) + '</span><span class="fpn">' + esc(info.name || info.id) + "</span>" + (info.id === view.pid ? '<span class="fcur">this project</span>' : "") + '<span class="fpm">' + (busy ? busy + " working" : "idle") + (holder ? '<span class="devonly"> \xB7 baton ' + esc(holder.id) + "</span>" : "") + '</span><span class="spacer"></span>' + (info.id !== view.pid ? '<button class="btn ghost xs" type="button" data-fopen="' + esc(info.id) + '">Open</button>' : "") + "</div>" + agents.map(function(a) {
|
|
9108
9155
|
return fleetAgentRow(info, a);
|
|
9109
9156
|
}).join("");
|
|
9110
9157
|
var o = pr.orchestra;
|
|
@@ -9135,7 +9182,7 @@
|
|
|
9135
9182
|
var lbl = agentLabel(a.kind, a.id), last = a.last;
|
|
9136
9183
|
var sub = [a.id !== lbl ? a.id : "", a.role || ""].filter(Boolean).join(" \xB7 ");
|
|
9137
9184
|
var chatName = a.chatTitle || (a.chat === "main" ? "Main thread" : a.chat);
|
|
9138
|
-
return '<div class="frow' + (a.busy ? " busy" : "") + '" data-fagent="' + esc(a.id) + '"><span class="fg">' + agentGlyph(a.kind, a.id) + '</span><span class="fn"><b>' + esc(lbl) + "</b>" + (sub ? "<small>" + esc(sub) + "</small>" : "") + '</span><span class="fs">' + (a.busy ? '<span class="fspin"></span>working' + (a.since ? " \xB7 " + fleetSince(a.since) : "") : '<span class="fidle"></span>idle') + '</span><span class="fa">' + (a.chat ? '<button class="fthr" type="button" data-fchat="' + esc(a.chat) + '" data-fpid="' + esc(info.id) + '" title="open this thread">' + ICONS.thread + "<span>" + esc(chatName) + "</span></button>" : "") + (last && last.line ? '<span class="fline">' + esc(last.line) + '<span class="ft">' + rel(last.ts) + "</span></span>" : a.chat ? "" : '<span class="fline">no activity yet</span>') + '</span><span class="fb">' + (a.holdsBaton ? '<span class="fbaton" title="holds the baton">baton</span>' : "") + permBadge(a.permissions || "auto", void 0, a.kind) + "</span></div>";
|
|
9185
|
+
return '<div class="frow' + (a.busy ? " busy" : "") + '" data-fagent="' + esc(a.id) + '"><span class="fg">' + agentGlyph(a.kind, a.id) + '</span><span class="fn"><b>' + esc(lbl) + "</b>" + (sub ? "<small>" + esc(sub) + "</small>" : "") + '</span><span class="fs">' + (a.busy ? '<span class="fspin"></span>working' + (a.since ? " \xB7 " + fleetSince(a.since) : "") : '<span class="fidle"></span>idle') + '</span><span class="fa">' + (a.chat ? '<button class="fthr" type="button" data-fchat="' + esc(a.chat) + '" data-fpid="' + esc(info.id) + '" title="open this thread">' + ICONS.thread + "<span>" + esc(chatName) + "</span></button>" : "") + (last && last.line ? '<span class="fline">' + esc(last.line) + '<span class="ft">' + rel(last.ts) + "</span></span>" : a.chat ? "" : '<span class="fline">no activity yet</span>') + '</span><span class="fb devonly">' + (a.holdsBaton ? '<span class="fbaton" title="holds the baton">baton</span>' : "") + permBadge(a.permissions || "auto", void 0, a.kind) + "</span></div>";
|
|
9139
9186
|
}
|
|
9140
9187
|
__name(fleetAgentRow, "fleetAgentRow");
|
|
9141
9188
|
function openFleetThread(toPid, chat) {
|
|
@@ -9181,6 +9228,18 @@
|
|
|
9181
9228
|
drawTeamBlock(true);
|
|
9182
9229
|
});
|
|
9183
9230
|
wireTeamDeploys(host);
|
|
9231
|
+
var ao = host.querySelector("[data-tautoon]");
|
|
9232
|
+
if (ao) ao.onclick = function() {
|
|
9233
|
+
ao.disabled = true;
|
|
9234
|
+
api("/api/projects/" + view.pid + "/team/auto", { method: "POST", body: "{}" }).then(function(j) {
|
|
9235
|
+
teamAuto[view.pid] = j;
|
|
9236
|
+
toast(j.wrotePolicy ? "On \u2014 loom.team.json written: commit and merge it so teammates' runners can help" : "On \u2014 teammates' goals can use your agents now");
|
|
9237
|
+
drawTeamBlock(true);
|
|
9238
|
+
}).catch(function(err) {
|
|
9239
|
+
ao.disabled = false;
|
|
9240
|
+
toast(err.message);
|
|
9241
|
+
});
|
|
9242
|
+
};
|
|
9184
9243
|
Array.prototype.forEach.call(host.querySelectorAll("[data-tadopt]"), function(b) {
|
|
9185
9244
|
b.onclick = function() {
|
|
9186
9245
|
var n = Number(b.getAttribute("data-tadopt")), owner = b.getAttribute("data-towner") || "a teammate";
|
|
@@ -14842,26 +14901,26 @@
|
|
|
14842
14901
|
}));
|
|
14843
14902
|
var share = p && p.costUsd > 0 && total > 0 ? Math.min(100, Math.round(p.costUsd / total * 100)) : 0;
|
|
14844
14903
|
var gh = state.github, ghSeg = "";
|
|
14845
|
-
if (gh && gh.connected) ghSeg = '<span class="sit ghok" title="GitHub connected as ' + esc(gh.user || "") + '">' + ICONS.github + " " + esc(gh.user || "connected") + "</span>";
|
|
14846
|
-
else if (gh && gh.installed) ghSeg = '<button class="sit ghconnect" id="ghconnect" title="sign in to GitHub in a terminal">' + ICONS.github + " Connect GitHub</button>";
|
|
14904
|
+
if (gh && gh.connected) ghSeg = '<span class="sit ghok devonly" title="GitHub connected as ' + esc(gh.user || "") + '">' + ICONS.github + " " + esc(gh.user || "connected") + "</span>";
|
|
14905
|
+
else if (gh && gh.installed) ghSeg = '<button class="sit ghconnect devonly" id="ghconnect" title="sign in to GitHub in a terminal">' + ICONS.github + " Connect GitHub</button>";
|
|
14847
14906
|
var lp = state.loompad, lpUp = !!(lp && lp.up);
|
|
14848
|
-
var lpSeg = '<button class="sit lppill' + (lpUp ? " on" : "") + '" id="lppill" title="' + (lpUp ? "LoomPad voice backend connected" + (lp && lp.brain ? " \xB7 brain " + esc(String(lp.brain)) : "") + " \u2014 the pad can speak" : "LoomPad voice backend offline \u2014 start it so the pad can speak") + '"><span class="sdot' + (lpUp ? "" : " off") + '"></span>LoomPad' + (lpUp ? "" : " offline") + "</button>";
|
|
14907
|
+
var lpSeg = '<button class="sit lppill devonly' + (lpUp ? " on" : "") + '" id="lppill" title="' + (lpUp ? "LoomPad voice backend connected" + (lp && lp.brain ? " \xB7 brain " + esc(String(lp.brain)) : "") + " \u2014 the pad can speak" : "LoomPad voice backend offline \u2014 start it so the pad can speak") + '"><span class="sdot' + (lpUp ? "" : " off") + '"></span>LoomPad' + (lpUp ? "" : " offline") + "</button>";
|
|
14849
14908
|
var gd = state.gitDel, gdSeg = "";
|
|
14850
14909
|
if (p && gd && gd.pid === p.id) {
|
|
14851
14910
|
var gi = gitDeliveryInfo(gd.mode);
|
|
14852
|
-
gdSeg = '<button class="sit gitdel ' + esc(gi.mode) + '" id="gitdel" type="button" aria-haspopup="menu" title="git delivery for ' + esc(p.name || p.id) + ": " + esc(gi.name) + ' \u2014 click to change">' + gi.icon + esc(gi.short) + "</button>";
|
|
14911
|
+
gdSeg = '<button class="sit gitdel devonly ' + esc(gi.mode) + '" id="gitdel" type="button" aria-haspopup="menu" title="git delivery for ' + esc(p.name || p.id) + ": " + esc(gi.name) + ' \u2014 click to change">' + gi.icon + esc(gi.short) + "</button>";
|
|
14853
14912
|
}
|
|
14854
14913
|
el.innerHTML = // a project's live socket when one is open; otherwise whether the daemon itself answers
|
|
14855
14914
|
(function() {
|
|
14856
14915
|
var up = p ? state.wsLive : state.daemonUp !== false;
|
|
14857
14916
|
return '<span class="sit"><span class="sdot' + (up ? "" : " off") + '"></span>' + (up ? "live" : "offline") + "</span>";
|
|
14858
|
-
})() + '<span class="sit">' + esc(location.host) + "</span>" + (p ? '<span class="sit">baton ' + esc(p.holder || "\u2014") + "</span>" : "") + (p && p.costUsd > 0 ? (function() {
|
|
14917
|
+
})() + '<span class="sit devonly">' + esc(location.host) + "</span>" + (p ? '<span class="sit devonly">baton ' + esc(p.holder || "\u2014") + "</span>" : "") + (p && p.costUsd > 0 ? (function() {
|
|
14859
14918
|
var b = budgetFor(p.id);
|
|
14860
14919
|
var cls = b ? p.costUsd >= b ? " over" : p.costUsd >= b * 0.8 ? " warn" : "" : "";
|
|
14861
14920
|
var w = b ? Math.min(100, Math.round(p.costUsd / b * 100)) : share;
|
|
14862
14921
|
var label = b ? money(p.costUsd) + " / $" + b.toFixed(2) : money(p.costUsd) + " \xB7 " + share + "% of \u03A3";
|
|
14863
14922
|
var tip = b ? "spend vs budget \u2014 click to adjust" : "usage breakdown \u2014 click to set a budget";
|
|
14864
|
-
return '<button class="sit usagepill' + cls + '" id="usagepill" title="' + tip + '"><span class="meter"><i style="width:' + w + '%"></i></span>' + label + "</button>";
|
|
14923
|
+
return '<button class="sit usagepill devonly' + cls + '" id="usagepill" title="' + tip + '"><span class="meter"><i style="width:' + w + '%"></i></span>' + label + "</button>";
|
|
14865
14924
|
})() : "") + '<span class="spacer"></span>' + // A newer Loom is worth one quiet pill, not a banner: it opens the
|
|
14866
14925
|
// Updates section, where the button says exactly what it will run.
|
|
14867
14926
|
(state.update && state.update.behindRelease ? '<button class="sit updready" id="updready" title="Loom ' + esc(state.update.latest) + ' is out \u2014 open Updates">' + ICONS.up + " " + esc(state.update.latest) + "</button>" : "") + lpSeg + branchSeg() + gdSeg + ghSeg + (busy ? '<span class="sit" style="color:var(--live)">' + busy + " working</span>" : "") + '<span class="sit">' + (state.projects || []).length + " project" + ((state.projects || []).length === 1 ? "" : "s") + "</span>" + (total > 0 ? '<span class="sit">\u03A3 ' + money(total) + "</span>" : "");
|
|
@@ -16710,11 +16769,11 @@
|
|
|
16710
16769
|
// business one pixel from Interrupt) and Interrupt went into the
|
|
16711
16770
|
// composer. What's left beside the panel toggle is the panel toggle:
|
|
16712
16771
|
// this strip is about the window, not about the turn.
|
|
16713
|
-
desktop ? "" : '<button id="brainbtn" class="iconbtn" title="unified memory">' + ICONS.memory + '</button><button id="treebtn" class="iconbtn" title="working tree">' + ICONS.tree + '</button><button id="routebtn" class="iconbtn" title="routes">' + ICONS.route + '</button><button id="orchbtn" class="iconbtn" title="orchestra">' + ICONS.orchestra + '</button><button id="crewbtn" class="iconbtn" title="crew \xB7 agents with roles on one goal">' + ICONS.team + '</button><button id="fleetbtn" class="iconbtn" title="fleet \xB7 what every agent is doing">' + ICONS.fleet + '</button><button class="apbadge" id="apbadge" type="button" style="display:none"></button>'
|
|
16772
|
+
desktop ? "" : '<button id="brainbtn" class="iconbtn" title="unified memory">' + ICONS.memory + '</button><button id="treebtn" class="iconbtn devonly" title="working tree">' + ICONS.tree + '</button><button id="routebtn" class="iconbtn devonly" title="routes">' + ICONS.route + '</button><button id="orchbtn" class="iconbtn devonly" title="orchestra">' + ICONS.orchestra + '</button><button id="crewbtn" class="iconbtn devonly" title="crew \xB7 agents with roles on one goal">' + ICONS.team + '</button><button id="fleetbtn" class="iconbtn" title="fleet \xB7 what every agent is doing">' + ICONS.fleet + '</button><button class="apbadge" id="apbadge" type="button" style="display:none"></button>'
|
|
16714
16773
|
);
|
|
16715
|
-
var composerHtml = '<div class="composer" id="composerwrap"><form class="cbox" id="cform"><div class="cmenu" id="cmenu" style="display:none"></div><div class="cchips" id="cchips" style="display:none"></div><div class="cqueue" id="cqueue" style="display:none"></div><textarea id="box" class="cinput" rows="2" aria-label="message" placeholder="Message… @ for files, / for actions" autocomplete="off"></textarea><div class="cskillsug" id="cskillsug" style="display:none"></div><div class="cpanel" id="cpanel" style="display:none"></div><div class="corch" id="corch" style="display:none"></div><div class="crow"><div class="cmode" id="cmode" role="tablist" aria-label="composer mode"><button type="button" role="tab" data-cmode="chat" title="talk to one agent">' + ICONS.chat + 'Chat</button><button type="button" role="tab" data-cmode="orch" title="one agent plans, many work in parallel">' + ICONS.orchestra + 'Orchestrate</button></div><button class="ctool iconly" id="attach" type="button" title="attach an image or file" aria-label="attach a file">' + ICONS.plus + '</button><span class="cpick" id="cpick"><button class="cagent" id="cagent" type="button" title="who runs this turn \u2014 AUTO routes it, or pick an agent" aria-label="who runs this turn"><span class="cadot" id="cadot"></span><span class="can">agent</span><span class="cchev">' + ICONS.chevron + '</span></button><button class="ctool" id="modelpick" type="button" title="pick a model" aria-label="pick a model"><span class="cmodel" id="cmodellabel">model</span><span class="cchev">' + ICONS.updown + '</span></button></span><button class="cperm" id="cperm" type="button" aria-haspopup="menu" style="display:none"></button><span class="cctx" id="cctx" style="display:none"></span><button class="cslot" id="morebtn" type="button" aria-haspopup="menu" aria-expanded="false" title="MCPs, skills and more"><span class="cslotico">' + ICONS.dots + '</span><span class="cslotlbl">More</span><span class="skcount" id="skcount" style="display:none">0</span></button><button class="cslot" id="micbtn" type="button" aria-label="hold to talk" title="hold to talk \u2014 transcribed by LOOM_STT_CMD on the daemon, or by this browser when that isn\u2019t set"><span class="cslotico">' + ICONS.mic + '</span></button><button class="cprompt" id="promptbtn" type="button" aria-haspopup="dialog" title="prompts \u2014 saved and recent (' + KMOD + '\u21E7V)">' + ICONS.clipboard + '<span class="cslotlbl">Prompts</span><kbd>' + KMOD + '\u21E7V</kbd></button><span style="flex:1"></span><span class="ctok" id="ctok" aria-live="off"></span><span class="ckhint" aria-hidden="true"><kbd>\u23CE</kbd> send \xB7 <kbd>\u21E7\u23CE</kbd> new line</span><span class="csend"><button class="cplan" id="planbtn" type="button" role="switch" aria-checked="false" title="plan mode \u2014 write a plan, change no code"><span class="ptrack"><i></i></span><span class="cplanlbl">Plan</span></button><button class="sendbtn" id="send" type="submit" title="send" aria-label="send">' + ICONS.up + '</button><button class="sendbtn orchsend" id="orchsend" type="button" title="plan this goal and run it in parallel" style="display:none">' + ICONS.orchestra + 'Orchestrate</button><button class="sendbtn stopbtn" id="stop" type="button" title="interrupt" aria-label="interrupt" style="display:none">' + ICONS.stop + '</button></span></div><input type="file" id="cfile" multiple style="display:none"></form><div class="hint" id="hint"></div></div>';
|
|
16774
|
+
var composerHtml = '<div class="composer" id="composerwrap"><form class="cbox" id="cform"><div class="cmenu" id="cmenu" style="display:none"></div><div class="cchips" id="cchips" style="display:none"></div><div class="cqueue" id="cqueue" style="display:none"></div><textarea id="box" class="cinput" rows="2" aria-label="message" placeholder="Message… @ for files, / for actions" autocomplete="off"></textarea><div class="cskillsug" id="cskillsug" style="display:none"></div><div class="cpanel" id="cpanel" style="display:none"></div><div class="corch" id="corch" style="display:none"></div><div class="crow"><div class="cmode devonly" id="cmode" role="tablist" aria-label="composer mode"><button type="button" role="tab" data-cmode="chat" title="talk to one agent">' + ICONS.chat + 'Chat</button><button type="button" role="tab" data-cmode="orch" title="one agent plans, many work in parallel">' + ICONS.orchestra + 'Orchestrate</button></div><button class="ctool iconly" id="attach" type="button" title="attach an image or file" aria-label="attach a file">' + ICONS.plus + '</button><span class="cpick" id="cpick"><button class="cagent" id="cagent" type="button" title="who runs this turn \u2014 AUTO routes it, or pick an agent" aria-label="who runs this turn"><span class="cadot" id="cadot"></span><span class="can">agent</span><span class="cchev">' + ICONS.chevron + '</span></button><button class="ctool devonly" id="modelpick" type="button" title="pick a model" aria-label="pick a model"><span class="cmodel" id="cmodellabel">model</span><span class="cchev">' + ICONS.updown + '</span></button></span><button class="cperm devonly" id="cperm" type="button" aria-haspopup="menu" style="display:none"></button><span class="cctx devonly" id="cctx" style="display:none"></span><button class="cslot devonly" id="morebtn" type="button" aria-haspopup="menu" aria-expanded="false" title="MCPs, skills and more"><span class="cslotico">' + ICONS.dots + '</span><span class="cslotlbl">More</span><span class="skcount" id="skcount" style="display:none">0</span></button><button class="cslot" id="micbtn" type="button" aria-label="hold to talk" title="hold to talk \u2014 transcribed by LOOM_STT_CMD on the daemon, or by this browser when that isn\u2019t set"><span class="cslotico">' + ICONS.mic + '</span></button><button class="cprompt devonly" id="promptbtn" type="button" aria-haspopup="dialog" title="prompts \u2014 saved and recent (' + KMOD + '\u21E7V)">' + ICONS.clipboard + '<span class="cslotlbl">Prompts</span><kbd>' + KMOD + '\u21E7V</kbd></button><span style="flex:1"></span><span class="ctok" id="ctok" aria-live="off"></span><span class="ckhint" aria-hidden="true"><kbd>\u23CE</kbd> send \xB7 <kbd>\u21E7\u23CE</kbd> new line</span><span class="csend"><button class="cplan" id="planbtn" type="button" role="switch" aria-checked="false" title="plan mode \u2014 write a plan, change no code"><span class="ptrack"><i></i></span><span class="cplanlbl">Plan</span></button><button class="sendbtn" id="send" type="submit" title="send" aria-label="send">' + ICONS.up + '</button><button class="sendbtn orchsend" id="orchsend" type="button" title="plan this goal and run it in parallel" style="display:none">' + ICONS.orchestra + 'Orchestrate</button><button class="sendbtn stopbtn" id="stop" type="button" title="interrupt" aria-label="interrupt" style="display:none">' + ICONS.stop + '</button></span></div><input type="file" id="cfile" multiple style="display:none"></form><div class="hint" id="hint"></div></div>';
|
|
16716
16775
|
if (desktop) {
|
|
16717
|
-
mount.innerHTML = '<div class="panel"><div class="tabstrip" id="tabstrip"><button id="sbbtn" class="iconbtn sbbtn" type="button" title="projects and threads" aria-label="show projects and threads">' + ICONS.panelRight + '</button><span id="tabsbox" style="display:contents"></span><span class="spacer"></span><button class="apbadge" id="apbadge" type="button" style="display:none"></button><button id="invitebtn" class="btn outline sm invitebtn" type="button" title="invite a teammate \u2014 one link sets up the team, this repo and their agents">' + ICONS.team + '<span>Invite</span></button><button id="termbtn" class="iconbtn" title="toggle terminal (\u2303`)">' + ICONS.terminal + '</button><button id="phonebtn" class="iconbtn" title="connect a phone" aria-label="connect a phone">' + ICONS.phone + '</button><button id="consolebtn" class="iconbtn" title="console \xB7 errors and logs">' + ICONS.console + '<span class="errdot" id="errdot"></span></button><button id="browserbtn" class="iconbtn" title="browser \xB7 live page and Playwright specs">' + ICONS.globe + '</button><button id="subagentsbtn" class="iconbtn" title="subagents \xB7 every agent an orchestrator or crew put to work">' + ICONS.agents + '</button><button id="railbtn" class="iconbtn" title="toggle right panel">' + ICONS.panelRight + "</button>" + headerActions + '</div><div class="paneswrap"><div class="mainpane" id="mainpane"><div class="pane scroll" id="pane-thread"><div id="agenthead" class="agenthead" style="display:none"></div><div id="routebar"></div><div id="feed">' + LOADER + '</div><div id="feedlive" aria-live="polite"></div><button type="button" class="jumpnew" id="jumpnew">' + ICONS.arrowDown + 'Latest</button></div><div class="pane scroll" id="pane-brain" style="display:none">' + LOADER + '</div><div class="pane scroll" id="pane-observatory" style="display:none">' + LOADER + '</div><div class="pane scroll" id="pane-board" style="display:none"></div><div class="pane scroll" id="pane-orchestra" style="display:none"></div><div class="pane scroll" id="pane-crew" style="display:none"></div><div class="pane scroll" id="pane-fleet" style="display:none"></div><div class="pane scroll" id="pane-team" style="display:none"><div class="teampane" id="fteam"></div></div>' + composerHtml + '</div><div class="dockpane" id="dockpane"><div class="rz rz-dock" id="rz-dock" title="drag to resize"></div><div class="dockhead" id="dockhead"><div class="docktabs" id="docktabs" role="tablist"></div><button id="dockclose" class="iconbtn" title="close every tab">' + ICONS.x + '</button></div><div class="pane scroll" id="pane-changes">' + LOADER + '</div></div></div><div class="termdock" id="termdock"><div class="termresize" id="termresize"></div><div class="termtabs"><span id="termtabs" style="display:contents"></span><button id="termadd" class="iconbtn" title="new terminal">' + ICONS.plus + '</button><span class="spacer"></span><span class="termfind" id="termfind" hidden><input id="termq" placeholder="Find in terminal" spellcheck="false" autocomplete="off" aria-label="find in terminal"><span id="termqn" class="termqn"></span></span><button id="termsearch" class="iconbtn" title="Find in this terminal">' + ICONS.search + '</button><button id="termclear" class="iconbtn" title="Clear this terminal">' + ICONS.trash + '</button><button id="termhide" class="iconbtn" title="hide terminal">' + ICONS.x + '</button></div><div class="termpanes" id="termpanes"><div class="conwrap" id="conwrap"><div class="conbar"><span class="lvl on" data-lvl="all">all</span><span class="lvl" data-lvl="error">errors</span><span class="lvl" data-lvl="warn">warnings</span><select id="conscope" class="consel" title="filter by scope"><option value="">all scopes</option></select><input id="consearch" class="consearch" placeholder="search\u2026" autocomplete="off" spellcheck="false"><span class="spacer" style="flex:1"></span><span id="concount"></span><button id="conclear" class="iconbtn" title="clear">' + ICONS.x + '</button></div><div class="conlist" id="conlist"></div></div><div class="browwrap" id="browwrap"><div class="browrail" id="browrail"><div class="browbar"><span class="lbl">Dev servers</span><span class="spacer" style="flex:1"></span><button id="srvreload" class="iconbtn xs" title="refresh">' + ICONS.refresh + '</button><button id="browrailclose" class="iconbtn xs browrailclose" title="hide">' + ICONS.x + '</button></div><div class="srvlist" id="srvlist">' + LOADER + '</div><div class="browbar"><span class="lbl">Playwright specs</span><span class="spacer" style="flex:1"></span><button id="specstop" class="iconbtn xs" title="stop the running spec" style="display:none">' + ICONS.stop + '</button><button id="specreload" class="iconbtn xs" title="rescan">' + ICONS.refresh + '</button></div><div class="speclist" id="speclist">' + LOADER + '</div><div class="specout" id="specout" style="display:none"></div></div><div class="browmain"><div class="browurl"><button id="browrailbtn" class="iconbtn browrailbtn" title="servers and specs">' + ICONS.tasks + '</button><button id="browback" class="iconbtn" title="back">' + ICONS.back + '</button><button id="browfwd" class="iconbtn" title="forward">' + ICONS.arrowRight + '</button><button id="browreload" class="iconbtn" title="reload">' + ICONS.refresh + '</button><input id="browurl" placeholder="http://localhost:3000 \u2014 preview a dev server" autocomplete="off" spellcheck="false"><button id="browgo" class="iconbtn" title="open">' + ICONS.play + '</button><button id="browmore" class="iconbtn browmore" title="more">' + ICONS.dots + '</button><span class="browtools" id="browtools"><span class="browsizes" id="browsizes"><button data-w="0" class="on" title="fit the pane">Fit</button><button data-w="375" title="phone width">375</button><button data-w="768" title="tablet width">768</button><button data-w="1280" title="desktop width">1280</button></span><span class="browsizes" id="browscheme"><button data-s="" class="on" title="whatever your OS is set to">Auto</button><button data-s="light" title="preview the page in light mode">\u2600</button><button data-s="dark" title="preview the page in dark mode">\u263D</button></span><button id="browshot" class="iconbtn" title="screenshot into the composer">' + ICONS.camera + '</button><button id="browext" class="iconbtn" title="open in your browser">' + ICONS.external + '</button><label class="browauto" title="reload when an agent changes a file this server serves"><input type="checkbox" id="browautorel" checked><span>auto</span></label></span></div><div class="browframe" id="browframe"><div class="browhint">Point this at a running dev server to see the page beside its tests.<br>Sites that forbid embedding (X-Frame-Options) won\u2019t render here \u2014 local ones do.</div></div><div class="srvlog" id="srvlog" style="display:none"><div class="srvlogbar"><span class="lbl" id="srvlogname"></span><span class="spacer" style="flex:1"></span><button id="srvlogclose" class="iconbtn xs" title="hide">' + ICONS.x + '</button></div><div class="srvloglines" id="srvloglines"></div></div><div class="pglog" id="pglog" style="display:none"><div class="srvlogbar"><span class="lbl">Page</span><span class="pgtabs" id="pgtabs"><button data-pg="console" class="on">Console</button><button data-pg="network">Network</button></span><span class="spacer" style="flex:1"></span><span class="pgcount" id="pgcount"></span><button id="pgpick" class="iconbtn xs" title="pick an element on the page">' + ICONS.target + '</button><button id="pgclear" class="iconbtn xs" title="clear">' + ICONS.x + '</button></div><div class="srvloglines" id="pglines"></div></div></div></div></div><form class="terminput" id="termform" style="display:none"><span class="pr">❯</span><input id="terminput" placeholder="run a command\u2026" autocomplete="off" autocapitalize="off" spellcheck="false"><span class="st"></span></form></div></div>';
|
|
16776
|
+
mount.innerHTML = '<div class="panel"><div class="tabstrip" id="tabstrip"><button id="sbbtn" class="iconbtn sbbtn" type="button" title="projects and threads" aria-label="show projects and threads">' + ICONS.panelRight + '</button><span id="tabsbox" style="display:contents"></span><span class="spacer"></span><button class="apbadge" id="apbadge" type="button" style="display:none"></button><button id="invitebtn" class="btn outline sm invitebtn" type="button" title="invite a teammate \u2014 one link sets up the team, this repo and their agents">' + ICONS.team + '<span>Invite</span></button><button id="termbtn" class="iconbtn devonly" title="toggle terminal (\u2303`)">' + ICONS.terminal + '</button><button id="phonebtn" class="iconbtn" title="connect a phone" aria-label="connect a phone">' + ICONS.phone + '</button><button id="consolebtn" class="iconbtn devonly" title="console \xB7 errors and logs">' + ICONS.console + '<span class="errdot" id="errdot"></span></button><button id="browserbtn" class="iconbtn devonly" title="browser \xB7 live page and Playwright specs">' + ICONS.globe + '</button><button id="subagentsbtn" class="iconbtn devonly" title="subagents \xB7 every agent an orchestrator or crew put to work">' + ICONS.agents + '</button><button id="railbtn" class="iconbtn devonly" title="toggle right panel">' + ICONS.panelRight + "</button>" + headerActions + '</div><div class="paneswrap"><div class="mainpane" id="mainpane"><div class="pane scroll" id="pane-thread"><div id="agenthead" class="agenthead" style="display:none"></div><div id="routebar"></div><div id="feed">' + LOADER + '</div><div id="feedlive" aria-live="polite"></div><button type="button" class="jumpnew" id="jumpnew">' + ICONS.arrowDown + 'Latest</button></div><div class="pane scroll" id="pane-brain" style="display:none">' + LOADER + '</div><div class="pane scroll" id="pane-observatory" style="display:none">' + LOADER + '</div><div class="pane scroll" id="pane-board" style="display:none"></div><div class="pane scroll" id="pane-orchestra" style="display:none"></div><div class="pane scroll" id="pane-crew" style="display:none"></div><div class="pane scroll" id="pane-fleet" style="display:none"></div><div class="pane scroll" id="pane-team" style="display:none"><div class="teampane" id="fteam"></div></div>' + composerHtml + '</div><div class="dockpane" id="dockpane"><div class="rz rz-dock" id="rz-dock" title="drag to resize"></div><div class="dockhead" id="dockhead"><div class="docktabs" id="docktabs" role="tablist"></div><button id="dockclose" class="iconbtn" title="close every tab">' + ICONS.x + '</button></div><div class="pane scroll" id="pane-changes">' + LOADER + '</div></div></div><div class="termdock" id="termdock"><div class="termresize" id="termresize"></div><div class="termtabs"><span id="termtabs" style="display:contents"></span><button id="termadd" class="iconbtn" title="new terminal">' + ICONS.plus + '</button><span class="spacer"></span><span class="termfind" id="termfind" hidden><input id="termq" placeholder="Find in terminal" spellcheck="false" autocomplete="off" aria-label="find in terminal"><span id="termqn" class="termqn"></span></span><button id="termsearch" class="iconbtn" title="Find in this terminal">' + ICONS.search + '</button><button id="termclear" class="iconbtn" title="Clear this terminal">' + ICONS.trash + '</button><button id="termhide" class="iconbtn" title="hide terminal">' + ICONS.x + '</button></div><div class="termpanes" id="termpanes"><div class="conwrap" id="conwrap"><div class="conbar"><span class="lvl on" data-lvl="all">all</span><span class="lvl" data-lvl="error">errors</span><span class="lvl" data-lvl="warn">warnings</span><select id="conscope" class="consel" title="filter by scope"><option value="">all scopes</option></select><input id="consearch" class="consearch" placeholder="search\u2026" autocomplete="off" spellcheck="false"><span class="spacer" style="flex:1"></span><span id="concount"></span><button id="conclear" class="iconbtn" title="clear">' + ICONS.x + '</button></div><div class="conlist" id="conlist"></div></div><div class="browwrap" id="browwrap"><div class="browrail" id="browrail"><div class="browbar"><span class="lbl">Dev servers</span><span class="spacer" style="flex:1"></span><button id="srvreload" class="iconbtn xs" title="refresh">' + ICONS.refresh + '</button><button id="browrailclose" class="iconbtn xs browrailclose" title="hide">' + ICONS.x + '</button></div><div class="srvlist" id="srvlist">' + LOADER + '</div><div class="browbar"><span class="lbl">Playwright specs</span><span class="spacer" style="flex:1"></span><button id="specstop" class="iconbtn xs" title="stop the running spec" style="display:none">' + ICONS.stop + '</button><button id="specreload" class="iconbtn xs" title="rescan">' + ICONS.refresh + '</button></div><div class="speclist" id="speclist">' + LOADER + '</div><div class="specout" id="specout" style="display:none"></div></div><div class="browmain"><div class="browurl"><button id="browrailbtn" class="iconbtn browrailbtn" title="servers and specs">' + ICONS.tasks + '</button><button id="browback" class="iconbtn" title="back">' + ICONS.back + '</button><button id="browfwd" class="iconbtn" title="forward">' + ICONS.arrowRight + '</button><button id="browreload" class="iconbtn" title="reload">' + ICONS.refresh + '</button><input id="browurl" placeholder="http://localhost:3000 \u2014 preview a dev server" autocomplete="off" spellcheck="false"><button id="browgo" class="iconbtn" title="open">' + ICONS.play + '</button><button id="browmore" class="iconbtn browmore" title="more">' + ICONS.dots + '</button><span class="browtools" id="browtools"><span class="browsizes" id="browsizes"><button data-w="0" class="on" title="fit the pane">Fit</button><button data-w="375" title="phone width">375</button><button data-w="768" title="tablet width">768</button><button data-w="1280" title="desktop width">1280</button></span><span class="browsizes" id="browscheme"><button data-s="" class="on" title="whatever your OS is set to">Auto</button><button data-s="light" title="preview the page in light mode">\u2600</button><button data-s="dark" title="preview the page in dark mode">\u263D</button></span><button id="browshot" class="iconbtn" title="screenshot into the composer">' + ICONS.camera + '</button><button id="browext" class="iconbtn" title="open in your browser">' + ICONS.external + '</button><label class="browauto" title="reload when an agent changes a file this server serves"><input type="checkbox" id="browautorel" checked><span>auto</span></label></span></div><div class="browframe" id="browframe"><div class="browhint">Point this at a running dev server to see the page beside its tests.<br>Sites that forbid embedding (X-Frame-Options) won\u2019t render here \u2014 local ones do.</div></div><div class="srvlog" id="srvlog" style="display:none"><div class="srvlogbar"><span class="lbl" id="srvlogname"></span><span class="spacer" style="flex:1"></span><button id="srvlogclose" class="iconbtn xs" title="hide">' + ICONS.x + '</button></div><div class="srvloglines" id="srvloglines"></div></div><div class="pglog" id="pglog" style="display:none"><div class="srvlogbar"><span class="lbl">Page</span><span class="pgtabs" id="pgtabs"><button data-pg="console" class="on">Console</button><button data-pg="network">Network</button></span><span class="spacer" style="flex:1"></span><span class="pgcount" id="pgcount"></span><button id="pgpick" class="iconbtn xs" title="pick an element on the page">' + ICONS.target + '</button><button id="pgclear" class="iconbtn xs" title="clear">' + ICONS.x + '</button></div><div class="srvloglines" id="pglines"></div></div></div></div></div><form class="terminput" id="termform" style="display:none"><span class="pr">❯</span><input id="terminput" placeholder="run a command\u2026" autocomplete="off" autocapitalize="off" spellcheck="false"><span class="st"></span></form></div></div>';
|
|
16718
16777
|
} else {
|
|
16719
16778
|
mount.innerHTML = '<div class="panel"><header><button id="back" class="iconbtn" title="back">' + ICONS.back + '</button><div class="ptitle"><span class="nm" id="pname">…</span><span class="st" id="pstat"></span></div><span class="spacer"></span>' + headerActions + '</header><div class="chips" id="chips"></div><div class="scroll" id="pane-thread"><div id="routesheet"></div><div id="routebar"></div><div id="feed">' + LOADER + '</div><div id="feedlive" aria-live="polite"></div><button type="button" class="jumpnew" id="jumpnew">' + ICONS.arrowDown + "Latest</button></div>" + composerHtml + "</div>";
|
|
16720
16779
|
}
|
|
@@ -16742,6 +16801,9 @@
|
|
|
16742
16801
|
tabs.splice(2, 0, "orchestra");
|
|
16743
16802
|
tabs.splice(3, 0, "crew");
|
|
16744
16803
|
tabs.splice(4, 0, "fleet");
|
|
16804
|
+
if (!devMode()) tabs = SIMPLE_TABS.filter(function(t) {
|
|
16805
|
+
return tabs.indexOf(t) >= 0;
|
|
16806
|
+
});
|
|
16745
16807
|
if (tabs.indexOf(state.tab) < 0) state.tab = "thread";
|
|
16746
16808
|
var LBL = {
|
|
16747
16809
|
thread: [ICONS.thread, "Chat", "talk to an agent in this thread"],
|
|
@@ -16764,13 +16826,27 @@
|
|
|
16764
16826
|
}
|
|
16765
16827
|
__name(drawTabs, "drawTabs");
|
|
16766
16828
|
function showTab(name) {
|
|
16829
|
+
if (!devMode() && SIMPLE_TABS.indexOf(name) < 0) {
|
|
16830
|
+
toast("That\u2019s in Developer mode \u2014 turn it on in Settings \u2192 Preferences");
|
|
16831
|
+
return;
|
|
16832
|
+
}
|
|
16767
16833
|
var still = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
16768
16834
|
if (document.startViewTransition && state.tab && state.tab !== name && !still && !showTab.inTransition) {
|
|
16769
16835
|
showTab.inTransition = true;
|
|
16770
16836
|
try {
|
|
16837
|
+
var swapped = false;
|
|
16771
16838
|
var vt = document.startViewTransition(function() {
|
|
16839
|
+
if (swapped) return;
|
|
16840
|
+
swapped = true;
|
|
16772
16841
|
showTabNow(name);
|
|
16773
16842
|
});
|
|
16843
|
+
setTimeout(function() {
|
|
16844
|
+
if (!swapped) {
|
|
16845
|
+
swapped = true;
|
|
16846
|
+
showTab.inTransition = false;
|
|
16847
|
+
showTabNow(name);
|
|
16848
|
+
}
|
|
16849
|
+
}, 400);
|
|
16774
16850
|
var quiet = /* @__PURE__ */ __name(function() {
|
|
16775
16851
|
}, "quiet");
|
|
16776
16852
|
if (vt.ready) vt.ready.catch(quiet);
|
|
@@ -17929,7 +18005,7 @@
|
|
|
17929
18005
|
el.id = "recap";
|
|
17930
18006
|
el.className = "recap";
|
|
17931
18007
|
el.setAttribute("role", "status");
|
|
17932
|
-
el.innerHTML = '<span class="recapi">' + ICONS.clock + '</span><span class="recapt"><b>Since you were here</b> <span class="dim">' + esc(newDay ? relDay(last) : rel(last)) + "</span> \xB7 " + turns + " turn" + (turns === 1 ? "" : "s") + " (" + who + ")" + (errs ? ' \xB7 <span class="recaperr">' + errs + " failed</span>" : "") + (cost > 0 ? " \xB7 " + money(cost) : "") + '</span><button type="button" class="btn xs ghost" id="recapgo">Insights</button><button type="button" class="iconbtn xs" id="recapx" aria-label="dismiss" title="dismiss">' + ICONS.x + "</button>";
|
|
18008
|
+
el.innerHTML = '<span class="recapi">' + ICONS.clock + '</span><span class="recapt"><b>Since you were here</b> <span class="dim">' + esc(newDay ? relDay(last) : rel(last)) + "</span> \xB7 " + turns + " turn" + (turns === 1 ? "" : "s") + " (" + who + ")" + (errs ? ' \xB7 <span class="recaperr">' + errs + " failed</span>" : "") + (cost > 0 && devMode() ? " \xB7 " + money(cost) : "") + '</span><button type="button" class="btn xs ghost devonly" id="recapgo">Insights</button><button type="button" class="iconbtn xs" id="recapx" aria-label="dismiss" title="dismiss">' + ICONS.x + "</button>";
|
|
17933
18009
|
host.insertBefore(el, feed);
|
|
17934
18010
|
el.querySelector("#recapx").onclick = function() {
|
|
17935
18011
|
el.remove();
|
|
@@ -18967,7 +19043,9 @@
|
|
|
18967
19043
|
var gh = hue(p.id + p.name);
|
|
18968
19044
|
var rows = '<div class="srow' + (sel ? " sel" : "") + '" data-id="' + esc(p.id) + '"><div class="n"><button class="scaret' + (open ? " open" : "") + '" data-caret="' + esc(p.id) + '" aria-label="' + (open ? "collapse " : "expand ") + esc(p.name) + '" aria-expanded="' + (open ? "true" : "false") + '">' + ICONS.chevron + '</button><span class="pglyph' + (p.needsInput ? " hot" : "") + '" style="background:color-mix(in srgb, hsl(' + gh + ",60%,50%) 20%, transparent);color:hsl(" + gh + ',60%,var(--agent-l))">' + esc((p.name || "?").slice(0, 1).toUpperCase()) + '</span><span class="nm">' + esc(p.name) + "</span>" + // The gear takes the trailing slot, so the count needs its own
|
|
18969
19045
|
// margin-left:auto — .cnt's rule loses to the badge's inline style.
|
|
18970
|
-
(act ? '<span class="badge live" style="margin-left:auto">' + (r.current + 1) + "/" + r.steps.length + "</span>" : '<span class="cnt" style="margin-left:auto">' + adapters.length + "</span>") + '<button class="psetbtn" data-pset="' + esc(p.id) + '" title="project settings" aria-label="settings for ' + esc(p.name) + '">' + ICONS.gear + "</button></div>" + (p.error ? '<div class="m merr">' + ICONS.alert + (p.missing ? "folder missing" : "needs loom init") + "</div></div>" : '<div class="m">baton ' + esc(p.holder || "\u2014") + (p.costUsd > 0 ? " \xB7 " + money(p.costUsd) : "") + "</div></div>")
|
|
19046
|
+
(act ? '<span class="badge live" style="margin-left:auto">' + (r.current + 1) + "/" + r.steps.length + "</span>" : '<span class="cnt" style="margin-left:auto">' + adapters.length + "</span>") + '<button class="psetbtn" data-pset="' + esc(p.id) + '" title="project settings" aria-label="settings for ' + esc(p.name) + '">' + ICONS.gear + "</button></div>" + (p.error ? '<div class="m merr">' + ICONS.alert + (p.missing ? "folder missing" : "needs loom init") + "</div></div>" : devMode() ? '<div class="m">baton ' + esc(p.holder || "\u2014") + (p.costUsd > 0 ? " \xB7 " + money(p.costUsd) : "") + "</div></div>" : '<div class="m">' + ((p.agents || []).some(function(a) {
|
|
19047
|
+
return a.busy;
|
|
19048
|
+
}) || (p.activity || []).length ? '<span class="mwork">working</span>' : "idle") + "</div></div>");
|
|
18971
19049
|
if (open && !p.error) {
|
|
18972
19050
|
var chats = (p.chats || [{ id: "main", title: "Main", createdAt: 0 }]).slice();
|
|
18973
19051
|
var here = currentChat(), SHOW = 8;
|
|
@@ -20791,7 +20869,7 @@
|
|
|
20791
20869
|
}
|
|
20792
20870
|
__name(runnerAct, "runnerAct");
|
|
20793
20871
|
var JOB_ST = { queued: ["queued", "off"], claimed: ["running", "live"], done: ["done", "ok"], failed: ["failed", "err"], cancelled: ["cancelled", "off"] };
|
|
20794
|
-
var JOB_KIND = { start: "Start", "continue": "Continue", fix: "CI fix", "return": "Bring back", land: "Land" };
|
|
20872
|
+
var JOB_KIND = { start: "Start", "continue": "Continue", fix: "CI fix", "return": "Bring back", land: "Land", task: "Task" };
|
|
20795
20873
|
function jobProgressText(p) {
|
|
20796
20874
|
if (!p) return "";
|
|
20797
20875
|
var ts = p.tasks || [], done = ts.filter(function(t) {
|
|
@@ -20813,7 +20891,8 @@
|
|
|
20813
20891
|
return r.online;
|
|
20814
20892
|
}).length + "/" + rs.length + " online</span></div>";
|
|
20815
20893
|
h += rs.map(function(r) {
|
|
20816
|
-
return '<div class="trn" data-trunner="' + esc(r.deviceId) + '"><span class="tnt"><span class="odot ' + (r.online ? "ok" : "off") + '" title="' + (r.online ? "online" : "offline") + '"></span><b>' + esc(runnerName(r)) + "</b>" + (r.shared ? '<span class="tbdg">shared</span>' : "") +
|
|
20894
|
+
return '<div class="trn" data-trunner="' + esc(r.deviceId) + '"><span class="tnt"><span class="odot ' + (r.online ? "ok" : "off") + '" title="' + (r.online ? "online" : "offline") + '"></span><b>' + esc(runnerName(r)) + "</b>" + (r.shared ? '<span class="tbdg">shared</span>' : "") + // a teammate's shared runner is where your goals' team:<agent> tasks run
|
|
20895
|
+
(r.shared && !r.mine && r.online ? '<span class="tbdg" title="your goals can send tasks here: team:' + esc((r.kinds || []).join(", team:")) + '">takes your tasks</span>' : "") + "<small>" + (r.mine ? "yours" : esc(r.github || "a teammate") + "\u2019s") + " \xB7 " + (esc((r.kinds || []).join(", ")) || "no agents yet") + (r.capacity ? " \xB7 " + Number(r.capacity) + " at a time" : "") + (r.online ? "" : " \xB7 seen " + rel(r.lastSeen)) + "</small></span></div>";
|
|
20817
20896
|
}).join("");
|
|
20818
20897
|
if (jobs.length) h += '<div class="trsub">Jobs</div>' + jobs.map(function(j) {
|
|
20819
20898
|
var s = JOB_ST[j.state] || [j.state || "\u2014", "off"], on = byId[j.runnerId];
|
|
@@ -20964,11 +21043,42 @@
|
|
|
20964
21043
|
}).join("") || '<span class="tcm">none shared yet \u2014 share a project below, or push one whose remote is a team repo</span>') + '</div><div class="tinvslot">' + teamInviteHtml(t.id) + '</div><div class="tsec">' + teamSessionsHtml(t) + '</div><div class="tsplit"><div class="tsec">' + teamLeasesHtml(t) + '</div><div class="tsec">' + teamFeedHtml(t, 15) + "</div></div>" + teamCostsHtml(t) + "</div>";
|
|
20965
21044
|
}
|
|
20966
21045
|
__name(teamCardHtml, "teamCardHtml");
|
|
21046
|
+
var teamAuto = {};
|
|
21047
|
+
function loadTeamAuto(pid, force) {
|
|
21048
|
+
var cur = teamAuto[pid];
|
|
21049
|
+
if (cur && (cur.loading || !force)) return;
|
|
21050
|
+
teamAuto[pid] = Object.assign({}, cur, { loading: true });
|
|
21051
|
+
api("/api/projects/" + pid + "/team/auto").then(function(j) {
|
|
21052
|
+
teamAuto[pid] = j;
|
|
21053
|
+
}, function(err) {
|
|
21054
|
+
teamAuto[pid] = { err: err.message || String(err) };
|
|
21055
|
+
}).then(function() {
|
|
21056
|
+
runnersChanged(pid);
|
|
21057
|
+
});
|
|
21058
|
+
}
|
|
21059
|
+
__name(loadTeamAuto, "loadTeamAuto");
|
|
21060
|
+
function teamAutoHtml(pid) {
|
|
21061
|
+
if (!pid) return "";
|
|
21062
|
+
if (!teamAuto[pid]) loadTeamAuto(pid);
|
|
21063
|
+
var a = teamAuto[pid] || {};
|
|
21064
|
+
if (a.err || a.shared === void 0) return "";
|
|
21065
|
+
var line = /* @__PURE__ */ __name(function(ok, text) {
|
|
21066
|
+
return '<div class="tal' + (ok ? " ok" : "") + '"><span class="odot ' + (ok ? "ok" : "off") + '"></span>' + text + "</div>";
|
|
21067
|
+
}, "line");
|
|
21068
|
+
var on = (a.teammates || []).filter(function(t) {
|
|
21069
|
+
return t.online;
|
|
21070
|
+
});
|
|
21071
|
+
var mine = a.myRunner && a.myRunner.running && a.myRunner.shared;
|
|
21072
|
+
return '<div class="tcard tauto"><div class="tsec" style="border-top:0"><div class="tsech">Two machines, one goal<span class="n">' + (a.ready ? "on" : "not yet") + '</span></div><div class="tad">Goals you start split across teammates\u2019 machines \u2014 each task runs with their own agent and comes back into one PR.</div>' + line(a.policyShared, "Repo policy lets teammates\u2019 runners help" + (a.policyShared ? "" : " <small>(loom.team.json \u2192 runners.shared)</small>")) + line(mine, mine ? "Your agents can take teammates\u2019 tasks" : "Your machine isn\u2019t taking teammates\u2019 tasks") + line(on.length > 0, on.length ? "Online: " + on.map(function(t) {
|
|
21073
|
+
return "<b>" + esc(t.github) + "</b> (" + esc(t.kinds.join(", ")) + ")";
|
|
21074
|
+
}).join(", ") : "No teammate\u2019s machine is online for it yet") + (a.ready ? '<div class="tad">A goal gets <b>' + esc((a.workers || []).join(", ")) + "</b> as workers. Keep one local with <code>--solo</code>.</div>" : "") + (mine ? "" : '<div class="tact"><button class="btn primary xs" type="button" data-tautoon>Turn on</button></div>') + "</div></div>";
|
|
21075
|
+
}
|
|
21076
|
+
__name(teamAutoHtml, "teamAutoHtml");
|
|
20967
21077
|
function teamFleetHtml(st, pid) {
|
|
20968
21078
|
var teams = st.signedIn ? st.teams || [] : [];
|
|
20969
21079
|
if (!teams.length) return teamHeadHtml() + teamJoinCardHtml();
|
|
20970
21080
|
var down = st.connected === false ? '<div class="tinvw thubdown">' + ICONS.alert + "<span><b>Can\u2019t reach the team hub.</b> " + esc(st.lastError || "") + " \u2014 your agents keep working here; Loom tries again every 15 seconds.</span></div>" : "";
|
|
20971
|
-
return teamHeadHtml() + down + teamShareHtml(pid, true) + teamNeedsHtml(pid) + teamRunnersHtml(pid) + teams.map(teamCardHtml).join("") + teamDeploysHtml(pid);
|
|
21081
|
+
return teamHeadHtml() + down + teamShareHtml(pid, true) + teamNeedsHtml(pid) + teamAutoHtml(pid) + teamRunnersHtml(pid) + teams.map(teamCardHtml).join("") + teamDeploysHtml(pid);
|
|
20972
21082
|
}
|
|
20973
21083
|
__name(teamFleetHtml, "teamFleetHtml");
|
|
20974
21084
|
|
|
@@ -21384,7 +21494,7 @@
|
|
|
21384
21494
|
var who = /* @__PURE__ */ __name(function(id, c) {
|
|
21385
21495
|
return '<span class="' + c + '">' + (id ? agentGlyph(kindOf(id), id, "brand") : "") + esc(id ? labelOf(id) : "\u2014") + "</span>";
|
|
21386
21496
|
}, "who");
|
|
21387
|
-
return '<div class="handoff" title="the baton passed ' + esc(new Date(Number(e.ts) || Date.now()).toLocaleString()) + '"><span class="hochip"><span class="hok">baton</span>' + who(p.from, "a") + '<span class="shuttle">' + ICONS.arrowRight + "</span>" + who(p.to, "b") + "</span></div>";
|
|
21497
|
+
return '<div class="handoff' + (p.from ? "" : " devonly") + '" title="the baton passed ' + esc(new Date(Number(e.ts) || Date.now()).toLocaleString()) + '"><span class="hochip"><span class="hok devonly">baton</span>' + who(p.from, "a") + '<span class="shuttle">' + ICONS.arrowRight + "</span>" + who(p.to, "b") + "</span></div>";
|
|
21388
21498
|
}
|
|
21389
21499
|
if (e.kind === "subtask_started") return '<div class="sys" style="padding-left:22px">\u21B3 ' + esc(e.agentId) + " picks up a subtask for " + esc(p.parent) + ": " + esc(String(p.task || "").slice(0, 90)) + "</div>";
|
|
21390
21500
|
if (e.kind === "subtask_done") return '<div class="sys" style="padding-left:22px;color:var(--live)">\u21B3 ' + esc(e.agentId) + " finished its subtask</div>";
|
|
@@ -21666,7 +21776,7 @@
|
|
|
21666
21776
|
var r = p.rerun || null;
|
|
21667
21777
|
var meta = r ? esc(String(r.command || "")) + " \xB7 " + durfmt(Number(r.ms) || 0) + (r.clean === false ? " \xB7 in place" : " \xB7 clean checkout") : "";
|
|
21668
21778
|
var claims = (p.claims || []).map(function(c) {
|
|
21669
|
-
return '<li class="pf-' + esc(c.status || "unchecked") + '"><span class="pfq">\u201C' + esc(String(c.text || "").slice(0, 140)) + '\u201D</span><span class="pfw">' + esc(String(c.why || "")) + "</span></li>";
|
|
21779
|
+
return '<li class="pf-' + esc(c.status || "unchecked") + '"><span class="pfq">\u201C' + esc(String(c.text || "").replace(/\*\*|__|`/g, "").slice(0, 140)) + '\u201D</span><span class="pfw">' + esc(String(c.why || "")) + "</span></li>";
|
|
21670
21780
|
}).join("");
|
|
21671
21781
|
var counts = r && r.counts ? '<span class="pfn">' + Number(r.counts.passed || 0) + " passed" + (r.counts.failed ? " \xB7 " + Number(r.counts.failed) + " failed" : "") + "</span>" : "";
|
|
21672
21782
|
return '<div class="proofcard v-' + esc(p.verdict || "unverified") + '" data-agent="' + esc(p.about || "") + '"><div class="pfh"><span class="pfi">' + ICONS[v.icon] + "</span><b>" + v.label + '</b><span class="pfhl">' + esc(String(p.headline || "")) + "</span>" + counts + (p.auto ? '<span class="pfauto" title="checked automatically: the reply claimed it">auto</span>' : "") + "</div>" + (claims ? '<ul class="pfc">' + claims + "</ul>" : "") + (r ? '<details class="pfr"><summary>' + meta + (r.timedOut ? " \xB7 timed out" : "") + "</summary><pre>" + esc(String(r.tail || "")) + "</pre></details>" : "") + "</div>";
|
|
@@ -22318,7 +22428,9 @@
|
|
|
22318
22428
|
|
|
22319
22429
|
// src/web/navigation.js
|
|
22320
22430
|
function route() {
|
|
22431
|
+
state.rerender = route;
|
|
22321
22432
|
applyTheme();
|
|
22433
|
+
applyMode();
|
|
22322
22434
|
state.composer = null;
|
|
22323
22435
|
state.toggleTerm = null;
|
|
22324
22436
|
state.selectProject = null;
|