@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +10 -0
  3. package/dist/cli/index.js +59 -7
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/core/crew.d.ts +16 -1
  6. package/dist/core/crew.js +26 -2
  7. package/dist/core/crew.js.map +1 -1
  8. package/dist/core/orchestra.d.ts +36 -0
  9. package/dist/core/orchestra.js +154 -82
  10. package/dist/core/orchestra.js.map +1 -1
  11. package/dist/core/team-hub.d.ts +2 -1
  12. package/dist/core/team-hub.js +1 -1
  13. package/dist/core/team-hub.js.map +1 -1
  14. package/dist/core/team-leases.d.ts +7 -0
  15. package/dist/core/team-leases.js +19 -0
  16. package/dist/core/team-leases.js.map +1 -1
  17. package/dist/daemon/client.d.ts +12 -1
  18. package/dist/daemon/client.js +5 -2
  19. package/dist/daemon/client.js.map +1 -1
  20. package/dist/daemon/routes/brain.js +2 -2
  21. package/dist/daemon/routes/brain.js.map +1 -1
  22. package/dist/daemon/routes/orchestra.js +1 -0
  23. package/dist/daemon/routes/orchestra.js.map +1 -1
  24. package/dist/daemon/routes/project-team.js +17 -0
  25. package/dist/daemon/routes/project-team.js.map +1 -1
  26. package/dist/daemon/runner.d.ts +11 -0
  27. package/dist/daemon/runner.js +111 -5
  28. package/dist/daemon/runner.js.map +1 -1
  29. package/dist/daemon/runtime/briefings.js +12 -1
  30. package/dist/daemon/runtime/briefings.js.map +1 -1
  31. package/dist/daemon/runtime.d.ts +9 -2
  32. package/dist/daemon/runtime.js +8 -4
  33. package/dist/daemon/runtime.js.map +1 -1
  34. package/dist/daemon/team-coordinator.d.ts +27 -3
  35. package/dist/daemon/team-coordinator.js +54 -2
  36. package/dist/daemon/team-coordinator.js.map +1 -1
  37. package/dist/daemon/team.d.ts +42 -1
  38. package/dist/daemon/team.js +92 -1
  39. package/dist/daemon/team.js.map +1 -1
  40. package/dist/hub/supabase-client.d.ts +4 -2
  41. package/dist/hub/supabase-client.js +21 -12
  42. package/dist/hub/supabase-client.js.map +1 -1
  43. package/dist/web/app.css +12 -0
  44. package/dist/web/app.js +140 -28
  45. package/dist/web/app.js.map +3 -3
  46. package/docs/release-notes/v1.2.3.md +29 -0
  47. package/docs/release-notes/v1.2.4.md +13 -0
  48. package/docs/teams.md +31 -0
  49. 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 style="margin-left:auto">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></div>';
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 " + esc(holder.id) : "") + '</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) {
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&hellip; @ 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&hellip; @ 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&#96;)">' + 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">&#10095;</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&#96;)">' + 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">&#10095;</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">&hellip;</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>' : "") + "<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>";
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;