crewswarm 0.9.3 → 0.9.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 (73) hide show
  1. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
  2. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
  3. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
  4. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
  5. package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
  6. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
  7. package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
  8. package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
  9. package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
  10. package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
  11. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  12. package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
  13. package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
  14. package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
  15. package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
  16. package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
  17. package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
  18. package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
  19. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
  20. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
  21. package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
  22. package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
  23. package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
  24. package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
  25. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
  26. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
  27. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
  28. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
  29. package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
  30. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
  31. package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
  32. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
  33. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
  34. package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
  35. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  36. package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
  37. package/apps/dashboard/dist/index.html +56 -7
  38. package/apps/dashboard/dist/index.html.br +0 -0
  39. package/apps/dashboard/dist/index.html.gz +0 -0
  40. package/apps/vibe/server.mjs +98 -53
  41. package/lib/bridges/cli-executor.mjs +1 -1
  42. package/lib/browser/passthrough-stderr.js +1 -0
  43. package/lib/chat/project-messages.mjs +3 -5
  44. package/lib/cli-process-tracker.mjs +3 -2
  45. package/lib/contacts/identity-linker.mjs +1 -0
  46. package/lib/crew-judge/judge.mjs +19 -18
  47. package/lib/crew-lead/agent-manager.mjs +1 -1
  48. package/lib/crew-lead/background.mjs +14 -1
  49. package/lib/crew-lead/chat-handler.mjs +4 -1
  50. package/lib/crew-lead/http-server.mjs +73 -65
  51. package/lib/crew-lead/prompts.mjs +7 -1
  52. package/lib/crew-lead/tools.mjs +3 -2
  53. package/lib/crew-lead/wave-dispatcher.mjs +4 -2
  54. package/lib/engines/crew-cli.mjs +1 -1
  55. package/lib/engines/engine-registry.mjs +11 -9
  56. package/lib/engines/runners.mjs +23 -2
  57. package/lib/gemini-cli-passthrough-noise.mjs +1 -1
  58. package/lib/integrations/code-search.mjs +4 -3
  59. package/lib/memory/shared-adapter.mjs +23 -10
  60. package/lib/pipeline/manager.mjs +2 -1
  61. package/lib/runtime/config.mjs +1 -1
  62. package/lib/runtime/spending.mjs +2 -1
  63. package/package.json +17 -9
  64. package/scripts/dashboard-validation.mjs +2 -0
  65. package/scripts/dashboard.mjs +1110 -484
  66. package/scripts/generate-openapi.mjs +683 -277
  67. package/scripts/restart-service.sh +12 -9
  68. package/apps/dashboard/dist/assets/chat-core-3KirthZA.js +0 -1
  69. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
  70. package/apps/dashboard/dist/assets/index-GSWxxEPO.js +0 -2
  71. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
  72. package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +0 -1
  73. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
@@ -0,0 +1 @@
1
+ import{p as e,s as t,g as o,a as n,e as a}from"./core-utils-CmOkXgzi.js";import{s as r,u as s}from"./tab-projects-tab-SFH4E--a.js";let l=null,i=null;function c({getModels:e,populateModelDropdown:t}={}){l=e||l,i=t||i}async function d(){const n=document.getElementById("rtTokenInput").value.trim();if(n)try{await e("/api/settings/rt-token",{token:n}),t("RT Bus token saved"),document.getElementById("rtTokenInput").value="",async function(){try{const e=await o("/api/settings/rt-token"),t=document.getElementById("rtTokenBadge"),n=document.getElementById("rtTokenInput");e.token?(t.textContent="set ✓",t.style.background="rgba(52,211,153,0.15)",t.style.color="var(--green)",t.style.borderColor="rgba(52,211,153,0.3)",n.placeholder="••••••••••••••••••••••• (saved)"):(t.textContent="not set",t.style.background="rgba(251,191,36,0.15)",t.style.color="var(--yellow)",t.style.borderColor="rgba(251,191,36,0.3)")}catch{}}()}catch(a){t("Save failed: "+a.message,"error")}else t("Paste a token first","error")}async function u(){const e=document.getElementById("configLockBadge"),t=document.getElementById("configLockStatus"),n=document.querySelector('[data-action="lockConfig"]'),a=document.querySelector('[data-action="unlockConfig"]');try{(await o("/api/config/lock-status")).locked?(e.textContent="🔒 Locked",e.style.background="rgba(52,211,153,0.15)",e.style.color="var(--green)",e.style.borderColor="rgba(52,211,153,0.3)",t&&(t.textContent="✓ Config is protected from overwrites"),n&&(n.className="btn-primary",n.style.opacity="0.6",n.style.pointerEvents="none"),a&&(a.className="btn-ghost",a.style.opacity="1",a.style.pointerEvents="auto")):(e.textContent="🔓 Unlocked",e.style.background="rgba(251,191,36,0.15)",e.style.color="var(--yellow)",e.style.borderColor="rgba(251,191,36,0.3)",t&&(t.textContent="⚠️ Config can be modified — lock it after making changes"),n&&(n.className="btn-primary",n.style.opacity="1",n.style.pointerEvents="auto"),a&&(a.className="btn-ghost",a.style.opacity="0.6",a.style.pointerEvents="none"))}catch{e&&(e.textContent="? unknown")}}async function g(){try{await e("/api/config/lock",{}),t("✓ Config locked — protected from overwrites"),u()}catch(o){t("Lock failed: "+o.message,"error")}}async function m(){try{await e("/api/config/unlock",{}),t("✓ Config unlocked — you can now make changes"),u()}catch(o){t("Unlock failed: "+o.message,"error")}}async function y(){try{const e=await o("/api/settings/opencode-project"),t=document.getElementById("opencodeProjInput"),n=document.getElementById("opencodeProjStatus");t&&(t.placeholder=e.dir||"e.g. /Users/you/Desktop/myproject",t.value=e.dir||""),n&&(n.textContent=e.dir?"✅ Current: "+e.dir:"⚠️ Not set — OpenCode will write files to the crewswarm repo root. Set this to your project folder."),document.getElementById("opencodeFallbackSelect")&&l&&(await l(),i&&i("opencodeFallbackSelect",e.fallbackModel||""));const a=document.getElementById("opencodeFallbackStatus");a&&(a.textContent=e.fallbackModel?"✅ Fallback: "+e.fallbackModel:"⚠️ Using default groq/kimi-k2-instruct-0905"),document.getElementById("opencodeModelSelect")&&l&&(await l(),i&&i("opencodeModelSelect",e.opencodeModel||""));const r=document.getElementById("opencodeModelStatus");r&&(r.textContent=e.opencodeModel?"✅ Primary: "+e.opencodeModel:"⚠️ Using default groq/moonshotai/kimi-k2-instruct-0905");const s=document.getElementById("crewLeadModelSelect");s&&e.crewLeadModel&&(s.value=e.crewLeadModel)}catch{}}async function f(){var o,a;const l=((null==(o=document.getElementById("opencodeProjInput"))?void 0:o.value)||"").trim(),i=((null==(a=document.getElementById("opencodeFallbackSelect"))?void 0:a.value)||"").trim();try{if(await e("/api/settings/opencode-project",{dir:l||void 0,fallbackModel:i||void 0}),t("OpenCode settings saved — fallback takes effect on next task (no restart needed)"),y(),l&&n.projectsData){const e=Object.values(n.projectsData).find(e=>e.outputDir===l);if(e){n.chatActiveProjectId=e.id,r(e.id);const t=document.getElementById("chatProjectSelect");t&&(t.value=e.id),s()}}}catch(c){t("Save failed: "+c.message,"error")}}async function p(){const o=document.getElementById("opencodeModelSelect"),n=((null==o?void 0:o.value)||"").trim(),a=document.getElementById("opencodeModelStatus");try{await e("/api/settings/opencode-project",{opencodeModel:n||void 0}),a&&(a.textContent="✓ Saved",a.style.color="var(--green-hi)"),t(n?`Primary OpenCode model → ${n}`:"OpenCode model reset to default"),setTimeout(()=>{a&&(a.textContent=n?"✅ Primary: "+n:"⚠️ Using default groq/moonshotai/kimi-k2-instruct-0905")},3e3)}catch(r){a&&(a.textContent="Error: "+r.message,a.style.color="var(--red)"),t("Save failed: "+r.message,"error")}}async function v(){const e=document.getElementById("bgConsciousnessBtn"),t=document.getElementById("bgConsciousnessStatus"),n=document.getElementById("bgConsciousnessModel");try{const a=await o("/api/settings/bg-consciousness");if(!1===a.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const r=a.enabled;e&&(e.textContent=r?"🟢 ON":"⚫ OFF",e.style.background=r?"rgba(34,197,94,0.15)":"var(--surface-2)",e.style.borderColor=r?"var(--green-hi)":"var(--border)",e.style.color=r?"var(--green-hi)":"var(--text-2)"),n&&a.model&&(n.placeholder=a.model),t&&(t.textContent=r?"Active — crew-lead reflects every "+Math.round(a.intervalMs/6e4)+"min when idle. Model: "+a.model:"Off — crew-lead will not self-reflect between tasks.")}catch(a){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+a.message)}}async function C(){try{const n=await o("/api/settings/bg-consciousness");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");const a=await e("/api/settings/bg-consciousness",{enabled:!n.enabled});t("Background consciousness "+(a.enabled?"ENABLED":"DISABLED")),v()}catch(n){t("Failed: "+n.message,"error")}}async function b(){const o=document.getElementById("bgConsciousnessModel"),n=((null==o?void 0:o.value)||"").trim();if(n)try{await e("/api/settings/bg-consciousness",{model:n}),t("Background consciousness model → "+n),o.value="",v()}catch(a){t("Failed: "+a.message,"error")}else t("Enter a model first (e.g. groq/llama-3.3-70b-versatile)","error")}async function h(){const e=document.getElementById("cursorWavesBtn"),t=document.getElementById("cursorWavesStatus");try{const n=await o("/api/settings/cursor-waves");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"⚡ ON":"⚫ OFF",e.style.background=a?"rgba(168,85,247,0.15)":"var(--surface-2)",e.style.borderColor=a?"#a855f7":"var(--border)",e.style.color=a?"#c084fc":"var(--text-2)"),t&&(t.textContent=a?"Active — multi-agent waves fan out to Cursor subagents in parallel. crew-orchestrator coordinates each wave.":"Off — each agent in a wave dispatches independently through the standard gateway.")}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+n.message)}}async function E(){try{const n=await o("/api/settings/cursor-waves");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");const a=await e("/api/settings/cursor-waves",{enabled:!n.enabled});t("Cursor Parallel Waves "+(a.enabled?"ENABLED ⚡":"DISABLED")),h()}catch(n){t("Failed: "+n.message,"error")}}async function k(){const e=document.getElementById("tmuxBridgeBtn"),t=document.getElementById("tmuxBridgeStatus");try{const n=await o("/api/settings/tmux-bridge");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"🔌 ON":"⚫ OFF",e.style.background=a?"rgba(52,211,153,0.15)":"var(--surface-2)",e.style.borderColor=a?"rgba(52,211,153,0.3)":"var(--border)",e.style.color=a?"var(--green)":"var(--text-2)"),t&&(t.textContent=a?"Active — agents can share persistent tmux sessions across pipeline waves. Requires tmux + smux.":"Off — agents use standard cold-start execution (no session persistence).")}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+n.message)}}async function x(){try{const n=await o("/api/settings/tmux-bridge");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");const a=await e("/api/settings/tmux-bridge",{enabled:!n.enabled});t("tmux-bridge "+(a.enabled?"ENABLED 🔌":"DISABLED")),k()}catch(n){t("Failed: "+n.message,"error")}}async function w(){const e=document.getElementById("autonomousMentionsBtn"),t=document.getElementById("autonomousMentionsStatus");try{const n=await o("/api/settings/autonomous-mentions");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=!1!==n.enabled;e&&(e.textContent=a?"🕸 ON":"⚫ OFF",e.style.background=a?"rgba(52,211,153,0.15)":"var(--surface-2)",e.style.borderColor=a?"rgba(52,211,153,0.3)":"var(--border)",e.style.color=a?"var(--green)":"var(--text-2)"),t&&(t.textContent=a?"Active — shared chat @mentions can auto-route to agents and CLI participants.":"Off — @mentions are recorded in chat history, but no autonomous routing will fire.",t.style.color="var(--text-3)")}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+n.message)}}async function I(){try{const n=await o("/api/settings/autonomous-mentions");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");const a=await e("/api/settings/autonomous-mentions",{enabled:!n.enabled});t("Autonomous mention routing "+(a.enabled?"ENABLED 🕸":"DISABLED")),w()}catch(n){t("Failed: "+n.message,"error")}}async function M(){const e=document.getElementById("claudeCodeBtn"),t=document.getElementById("claudeCodeStatus");try{const n=await o("/api/settings/claude-code");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services or check that crew-lead is running.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"🤖 ON":"⚫ OFF",e.style.background=a?"rgba(245,158,11,0.15)":"var(--surface-2)",e.style.borderColor=a?"var(--amber)":"var(--border)",e.style.color=a?"var(--yellow)":"var(--text-2)"),t&&(n.hasKey?(t.textContent=a?"Active — tasks route through Claude Code CLI. Per-agent override: set useClaudeCode: true in crewswarm.json.":"Off — tasks use direct LLM or OpenCode. Enable to run agents through Claude Code CLI.",t.style.color="var(--text-3)"):(t.textContent='⚠️ No Claude auth found — run "claude" in terminal to authenticate via OAuth, or set ANTHROPIC_API_KEY.',t.style.color="var(--amber)"))}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+n.message)}}async function S(){try{const n=await o("/api/settings/claude-code");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");if(!n.hasKey)return void t('No Claude auth found — run "claude" in terminal to authenticate via OAuth, or set ANTHROPIC_API_KEY',"error");const a=await e("/api/settings/claude-code",{enabled:!n.enabled});t("Claude Code executor "+(a.enabled?"ENABLED 🤖":"DISABLED")),M()}catch(n){t("Failed: "+n.message,"error")}}async function _(){const e=document.getElementById("codexBtn"),t=document.getElementById("codexStatus");try{const n=await o("/api/settings/codex");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"🟣 ON":"⚫ OFF",e.style.background=a?"rgba(168,85,247,0.15)":"var(--surface-2)",e.style.borderColor=a?"#a855f7":"var(--border)",e.style.color=a?"#a855f7":"var(--text-2)"),t&&(t.textContent=a?"Active — tasks route through Codex CLI. Per-agent override: set useCodex: true in crewswarm.json.":"Off — tasks use direct LLM or other engine. Enable to route all coding agents through Codex CLI.",t.style.color="var(--text-3)")}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+n.message,t.style.color="var(--text-3)")}}async function O(){try{const n=await o("/api/settings/codex");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");const a=await e("/api/settings/codex",{enabled:!n.enabled});t("Codex CLI executor "+(a.enabled?"ENABLED 🟣":"DISABLED")),_()}catch(n){t("Failed: "+n.message,"error")}}async function A(){const e=document.getElementById("geminiCliBtn"),t=document.getElementById("geminiCliStatus");try{const n=await o("/api/settings/gemini-cli");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"🔵 ON":"⚫ OFF",e.style.background=a?"rgba(66,133,244,0.15)":"var(--surface-2)",e.style.borderColor=a?"#4285f4":"var(--border)",e.style.color=a?"#4285f4":"var(--text-2)"),t&&(n.installed?(t.textContent=a?"Active — tasks route through Gemini CLI. Run gemini auth login if you haven't authenticated yet.":"Off — tasks use direct LLM or other engine. Enable to route coding agents through Gemini CLI (free Google OAuth tier).",t.style.color="var(--text-3)"):(t.textContent="⚠️ gemini binary not found — run: npm install -g @google/gemini-cli",t.style.color="var(--amber)"))}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load: "+n.message,t.style.color="var(--text-3)")}}async function R(){try{const n=await o("/api/settings/gemini-cli");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");if(!n.installed)return void t("Install Gemini CLI first: npm install -g @google/gemini-cli","error");const a=await e("/api/settings/gemini-cli",{enabled:!n.enabled});t("Gemini CLI executor "+(a.enabled?"ENABLED 🔵":"DISABLED")),A()}catch(n){t("Failed: "+n.message,"error")}}async function B(){const e=document.getElementById("crewCliBtn"),t=document.getElementById("crewCliStatus");try{const n=await o("/api/settings/crew-cli");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"🔧 ON":"⚫ OFF",e.style.background=a?"rgba(16,185,129,0.15)":"var(--surface-2)",e.style.borderColor=a?"#10b981":"var(--border)",e.style.color=a?"#10b981":"var(--text-2)"),t&&(t.textContent=a?"Active — multi-agent swarm tasks route through crew-cli with intelligent dispatch to specialists.":"Off — tasks use direct LLM or other engine. Enable to route all coding agents through crew-cli natively.")}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load status")}}async function L(){try{const n=await o("/api/settings/crew-cli");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");const a=await e("/api/settings/crew-cli",{enabled:!n.enabled});t("Crew CLI executor "+(a.enabled?"ENABLED 🔧":"DISABLED")),B()}catch(n){t("Failed: "+n.message,"error")}}async function D(){const e=document.getElementById("opencodeBtn"),t=document.getElementById("opencodeStatus");try{const n=await o("/api/settings/opencode");if(!1===n.ok)return e&&(e.textContent="⚫ OFF"),void(t&&(t.textContent="⚠️ Could not reach crew-lead — restart services.",t.style.color="var(--amber)"));const a=n.enabled;e&&(e.textContent=a?"⚡ ON":"⚫ OFF",e.style.background=a?"rgba(52,211,153,0.15)":"var(--surface-2)",e.style.borderColor=a?"rgba(52,211,153,0.3)":"var(--border)",e.style.color=a?"var(--green)":"var(--text-2)"),t&&(n.installed?(t.textContent=a?"⚡ Active — coding agents route through OpenCode for full IDE context and session persistence.":"⚫ Off — tasks use direct LLM or other configured engine. Enable to run agents through OpenCode CLI.",t.style.color="var(--text-3)"):(t.textContent="⚠️ opencode binary not found — install: npm install -g opencode",t.style.color="var(--amber)"))}catch(n){e&&(e.textContent="Error"),t&&(t.textContent="Could not load status",t.style.color="var(--text-3)")}}async function N(){try{const n=await o("/api/settings/opencode");if(!1===n.ok)return void t("Cannot reach crew-lead — restart services first","error");if(!n.installed)return void t("Install OpenCode CLI first: npm install -g opencode","error");const a=await e("/api/settings/opencode",{enabled:!n.enabled});t("OpenCode executor "+(a.enabled?"ENABLED ⚡":"DISABLED")),D()}catch(n){t("Failed: "+n.message,"error")}}async function W(){try{const e=await o("/api/settings/global-fallback"),t=document.getElementById("globalFallbackInput");t&&(t.value=e.globalFallbackModel||"");const n=document.getElementById("globalFallbackStatus");n&&(n.textContent=e.globalFallbackModel?"Active: any agent without a per-agent fallback will use "+e.globalFallbackModel:"Not set — agents without fallback will use the built-in default (groq/llama-3.3-70b-versatile).")}catch(e){console.warn("loadGlobalFallback:",e.message)}}async function T(){var o;const n=((null==(o=document.getElementById("globalFallbackInput"))?void 0:o.value)||"").trim();try{await e("/api/settings/global-fallback",{globalFallbackModel:n}),t(n?"Global fallback → "+n:"Global fallback cleared"),W()}catch(a){t("Failed: "+a.message,"error")}}async function F(){try{const e=await o("/api/settings/global-oc-loop"),t=document.getElementById("globalOcLoop"),n=document.getElementById("globalOcLoopRounds");t&&(t.checked=e.enabled||!1),n&&(n.value=e.maxRounds??10)}catch(e){}}async function P(){var o;const n=null==(o=document.getElementById("globalOcLoop"))?void 0:o.checked;try{await e("/api/settings/global-oc-loop",{enabled:n}),t("Global OC loop "+(n?"enabled":"disabled"))}catch(a){t("Failed: "+a.message,!0)}}async function G(){var o;const n=parseInt(null==(o=document.getElementById("globalOcLoopRounds"))?void 0:o.value)||10;try{await e("/api/settings/global-oc-loop",{maxRounds:n}),t("Max rounds set to "+n)}catch(a){t("Failed: "+a.message,!0)}}async function U(){try{const e=await o("/api/settings/passthrough-notify"),t=document.getElementById("passthroughNotifySelect");t&&(t.value=e.value||"both")}catch(e){}}async function j(){var o;const n=(null==(o=document.getElementById("passthroughNotifySelect"))?void 0:o.value)||"both",a=document.getElementById("passthroughNotifyStatus");try{await e("/api/settings/passthrough-notify",{value:n}),a&&(a.textContent="✓ Saved — takes effect on the next passthrough",a.style.color="var(--green-hi)"),t("Passthrough notifications → "+n)}catch(r){a&&(a.textContent="Error: "+r.message,a.style.color="var(--red)")}}async function H(){try{const e=await o("/api/settings/loop-brain"),t=document.getElementById("loopBrainModel");t&&e.loopBrain&&(t.value=e.loopBrain)}catch{}}const q=[{label:"Engine — OpenCode",vars:[{key:"CREWSWARM_OPENCODE_ENABLED",hint:"Route coding agents through OpenCode globally",default:"off"},{key:"CREWSWARM_OPENCODE_MODEL",hint:"Model passed to OpenCode — leave blank to use per-agent model",default:"per-agent"},{key:"CREWSWARM_OPENCODE_TIMEOUT_MS",hint:"ms before an OpenCode task is killed",default:"300000"},{key:"CREWSWARM_OPENCODE_AGENT",hint:"Override agent name passed to OpenCode",default:"auto"}]},{label:"Engine — Claude Code & Cursor",note:"Both use OAuth login (run claude or cursor once). No API key required.",vars:[{key:"CREWSWARM_CLAUDE_CODE_MODEL",hint:"Model passed to claude -p — leave blank for Claude Code default",default:"claude default"},{key:"CREWSWARM_CURSOR_MODEL",hint:"Cursor CLI --model when agent has no cursorCliModel (default: composer-2-fast)",default:"composer-2-fast"}]},{label:"Engine — Codex & crew-cli",note:"These are the dashboard-wide defaults when an agent does not have a per-route model override.",vars:[{key:"CREWSWARM_CODEX_MODEL",hint:"Model passed to codex exec --model (leave blank for Codex default)",default:"codex default"},{key:"CREWSWARM_CREW_CLI_MODEL",hint:"Model passed to crew chat --model and gateway crew-cli engine",default:"gemini-2.5-flash"}]},{label:"Engine — Gemini CLI",note:"Free tier via Google account — 60 req/min. Run gemini once to auth.",vars:[{key:"CREWSWARM_GEMINI_CLI_ENABLED",hint:"Route agents through Gemini CLI globally",default:"off"},{key:"CREWSWARM_GEMINI_CLI_MODEL",hint:"Model passed to gemini -p (e.g. gemini-2.0-flash) — blank for default",default:"gemini default"}]},{label:"Engine — Docker Sandbox",note:"Runs any inner engine inside an isolated Docker microVM. API keys injected by network proxy — never exposed to the agent.",vars:[{key:"CREWSWARM_DOCKER_SANDBOX",hint:"Route all coding agents through Docker Sandbox globally",default:"off"},{key:"CREWSWARM_DOCKER_SANDBOX_NAME",hint:"Pre-created sandbox name",default:"crewswarm"},{key:"CREWSWARM_DOCKER_SANDBOX_INNER_ENGINE",hint:"Engine inside the sandbox: claude, opencode, or codex",default:"claude"},{key:"CREWSWARM_DOCKER_SANDBOX_TIMEOUT_MS",hint:"ms before a sandboxed task is killed",default:"300000"}]},{label:"Engine Loop & Dispatch",vars:[{key:"CREWSWARM_ENGINE_LOOP",hint:"Enable Ouroboros engine loop for all agents",default:"off"},{key:"CREWSWARM_ENGINE_LOOP_MAX_ROUNDS",hint:"Max STEP iterations per loop run",default:"10"},{key:"CREWSWARM_ENGINE_IDLE_TIMEOUT_MS",hint:"Kill engine (Cursor/Claude) if no output for this many ms",default:"300000"},{key:"CREWSWARM_ENGINE_MAX_TOTAL_MS",hint:"Absolute max ms for any single engine task",default:"2700000"},{key:"CREWSWARM_DISPATCH_TIMEOUT_MS",hint:"ms before an unclaimed dispatch times out",default:"300000"},{key:"CREWSWARM_DISPATCH_CLAIMED_TIMEOUT_MS",hint:"ms before a claimed (in-progress) dispatch times out",default:"900000"},{key:"CREWSWARM_RT_AGENT",hint:"Agent ID used for the RT bus",default:"crew-coder"}]},{label:"Ports",vars:[{key:"CREW_LEAD_PORT",hint:"crew-lead HTTP server port",default:"5010"},{key:"SWARM_DASH_PORT",hint:"Dashboard port",default:"4319"},{key:"WA_HTTP_PORT",hint:"WhatsApp bridge HTTP port",default:"3000"}]},{label:"Background Consciousness",vars:[{key:"CREWSWARM_BG_CONSCIOUSNESS",hint:"Enable idle reflection loop",default:"off"},{key:"CREWSWARM_BG_CONSCIOUSNESS_INTERVAL_MS",hint:"Idle reflection interval in ms",default:"900000"},{key:"CREWSWARM_BG_CONSCIOUSNESS_MODEL",hint:"Model for background cycle (e.g. groq/llama-3.1-8b-instant)",default:"groq/llama-3.1-8b-instant"}]},{label:"Messaging",vars:[{key:"TELEGRAM_ALLOWED_USERNAMES",hint:"Comma-separated Telegram usernames allowed to message the bot",default:"all allowed"},{key:"WA_ALLOWED_NUMBERS",hint:"Comma-separated WhatsApp numbers in intl format (+1555…)",default:"all allowed"}]},{label:"Memory",vars:[{key:"SHARED_MEMORY_NAMESPACE",hint:"Namespace prefix for shared memory keys",default:"crewswarm"},{key:"SHARED_MEMORY_DIR",hint:"Directory for shared memory files",default:"~/.crewswarm/memory"}]},{label:"crew-cli — Streaming & Hooks",note:"Controls for crew-cli streaming output, tool hooks, and session token limits.",vars:[{key:"CREW_NO_STREAM",hint:"Disable streaming output — tokens arrive after full response (true/false)",default:"false"},{key:"CREW_HOOKS_FILE",hint:"Path to hooks.json for PreToolUse/PostToolUse hooks",default:".crew/hooks.json"},{key:"CREW_MAX_SESSION_TOKENS",hint:"Max estimated tokens per session before oldest turns are trimmed",default:"100000"}]},{label:"crew-cli — Codebase Index & RAG",note:"Codebase embedding index auto-builds on startup. Injects relevant file context into every worker prompt.",vars:[{key:"CREW_RAG_MODE",hint:"RAG mode: auto (use index when ready, else keyword), semantic, keyword, import-graph, off",default:"auto"},{key:"CREW_EMBEDDING_PROVIDER",hint:"Embedding provider: local (zero-cost), openai (best), gemini (free tier)",default:"local"},{key:"CREW_RAG_WORKER_BUDGET",hint:"Max tokens of RAG context injected per worker (approximate)",default:"4000"},{key:"CREW_RAG_MAX_FILES",hint:"Max code files to index (larger repos should increase this)",default:"2000"},{key:"CREW_RAG_BATCH_SIZE",hint:"Files per embedding batch (higher = faster but more API calls)",default:"20"}]},{label:"crew-cli — Checkpointing",note:"Automatic git checkpoints during pipeline execution for easy rollback.",vars:[{key:"CREW_AUTO_CHECKPOINT",hint:"Enable auto-commit at task boundaries (true/false)",default:"true"},{key:"CREW_CHECKPOINT_INTERVAL_MS",hint:"Periodic git stash snapshot interval during long tasks (ms, 0=off)",default:"60000"}]},{label:"PM Loop",vars:[{key:"PM_MAX_ITEMS",hint:"Max roadmap items per PM loop run",default:"10"},{key:"PM_MAX_CONCURRENT",hint:"Max concurrent agent tasks in PM loop",default:"20"},{key:"PM_USE_QA",hint:"Include crew-qa review after each PM task",default:"off"},{key:"PM_USE_SECURITY",hint:"Include crew-security review for auth/key tasks",default:"off"},{key:"PM_USE_SPECIALISTS",hint:"Route tasks to specialist agents (front/back/github) by keyword",default:"on"},{key:"PM_SELF_EXTEND",hint:"Auto-generate new roadmap items when queue is empty",default:"on"},{key:"PM_EXTEND_EVERY",hint:"Generate new items every N completions (0 = only when empty)",default:"5"},{key:"PM_CODER_AGENT",hint:"Override default coding agent for PM loop (e.g. crew-coder-front)",default:"crew-coder"},{key:"PM_AGENT_IDLE_TIMEOUT_MS",hint:"Kill PM dispatch if no activity for this many ms",default:"900000"},{key:"PHASED_TASK_TIMEOUT_MS",hint:"Overall timeout for a single agent task in the PM loop",default:"600000"}]}];async function $(){const e=document.getElementById("envAdvancedWidget");if(e)try{const[t,o]=await Promise.all([fetch("/api/env").then(e=>e.json()).catch(()=>({})),fetch("/api/env-advanced").then(e=>e.json()).catch(()=>({env:{}}))]),n=o.env||{},r=null!=t.uptime?t.uptime<60?t.uptime+"s":Math.floor(t.uptime/60)+"m":"—";let s=`<div style="display:flex;gap:24px;flex-wrap:wrap;font-size:11px;color:var(--text-3);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--border);">\n <span>cwd: <code style="color:var(--text-2);">${a(t.cwd||"—")}</code></span>\n <span>node: <code style="color:var(--text-2);">${a(t.node||"—")}</code></span>\n <span>uptime: <code style="color:var(--text-2);">${r}</code></span>\n </div>`;e.innerHTML=s;for(const l of q){const t=document.createElement("div");t.style.cssText="margin-bottom:18px;",t.innerHTML=`<div style="font-size:11px;font-weight:700;color:var(--text-3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:${l.note?"4px":"8px"};">${a(l.label)}</div>`+(l.note?`<div style="font-size:11px;color:var(--accent);margin-bottom:8px;line-height:1.4;">${a(l.note)}</div>`:"");for(const{key:e,hint:o,default:r}of l.vars){const s=n[e]??null,l=s??r??"",i=null===s,c=r?`default: ${r}`:"not set",d=document.createElement("div");d.style.cssText="margin-bottom:8px;",d.innerHTML=`\n <div style="display:flex;align-items:baseline;gap:6px;margin-bottom:3px;">\n <span style="font-size:11px;font-family:monospace;color:var(--accent);">${a(e)}</span>\n ${i&&r?'<span style="font-size:10px;color:var(--text-3);font-family:monospace;background:var(--bg-1);padding:1px 5px;border-radius:4px;border:1px solid var(--border);">default</span>':""}\n </div>\n <div style="font-size:10px;color:var(--text-3);margin-bottom:4px;">${a(o)}</div>\n <div style="display:flex;gap:6px;align-items:center;">\n <input data-env-key="${a(e)}" data-env-default="${a(r||"")}" type="text" value="${a(l)}"\n placeholder="${a(c)}"\n class="inp-sm inp-mono inp-flex" />\n <button data-env-save="${a(e)}" style="font-size:11px;padding:5px 10px;border-radius:6px;cursor:pointer;border:1px solid var(--border);background:var(--surface-2);color:var(--text-2);white-space:nowrap;">Save</button>\n <span data-env-status="${a(e)}" style="font-size:11px;min-width:50px;"></span>\n </div>`,t.appendChild(d)}e.appendChild(t)}e.querySelectorAll("[data-env-save]").forEach(t=>{t.addEventListener("click",()=>{const o=t.dataset.envSave,n=e.querySelector(`[data-env-key="${o}"]`),a=e.querySelector(`[data-env-status="${o}"]`);n&&a&&async function(e,t,o){const n=t.value.trim();o.textContent="Saving…",o.style.color="var(--text-3)";try{const t=await fetch("/api/env-advanced",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({[e]:n||null})}),a=await t.json();a.ok?(o.textContent=n?"✓ Saved":"✓ Cleared",o.style.color="var(--green)"):(o.textContent="Error: "+(a.error||"unknown"),o.style.color="var(--red, #f87171)")}catch(a){o.textContent="Error: "+a.message,o.style.color="var(--red, #f87171)"}setTimeout(()=>{o.textContent=""},3e3)}(o,n,a)})}),e.querySelectorAll("[data-env-key]").forEach(e=>{e.addEventListener("input",()=>{const t=e.value===(e.dataset.envDefault||"");e.style.color="var(--text-1)",e.style.opacity=t?"0.65":"1"})})}catch(t){e&&(e.textContent="Could not load: "+t.message)}}export{M as A,_ as B,A as C,B as D,D as E,F,H as G,U as H,$ as I,c as J,G as a,P as b,L as c,R as d,O as e,S as f,I as g,x as h,E as i,C as j,T as k,p as l,f as m,g as n,d as o,b as p,y as q,v as r,j as s,N as t,m as u,W as v,u as w,h as x,k as y,w as z};
@@ -0,0 +1 @@
1
+ import{s as e}from"./core-utils-CmOkXgzi.js";let t=[];function n(){document.querySelectorAll(".view, .view-sessions").forEach(e=>{e.classList.remove("active"),e.style.display&&(e.style.display="")});const e=document.querySelector(".msg-bar");e&&(e.style.display="")}function l(){n(),document.getElementById("skillsView").classList.add("active"),document.querySelectorAll(".nav-item").forEach(e=>e.classList.remove("active"));const e=document.getElementById("navSkills");e&&e.classList.add("active"),i(),"function"==typeof loadPendingApprovals&&loadPendingApprovals()}function a(){n();const e=document.getElementById("runSkillsView");e&&e.classList.add("active"),document.querySelectorAll(".nav-item").forEach(e=>e.classList.remove("active"));const t=document.getElementById("navRunSkills");t&&t.classList.add("active"),s()}async function s(){const e=document.getElementById("runSkillsGrid");if(e)try{const t=((await(await fetch("/api/health")).json()).skills||[]).filter(e=>!e.error&&e.url);if(!t.length)return void(e.innerHTML='<div style="color:var(--text-3);font-size:13px;">No API skills found. Add API skills (with a URL endpoint) in the Skills tab.</div>');e.innerHTML=t.map(e=>{const t=e.defaultParams&&Object.keys(e.defaultParams).length?JSON.stringify(e.defaultParams,null,2):"{}",n=(e.paramNotes||e.description||"").slice(0,120),l=(e.name||"").replace(/"/g,"&quot;");return'<div class="card" style="display:flex;flex-direction:column;"><div class="card-title" style="margin-bottom:6px;">'+(e.name||"unnamed")+'</div><div style="font-size:12px;color:var(--text-3);margin-bottom:10px;line-height:1.4;">'+(e.description||"")+"</div>"+(n?'<div style="font-size:11px;color:var(--text-2);margin-bottom:8px;">'+n+"</div>":"")+'<label style="font-size:11px;color:var(--text-2);margin-bottom:4px;">Params (JSON)</label><textarea data-skill="'+l+'" rows="4" style="font-family:monospace;font-size:12px;width:100%;margin-bottom:10px;resize:vertical;" class="runskills-params">'+t.replace(/</g,"&lt;")+'</textarea><div style="display:flex;align-items:center;gap:8px;margin-top:auto;"><button class="btn-green" style="font-size:12px;" data-action="runSkillFromUI" data-arg="'+l+'">Run</button><span class="runskills-result" data-skill="'+l+'" style="font-size:11px;color:var(--text-3);"></span></div></div>'}).join("")}catch(t){e.innerHTML='<div style="color:var(--red);font-size:12px;">Error loading health/skills: '+(t.message||"")+"</div>"}}async function o(e){const t=document.querySelector('.runskills-params[data-skill="'+(e||"").replace(/"/g,'\\"')+'"]'),n=document.querySelector('.runskills-result[data-skill="'+(e||"").replace(/"/g,'\\"')+'"]');if(!t)return;let l={};try{l=JSON.parse(t.value.trim()||"{}")}catch(a){return void(n&&(n.textContent="Invalid JSON"))}n&&(n.textContent="Running…");try{const t=await fetch("/api/skills/"+encodeURIComponent(e)+"/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({params:l})}),a=await t.json();if(n&&(a.ok?n.textContent="Done":n.textContent=a.error||"Error",n.style.color=a.ok?"var(--green)":"var(--red)"),!a.ok)return;if(void 0!==a.result&&n){const e="string"==typeof a.result?a.result:JSON.stringify(a.result).slice(0,120);n.textContent=e+(e.length>=120?"…":"")}}catch(a){n&&(n.textContent=a.message||"Request failed",n.style.color="var(--red)")}}async function i(){const e=document.getElementById("skillsList");try{const e=await(await fetch("/api/skills")).json();t=e.skills||[],r(t)}catch(n){e&&(e.innerHTML='<div style="color:var(--text-3);font-size:12px;">Error loading skills</div>')}}function r(e){const t=document.getElementById("skillsList");if(!t)return;if(!e.length)return void(t.innerHTML='<div style="color:var(--text-3);font-size:12px;padding:8px 0;">No skills match. Add one above or copy JSONs to ~/.crewswarm/skills/</div>');const n=e.filter(e=>"api"===e.type||!e.type&&e.url),l=e.filter(e=>"knowledge"===e.type||!e.type&&!e.url);function a(e){const t="knowledge"===e.type,n=e.requiresApproval?'<span style="margin-left:6px;font-size:10px;background:rgba(251,191,36,0.15);color:var(--yellow);padding:2px 6px;border-radius:4px;">⚠️ approval</span>':"",l=t?'<span style="margin-left:6px;font-size:10px;background:rgba(99,102,241,0.15);color:#818cf8;padding:2px 6px;border-radius:4px;">knowledge</span>':'<span style="margin-left:6px;font-size:10px;background:rgba(34,197,94,0.12);color:var(--green);padding:2px 6px;border-radius:4px;">API</span>',a=e.url?' · <code style="background:var(--bg-1);padding:1px 4px;border-radius:3px;">'+(e.method||"POST")+" "+(e.url||"").slice(0,55)+"</code>":"",s=e.aliases&&e.aliases.length?'<span style="margin-left:6px;font-size:10px;color:var(--text-3);">aliases: '+e.aliases.join(", ")+"</span>":"";return'<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-2);border-radius:var(--radius);border:1px solid var(--border);"><div style="min-width:0;"><div style="display:flex;align-items:center;flex-wrap:wrap;gap:2px;"><span style="font-weight:600;font-size:13px;">'+e.name+"</span>"+l+n+s+'</div><div style="font-size:11px;color:var(--text-3);margin-top:3px;">'+(e.description||"")+a+'</div></div><div style="display:flex;gap:6px;flex-shrink:0;margin-left:12px;">'+(t?"":'<button class="btn-ghost" style="font-size:11px;" data-action="editSkill" data-arg="'+e.name+'">Edit</button>')+'<button class="btn-ghost" style="font-size:11px;color:var(--red);" data-action="deleteSkill" data-arg="'+e.name+'">Delete</button></div></div>'}function s(e,t,n){return t.length?'<div style="margin-bottom:20px;"><div style="font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);margin-bottom:8px;">'+e+' <span style="font-weight:400;opacity:.7;">('+t.length+')</span></div><div style="display:flex;flex-direction:column;gap:6px;">'+t.map(a).join("")+"</div></div>":""}t.innerHTML=s("Knowledge",l)+s("API Integrations",n)}function d(e){const n=e.toLowerCase();r(n?t.filter(e=>(e.name||"").toLowerCase().includes(n)||(e.description||"").toLowerCase().includes(n)||(e.url||"").toLowerCase().includes(n)||(e.aliases||[]).some(e=>e.toLowerCase().includes(n))):t)}function c(e){var n,l,a,s;const o=t.find(t=>t.name===e);if(!o)return;document.getElementById("skEditName").value=e,document.getElementById("addSkillFormTitle").textContent="Edit Skill",document.getElementById("saveSkillBtn").textContent="Update Skill",document.getElementById("skName").value=o.name||"",document.getElementById("skDesc").value=o.description||"",document.getElementById("skUrl").value=o.url||"";const i=document.getElementById("skMethod");for(let t=0;t<i.options.length;t++)if(i.options[t].value===o.method){i.selectedIndex=t;break}const r=(null==(n=o.auth)?void 0:n.type)||"";document.getElementById("skAuthType").value=r,document.getElementById("skAuthKey").value=(null==(l=o.auth)?void 0:l.keyFrom)||(null==(a=o.auth)?void 0:a.token)||"",document.getElementById("skAuthHeader").value=(null==(s=o.auth)?void 0:s.header)||"",document.getElementById("skRequiresApproval").checked=!!o.requiresApproval,document.getElementById("skDefaults").value=o.defaultParams&&Object.keys(o.defaultParams).length?JSON.stringify(o.defaultParams,null,2):"",g();const d=document.getElementById("addSkillForm");d.style.display="block",d.scrollIntoView({behavior:"smooth",block:"start"})}function m(){y(),document.getElementById("importSkillForm").style.display="none";const e=document.getElementById("addSkillForm");e.style.display="none"===e.style.display?"block":"none"}function u(){y();const e=document.getElementById("importSkillForm");e.style.display="none"===e.style.display?"block":"none","none"!==e.style.display&&setTimeout(()=>document.getElementById("importSkillUrl").focus(),50)}async function p(){const e=document.getElementById("importSkillUrl"),t=document.getElementById("importSkillStatus"),n=document.getElementById("importSkillBtn"),l=e.value.trim();if(!l)return t.style.color="var(--red)",void(t.textContent="Paste a URL first.");n.disabled=!0,n.textContent="Importing…",t.style.color="var(--text-3)",t.textContent="Fetching & scanning…";try{const n=await fetch("/api/skills/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({url:l})}),a=await n.json();if(!n.ok||a.error)throw new Error(a.error||"Import failed");if(a.warnings&&a.warnings.length){t.style.color="var(--yellow)";const e={cmd_skill:"⚠ executes shell commands",ssrf_risk:"⚠ targets private network",insecure_url:"⚠ non-HTTPS endpoint",no_approval:"⚠ no approval gate on write"},n=a.warnings.map(t=>e[t.split(":")[0]]||t);t.innerHTML='✓ Imported <strong>"'+a.name+'"</strong> — '+n.join(" · ")}else t.style.color="var(--green)",t.textContent='✓ Imported "'+a.name+'" — no security warnings';e.value="",await i(),a.warnings&&a.warnings.length||setTimeout(()=>{document.getElementById("importSkillForm").style.display="none",t.textContent=""},3e3)}catch(a){t.style.color="var(--red)",t.textContent="Error: "+a.message}finally{n.disabled=!1,n.textContent="Import"}}function y(){document.getElementById("skEditName").value="",document.getElementById("addSkillFormTitle").textContent="New Skill",document.getElementById("saveSkillBtn").textContent="Save Skill",document.getElementById("addSkillForm").style.display="none",["skName","skDesc","skUrl","skAuthKey","skAuthHeader","skDefaults"].forEach(e=>{const t=document.getElementById(e);t&&(t.value="")}),document.getElementById("skAuthType").value="",document.getElementById("skRequiresApproval").checked=!1,g()}function g(){const e=document.getElementById("skAuthType").value;document.getElementById("skAuthHeaderWrap").style.display="header"===e?"block":"none"}async function v(){const t=document.getElementById("skName").value.trim(),n=document.getElementById("skUrl").value.trim();if(!t||!n)return void alert("Skill name and URL are required");let l={};try{const e=document.getElementById("skDefaults").value.trim();e&&(l=JSON.parse(e))}catch{return void alert("Default Params must be valid JSON")}const a=document.getElementById("skAuthType").value,s=document.getElementById("skAuthKey").value.trim();let o={};a&&s&&(o={type:a},s.startsWith("providers.")||s.startsWith("env.")?o.keyFrom=s:o.token=s,"header"===a&&(o.header=document.getElementById("skAuthHeader").value.trim()||"X-API-Key"));const r=document.getElementById("skEditName").value.trim(),d={name:t,url:n,method:document.getElementById("skMethod").value,description:document.getElementById("skDesc").value.trim(),auth:Object.keys(o).length?o:void 0,defaultParams:l,requiresApproval:document.getElementById("skRequiresApproval").checked};try{r&&r!==t&&await fetch("/api/skills/"+r,{method:"DELETE"});const n=await fetch("/api/skills",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(d)});if(!n.ok)throw new Error(await n.text());y(),i(),e(r?"Skill updated":"Skill saved")}catch(c){e("Failed: "+c.message,"error")}}async function f(t){if(confirm('Delete skill "'+t+'"?'))try{const n=await fetch("/api/skills/"+t,{method:"DELETE"});if(!n.ok)throw new Error(await n.text());i(),e("Deleted")}catch(n){e("Delete failed: "+n.message,"error")}}export{l as a,v as b,y as c,m as d,c as e,d as f,f as g,p as i,s as l,o as r,a as s,u as t,g as u};
@@ -0,0 +1 @@
1
+ import{a as s,b as t,g as e,e as a,s as n,p as i}from"./core-utils-CmOkXgzi.js";let l=()=>{},r=()=>{};function o(s={}){l=s.hideAllViews||l,r=s.setNavActive||r}let d=null;function c(){l(),document.getElementById("testingView").classList.add("active"),r("navTesting"),s.activeTab="testing",t(),m(),g(),e("/api/tests/progress").then(s=>{s.running&&!y&&(b(),y=setInterval(b,2e3))}).catch(()=>{}),d&&clearInterval(d),d=setInterval(()=>{document.getElementById("testingView").classList.contains("active")?(m(),g()):(clearInterval(d),d=null)},3e4)}function u(s){return!s||s<=0?"-":s>=6e4?(s/6e4).toFixed(1)+"m":s>=1e3?(s/1e3).toFixed(1)+"s":Math.round(s)+"ms"}function p(s){if(!s)return"-";const t=new Date(s);return t.toLocaleDateString(void 0,{month:"short",day:"numeric"})+" "+t.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})}function v(s,t){const e=s+t;return 0===e?"-":(s/e*100).toFixed(0)+"%"}const f={unit:"Unit",integration:"Integration",e2e:"E2E",all:"All",unknown:"Other"},h={unit:"#818cf8",integration:"#34d399",e2e:"#fbbf24",all:"#60a5fa",unknown:"#94a3b8"};async function m(){var s;const t=document.getElementById("testingContent");if(t)try{const n=await e("/api/tests/summary");if(!n.latest&&!n.fileCounts)return void(t.innerHTML='<div class="empty-state">No test results found. Run tests to see results here.</div>');let i="";const l=n.fileCounts||{},r=n.testCounts||{};i+='<div class="test-launch-grid">';const o=[{key:"unit",label:"Unit",files:l.unit,tests:r.unit,cmd:"test:unit",color:h.unit},{key:"integration",label:"Integration",files:l.integration,tests:r.integration,cmd:"test:integration",color:h.integration},{key:"e2e",label:"E2E",files:l.e2e,tests:r.e2e,cmd:"test:e2e",color:h.e2e},{key:"playwright",label:"Playwright",files:l.playwright,tests:r.playwright,cmd:"test:e2e:vibe",color:"#f472b6"},{key:"crew-cli",label:"crew-cli",files:l["crew-cli"],tests:r["crew-cli"],cmd:"test",color:"#10b981"}];for(const s of o){const t=s.tests?`<span class="test-launch-tests">${s.tests} tests</span>`:"",e=s.cmd?`<button class="test-launch-btn" data-action="runTests" data-arg="${s.cmd}">▶ Run</button>`:'<span class="meta" style="font-size:10px">npx playwright test</span>';i+=`\n <div class="test-launch-card" style="border-color:${s.color}30">\n <div class="test-launch-header">\n <span class="test-launch-name" style="color:${s.color}">${s.label}</span>\n ${e}\n </div>\n <div class="test-launch-counts">\n <span class="test-launch-files">${s.files||0} files</span>\n ${t}\n </div>\n </div>`}const d=Object.values(r).reduce((s,t)=>s+(t||0),0);i+=`\n <div class="test-launch-card test-launch-total" style="border-color:var(--accent)">\n <div class="test-launch-header">\n <span class="test-launch-name" style="color:var(--accent)">All</span>\n <button class="test-launch-btn" data-action="runTests" data-arg="test:all" style="background:var(--accent);color:#fff">▶ Run All</button>\n </div>\n <div class="test-launch-counts">\n <span class="test-launch-files">${l.total||0} files</span>\n ${d?`<span class="test-launch-tests">${d}+ tests</span>`:""}\n </div>\n </div>`,i+="</div>",i+='<div class="test-section-title">Latest Results by Suite</div>',i+='<div class="test-suite-grid">';for(const t of["unit","integration","e2e","all"]){const e=null==(s=n.suites)?void 0:s[t];if(!e||!e.total&&!e.passed&&!e.failed)continue;const a=(e.passed||0)+(e.failed||0),l=e.failed>0?"test-status-fail":"test-status-pass",r=e.failed>0?"FAIL":"PASS";i+=`\n <div class="test-suite-card">\n <div class="test-suite-header">\n <span class="test-suite-name" style="color:${h[t]}">${f[t]}</span>\n <span class="test-summary-status ${l}">${r}</span>\n </div>\n <div class="test-suite-stats">\n <div><span class="test-color-pass">${e.passed||0}</span> pass</div>\n <div><span class="${e.failed>0?"test-color-fail":""}">${e.failed||0}</span> fail</div>\n <div><span class="${e.skipped>0?"test-color-skip":""}">${e.skipped||0}</span> skip</div>\n <div><strong>${e.total||0}</strong> total</div>\n </div>\n <div class="test-suite-meta">\n ${v(e.passed||0,e.failed||0)} pass rate · ${u(e.duration_ms)} · ${p(e.timestamp)}\n </div>\n <div class="test-progress-bar">\n <div class="test-progress-pass" style="width:${a>0?(e.passed||0)/a*100:0}%"></div>\n <div class="test-progress-fail" style="width:${a>0?(e.failed||0)/a*100:0}%"></div>\n </div>\n </div>`}i+="</div>";const c=[];for(const s of Object.values(n.suites||{}))s.failures&&c.push(...s.failures);if(c.length>0){i+=`<div class="test-section-title">Failures (${c.length})</div>`;for(const s of c)i+=`\n <div class="test-failure-card">\n <div class="test-failure-name">${a(s.name)}</div>\n <div class="test-failure-file">${a(s.file)}</div>\n ${s.classification&&"unknown"!==s.classification?`<span class="test-failure-class">${a(s.classification)}</span>`:""}\n ${s.error?`<pre class="test-failure-error">${a(String(s.error).slice(0,500))}</pre>`:""}\n ${s.rerun_command?`<div class="test-failure-rerun"><code>${a(s.rerun_command)}</code></div>`:""}\n </div>`}const m=[];for(const[s,t]of Object.entries(n.suites||{}))t.skips&&m.push(...t.skips.map(t=>({...t,suite:s})));if(m.length>0){i+='<details class="test-skips-section">',i+=`<summary class="test-section-title" style="cursor:pointer">Skipped (${m.length}) — click to expand</summary>`,i+='<table class="test-groups-table"><thead><tr><th>Test</th><th>File</th><th>Suite</th></tr></thead><tbody>';for(const s of m.slice(0,50))i+=`<tr><td>${a(s.name)}</td><td class="meta">${a(s.file)}</td><td><span class="test-cat-badge test-cat-${a(s.suite)}">${a(s.suite)}</span></td></tr>`;m.length>50&&(i+=`<tr><td colspan="3" class="meta">...and ${m.length-50} more</td></tr>`),i+="</tbody></table></details>"}t.innerHTML=i}catch(n){t.innerHTML=`<div class="empty-state">Failed to load test results: ${a(n.message)}</div>`}}async function g(){const s=document.getElementById("testingHistory");if(s)try{const t=await e("/api/tests/history");if(!t.history||0===t.history.length)return void(s.innerHTML='<div class="meta">No run history yet.</div>');let n='<div class="test-section-title">Run History</div>';n+='\n <table class="test-history-table">\n <thead>\n <tr>\n <th>When</th>\n <th>Suite</th>\n <th>Status</th>\n <th class="num">Pass</th>\n <th class="num">Fail</th>\n <th class="num">Skip</th>\n <th class="num">Total</th>\n <th class="num">Duration</th>\n <th class="num">Rate</th>\n </tr>\n </thead>\n <tbody>';for(const s of t.history.slice(0,25)){const t=s.failed>0?"test-color-fail":"test-color-pass",e=f[s.suite]||s.suite||"?",i=h[s.suite]||h.unknown;n+=`\n <tr data-action="loadRunDetail" data-arg="${a(s.runId)}" style="cursor:pointer" class="${s.failed>0?"test-row-fail":""}">\n <td class="meta" style="white-space:nowrap">${p(s.timestamp)}</td>\n <td><span class="test-cat-badge" style="background:${i}20;color:${i}">${e}</span></td>\n <td class="${t}" style="font-weight:600">${s.failed>0?"FAIL":"PASS"} ▸</td>\n <td class="num">${s.passed}</td>\n <td class="num ${s.failed>0?"test-color-fail":""}">${s.failed}</td>\n <td class="num ${s.skipped>0?"test-color-skip":""}">${s.skipped}</td>\n <td class="num"><strong>${s.total}</strong></td>\n <td class="num">${u(s.duration_ms)}</td>\n <td class="num">${v(s.passed,s.failed)}</td>\n </tr>`}n+="</tbody></table>",n+='<div id="testingRunDetail"></div>',s.innerHTML=n}catch(t){s.innerHTML=`<div class="meta">Failed to load history: ${a(t.message)}</div>`}}async function $(s){var t,n;const i=document.getElementById("testingRunDetail");if(i){i.innerHTML='<div class="meta" style="padding:12px">Loading run detail...</div>';try{const l=await e("/api/tests/run-detail?runId="+encodeURIComponent(s));if(l.error)return void(i.innerHTML=`<div class="meta">${a(l.error)}</div>`);let r=`<div class="test-section-title">Run Detail: ${a(s)} <span class="meta" style="font-weight:400;font-size:11px;text-transform:none">${p(l.timestamp)}</span></div>`;r+=`<div class="test-suite-meta" style="margin-bottom:12px">${l.passed} pass, ${l.failed} fail, ${l.skipped} skip, ${l.total} total · ${u(l.duration_ms)} · ${v(l.passed,l.failed)} pass rate</div>`;if(!((null==(t=l.failures)?void 0:t.length)>0||(null==(n=l.skips)?void 0:n.length)>0)&&l.total>0&&(r+='<div class="meta" style="padding:8px 0;color:var(--text-2)">No detailed failure/skip data saved for this run. Run with <code>npm run test:all</code> to generate full reports.</div>'),l.failures&&l.failures.length>0){r+=`<div class="test-section-title">Failures (${l.failures.length})</div>`;for(const s of l.failures)r+=`\n <div class="test-failure-card">\n <div class="test-failure-name">${a(s.name)}</div>\n <div class="test-failure-file">${a(s.file)}</div>\n ${s.error?`<pre class="test-failure-error">${a(String(s.error).slice(0,500))}</pre>`:""}\n ${s.rerun_command?`<div class="test-failure-rerun"><code>${a(s.rerun_command)}</code></div>`:""}\n </div>`}if(l.skips&&l.skips.length>0){r+=`<details><summary class="test-section-title" style="cursor:pointer">Skipped (${l.skips.length})</summary>`,r+='<table class="test-groups-table"><thead><tr><th>Test</th><th>File</th></tr></thead><tbody>';for(const s of l.skips.slice(0,50))r+=`<tr><td>${a(s.name)}</td><td class="meta">${a(s.file)}</td></tr>`;l.skips.length>50&&(r+=`<tr><td colspan="2" class="meta">...and ${l.skips.length-50} more</td></tr>`),r+="</tbody></table></details>"}i.innerHTML=r,i.scrollIntoView({behavior:"smooth",block:"nearest"})}catch(l){i.innerHTML=`<div class="meta">Failed: ${a(l.message)}</div>`}}}let y=null;function b(){const s=document.getElementById("testProgressBar");s&&e("/api/tests/progress").then(t=>{var e;if(!t.running&&!t.finished)return void(s.innerHTML="");const n=((t.finished||Date.now())-t.started)/1e3,i=n>=60?(n/60).toFixed(1)+"m":Math.round(n)+"s",l=t.passed+t.failed+t.skipped,r=f[null==(e=t.suite)?void 0:e.replace("test:","")]||t.suite||"Tests";if(t.running){const e=t.current_file?t.current_file.split("/").pop():"";s.innerHTML=`\n <div class="test-progress-live">\n <div class="test-progress-live-header">\n <span class="test-progress-live-status">⏳ Running ${a(r)}...</span>\n <span class="meta">${i}</span>\n </div>\n <div class="test-progress-live-stats">\n <span class="test-color-pass">${t.passed} pass</span>\n <span class="test-color-fail">${t.failed} fail</span>\n <span class="test-color-skip">${t.skipped} skip</span>\n <span>${t.files_done} files</span>\n <span>${l} tests</span>\n </div>\n ${e?`<div class="test-progress-live-file">${a(e)}</div>`:""}\n <div class="test-progress-bar" style="margin-top:6px">\n <div class="test-progress-pass" style="width:${l>0?t.passed/l*100:0}%;transition:width 0.3s"></div>\n <div class="test-progress-fail" style="width:${l>0?t.failed/l*100:0}%;transition:width 0.3s"></div>\n </div>\n </div>`}else{const e=t.failed>0?"test-color-fail":"test-color-pass",n=t.failed>0?"FAILED":"PASSED";s.innerHTML=`\n <div class="test-progress-live test-progress-done">\n <div class="test-progress-live-header">\n <span class="${e}" style="font-weight:700">✓ ${a(r)} ${n}</span>\n <span class="meta">${i}</span>\n </div>\n <div class="test-progress-live-stats">\n <span class="test-color-pass">${t.passed} pass</span>\n <span class="test-color-fail">${t.failed} fail</span>\n <span class="test-color-skip">${t.skipped} skip</span>\n <span>${t.files_done} files</span>\n </div>\n </div>`,y&&(clearInterval(y),y=null),m(),g(),setTimeout(()=>{s&&(s.innerHTML="")},1e4)}}).catch(()=>{})}async function k(s){try{n(`Starting ${s}...`),await i("/api/tests/run",{suite:s}),y&&clearInterval(y),b(),y=setInterval(b,2e3)}catch(t){n("Failed to start tests: "+t.message,!0)}}export{o as i,$ as l,k as r,c as s};
@@ -6,13 +6,13 @@
6
6
  <title>crewswarm dashboard</title>
7
7
  <link rel="icon" type="image/png" href="/favicon.png" />
8
8
  <!-- Font: system stack only to avoid CORS when dashboard (4319) and studio (3333) both load Inter from Google -->
9
- <script type="module" crossorigin src="/assets/index-GSWxxEPO.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-BeVllEj_.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/core-utils-CmOkXgzi.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/setup-wizard-CA0Or47w.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/components-BS9fQjE_.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/orchestration-Ca2DLWN-.js">
14
- <link rel="modulepreload" crossorigin href="/assets/cli-process-COMRNPqr.js">
15
- <link rel="modulepreload" crossorigin href="/assets/chat-core-3KirthZA.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/cli-process-CNZ_UBCt.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/chat-core-uXb_C0GM.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/tab-swarm-chat-tab-BNrd88-r.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/tab-waves-tab-SaJDkb4x.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/tab-workflows-tab-B-soSy1k.js">
@@ -20,18 +20,19 @@
20
20
  <link rel="modulepreload" crossorigin href="/assets/tab-services-tab-DU_LH3uG.js">
21
21
  <link rel="modulepreload" crossorigin href="/assets/tab-agents-tab-BgpIsjkw.js">
22
22
  <link rel="modulepreload" crossorigin href="/assets/tab-prompts-tab-DVkUNaJd.js">
23
- <link rel="modulepreload" crossorigin href="/assets/tab-skills-tab-BpY0uZHW.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/tab-testing-tab-CezZOZcJ.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/tab-skills-tab-DR7PJ7NB.js">
24
25
  <link rel="modulepreload" crossorigin href="/assets/tab-contacts-tab-DiOyMYth.js">
25
26
  <link rel="modulepreload" crossorigin href="/assets/tab-engines-tab-BsdZVvU0.js">
26
27
  <link rel="modulepreload" crossorigin href="/assets/tab-swarm-tab-B1AcjL1W.js">
27
- <link rel="modulepreload" crossorigin href="/assets/tab-models-tab-BLEjmd19.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/tab-models-tab-dNRgsTOO.js">
28
29
  <link rel="modulepreload" crossorigin href="/assets/tab-projects-tab-SFH4E--a.js">
29
- <link rel="modulepreload" crossorigin href="/assets/tab-settings-tab-BselH1c0.js">
30
+ <link rel="modulepreload" crossorigin href="/assets/tab-settings-tab-CuvH_Fj_.js">
30
31
  <link rel="modulepreload" crossorigin href="/assets/tab-comms-tab-kguqTIzD.js">
31
32
  <link rel="modulepreload" crossorigin href="/assets/tab-usage-tab-BIOOnB-Y.js">
32
33
  <link rel="modulepreload" crossorigin href="/assets/tab-spending-tab-DEccQHnt.js">
33
34
  <link rel="modulepreload" crossorigin href="/assets/tab-pm-loop-tab-DiAPTJXu.js">
34
- <link rel="stylesheet" crossorigin href="/assets/index-CF0aJRtC.css">
35
+ <link rel="stylesheet" crossorigin href="/assets/index-D-sRshvg.css">
35
36
  </head>
36
37
  <body>
37
38
  <!-- Skip link for keyboard navigation -->
@@ -112,6 +113,10 @@
112
113
  <button class="nav-item" id="navRunSkills" data-view="run-skills">
113
114
  <span class="nav-icon">⚡</span> Run skills
114
115
  </button>
116
+ <button class="nav-item" id="navTesting" data-view="testing">
117
+ <span class="nav-icon">🧪</span> Testing
118
+ <span class="nav-badge hidden" id="testingBadge">0</span>
119
+ </button>
115
120
  <button class="nav-item" id="navBenchmarks" data-view="benchmarks">
116
121
  <span class="nav-icon">📊</span> Benchmarks
117
122
  </button>
@@ -1177,6 +1182,8 @@
1177
1182
  <option value="deepseek-chat">DeepSeek Chat</option>
1178
1183
  <option value="llama-3.3-70b-versatile">Llama 3.3 70B (Groq)</option>
1179
1184
  <option value="gpt-4o">GPT-4o</option>
1185
+ <option value="claude-haiku-4-5-20251001">Claude Haiku 4.5 (OAuth)</option>
1186
+ <option value="claude-sonnet-4-6">Claude Sonnet 4.6 (OAuth)</option>
1180
1187
  <option value="moonshot-v1-128k">Moonshot V1 128K</option>
1181
1188
  <option value="grok-4.20-beta-0309-non-reasoning">Grok 4.2 Beta (non-reasoning)</option>
1182
1189
  <option value="grok-4.20-beta-0309-reasoning">Grok 4.2 Beta (reasoning)</option>
@@ -2405,6 +2412,22 @@
2405
2412
  </div>
2406
2413
  </div>
2407
2414
 
2415
+ <!-- OAuth / Subscription Providers (no API key needed) -->
2416
+ <div
2417
+ style="
2418
+ font-size: 11px;
2419
+ font-weight: 600;
2420
+ color: var(--text-2);
2421
+ text-transform: uppercase;
2422
+ letter-spacing: 0.08em;
2423
+ margin: 18px 0 10px;
2424
+ padding: 0 2px;
2425
+ "
2426
+ >
2427
+ Subscription Providers (no API key)
2428
+ </div>
2429
+ <div id="oauthProvidersList"></div>
2430
+
2408
2431
  <!-- Search & Research Tools -->
2409
2432
  <div
2410
2433
  style="
@@ -5536,6 +5559,32 @@
5536
5559
  </div>
5537
5560
  </div>
5538
5561
 
5562
+ <!-- Testing -->
5563
+ <div class="view" id="testingView">
5564
+ <div class="page-header">
5565
+ <div>
5566
+ <div class="page-title">Testing</div>
5567
+ <div class="page-sub">
5568
+ Test suite results, group breakdown, and run history
5569
+ </div>
5570
+ </div>
5571
+ <div style="display: flex; align-items: center; gap: 8px">
5572
+ <button
5573
+ data-action="refreshTesting"
5574
+ class="btn-ghost"
5575
+ style="font-size: 12px"
5576
+ >
5577
+ ↻ Refresh
5578
+ </button>
5579
+ </div>
5580
+ </div>
5581
+ <div id="testProgressBar"></div>
5582
+ <div id="testingContent">
5583
+ <div class="meta" style="padding: 20px">Loading test results...</div>
5584
+ </div>
5585
+ <div id="testingHistory"></div>
5586
+ </div>
5587
+
5539
5588
  <!-- Benchmarks — ZeroEval / llm-stats leaderboards -->
5540
5589
  <div class="view" id="benchmarksView">
5541
5590
  <div class="page-header">
@@ -13,7 +13,7 @@ import http from "node:http";
13
13
  import os from "node:os";
14
14
  import path from "node:path";
15
15
  import fs from "node:fs";
16
- import { spawn } from "node:child_process";
16
+ import { spawn, execFileSync } from "node:child_process";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { WebSocketServer } from "ws";
19
19
  import { shouldSkipGeminiPassthroughLine } from "../../lib/gemini-cli-passthrough-noise.mjs";
@@ -752,60 +752,54 @@ function createCodexStreamRelay(onChunk) {
752
752
  }
753
753
 
754
754
  function createCrewCliStreamRelay(onChunk) {
755
- let transcript = "";
756
- let rawTranscript = "";
757
- let lineBuffer = "";
758
- let collectingAssistant = false;
759
-
760
- const appendAssistant = (text) => {
761
- const normalized = String(text || "").replace(/\r/g, "").trimEnd();
762
- if (!normalized) return;
763
- transcript = appendNormalizedChunk(transcript, normalized);
764
- onChunk?.(`${normalized}\n`);
765
- };
766
-
767
- const handleLine = (line) => {
768
- const cleaned = stripAnsi(line).replace(/\r/g, "");
769
- rawTranscript = appendNormalizedChunk(rawTranscript, cleaned);
770
- const trimmed = cleaned.trim();
771
- if (!trimmed) {
772
- if (collectingAssistant && transcript) {
773
- appendAssistant("");
774
- }
775
- return;
776
- }
777
-
778
- if (trimmed === "--- Agent Response ---") {
779
- collectingAssistant = true;
780
- return;
781
- }
782
-
783
- if (trimmed === "Pipeline timeline:") {
784
- collectingAssistant = false;
785
- return;
786
- }
787
-
788
- if (collectingAssistant) {
789
- appendAssistant(cleaned);
790
- }
791
- };
755
+ // Buffer ALL output — don't stream anything until we can extract the response.
756
+ // crew-cli emits logs + a JSON envelope; we only want the response field.
757
+ let rawOutput = "";
792
758
 
793
759
  return {
794
760
  push(chunk) {
795
- lineBuffer += chunk.toString("utf8");
796
- while (lineBuffer.includes("\n")) {
797
- const newlineIndex = lineBuffer.indexOf("\n");
798
- const line = lineBuffer.slice(0, newlineIndex);
799
- lineBuffer = lineBuffer.slice(newlineIndex + 1);
800
- handleLine(line);
801
- }
761
+ rawOutput += chunk.toString("utf8");
802
762
  },
803
763
  finish() {
804
- if (lineBuffer.trim()) {
805
- handleLine(lineBuffer);
806
- lineBuffer = "";
764
+ const cleaned = stripAnsi(rawOutput).replace(/\r/g, "");
765
+
766
+ // Strategy 1: Find JSON envelope with "response" field
767
+ const jsonMatch = cleaned.match(/\{[\s\S]*"kind":\s*"[^"]+\.result"[\s\S]*\}/);
768
+ if (jsonMatch) {
769
+ try {
770
+ const parsed = JSON.parse(jsonMatch[0]);
771
+ if (parsed.response) {
772
+ onChunk?.(parsed.response);
773
+ return parsed.response;
774
+ }
775
+ } catch { /* fall through to other strategies */ }
776
+ }
777
+
778
+ // Strategy 2: Extract "response" field via regex (handles malformed JSON)
779
+ const respMatch = cleaned.match(/"response":\s*"((?:[^"\\]|\\.)*)"/);
780
+ if (respMatch) {
781
+ const response = respMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\t/g, "\t");
782
+ onChunk?.(response);
783
+ return response;
784
+ }
785
+
786
+ // Strategy 3: Legacy "--- Agent Response ---" marker
787
+ const markerIdx = cleaned.indexOf("--- Agent Response ---");
788
+ if (markerIdx >= 0) {
789
+ let response = cleaned.slice(markerIdx + "--- Agent Response ---".length);
790
+ const timelineIdx = response.indexOf("Pipeline timeline:");
791
+ if (timelineIdx >= 0) response = response.slice(0, timelineIdx);
792
+ response = response.trim();
793
+ if (response) {
794
+ onChunk?.(response);
795
+ return response;
796
+ }
807
797
  }
808
- return transcript.trim() || summarizeCliFailure("crew-cli", rawTranscript);
798
+
799
+ // Fallback: send raw output (stripped of common log prefixes)
800
+ const fallback = summarizeCliFailure("crew-cli", cleaned);
801
+ onChunk?.(fallback);
802
+ return fallback;
809
803
  },
810
804
  };
811
805
  }
@@ -957,6 +951,20 @@ function createCliRelay(engine, onChunk, onDone) {
957
951
  return createDefaultCliRelay(onChunk);
958
952
  }
959
953
 
954
+ function isClaudeOauthAuthenticated() {
955
+ try {
956
+ const output = execFileSync("claude", ["auth", "status"], {
957
+ encoding: "utf8",
958
+ stdio: ["ignore", "pipe", "ignore"],
959
+ }).trim().toLowerCase();
960
+ if (!output) return false;
961
+ if (/"loggedin"\s*:\s*true/.test(output)) return true;
962
+ return output.includes("logged in");
963
+ } catch {
964
+ return false;
965
+ }
966
+ }
967
+
960
968
  function broadcastTerminalMessage(sessionId, payload) {
961
969
  const session = terminalSessions.get(sessionId);
962
970
  if (!session) return;
@@ -1093,12 +1101,13 @@ export function getCliCommand(engine, projectDir, message, modelOverride, resume
1093
1101
  command: binary,
1094
1102
  args: codexArgs,
1095
1103
  stdin: null,
1104
+ stripEnv: ["OPENAI_API_KEY"],
1096
1105
  };
1097
1106
  }
1098
1107
  case "claude":
1099
1108
  // Claude Code uses OAuth — no API key needed
1100
1109
  {
1101
- const args = ["-p", "--setting-sources", "user", "--output-format", "stream-json", "--verbose"];
1110
+ const args = ["-p", "--setting-sources", "user", "--output-format", "stream-json", "--verbose", "--permission-mode", "auto"];
1102
1111
  const model = modelOverride || process.env.CREWSWARM_CLAUDE_CODE_MODEL || "";
1103
1112
  // Add workspace directory context
1104
1113
  if (projectDir) args.push("--add-dir", projectDir);
@@ -1110,7 +1119,7 @@ export function getCliCommand(engine, projectDir, message, modelOverride, resume
1110
1119
  command: "claude",
1111
1120
  args,
1112
1121
  stdin: null,
1113
- stripEnv: ["CLAUDECODE", "CLAUDE_CODE"],
1122
+ stripEnv: ["CLAUDECODE", "CLAUDE_CODE", "ANTHROPIC_API_KEY"],
1114
1123
  };
1115
1124
  }
1116
1125
  case "cursor":
@@ -1153,6 +1162,7 @@ export function getCliCommand(engine, projectDir, message, modelOverride, resume
1153
1162
  command: "gemini",
1154
1163
  args,
1155
1164
  stdin: null,
1165
+ stripEnv: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
1156
1166
  };
1157
1167
  }
1158
1168
  case "opencode":
@@ -1165,7 +1175,7 @@ export function getCliCommand(engine, projectDir, message, modelOverride, resume
1165
1175
  model = cfg.opencodeModel || "";
1166
1176
  } catch {}
1167
1177
  }
1168
- if (!model) model = "opencode/gpt-5.2";
1178
+ if (!model) model = "openai/gpt-5.2-codex";
1169
1179
  const args = ["run", "-m", model, message];
1170
1180
  // Add workspace directory context
1171
1181
  if (projectDir) args.push("--dir", projectDir);
@@ -1175,12 +1185,13 @@ export function getCliCommand(engine, projectDir, message, modelOverride, resume
1175
1185
  command: "opencode",
1176
1186
  args,
1177
1187
  stdin: null,
1188
+ stripEnv: ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY"],
1178
1189
  };
1179
1190
  }
1180
1191
  case "crew-cli": {
1181
1192
  const crewBin = path.join(__dirname, "..", "..", "crew-cli", "bin", "crew.js");
1182
1193
  const model = modelOverride || process.env.CREWSWARM_CREW_CLI_MODEL || "";
1183
- const crewArgs = [crewBin, "chat", message, ...(projectDir ? ["--project", projectDir] : []), ...(model ? ["--model", model] : [])];
1194
+ const crewArgs = [crewBin, "chat", message, "--apply", "--json", ...(projectDir ? ["--project", projectDir] : []), ...(model ? ["--model", model] : [])];
1184
1195
  // Resume: crew-cli supports --session for conversation continuity
1185
1196
  if (resumeSession?.sessionId) crewArgs.push("--session", resumeSession.sessionId);
1186
1197
  return {
@@ -1214,6 +1225,10 @@ export function runCli({
1214
1225
  resume = true,
1215
1226
  }) {
1216
1227
  return new Promise((resolve, reject) => {
1228
+ if (engine === "claude" && !isClaudeOauthAuthenticated()) {
1229
+ reject(new Error("Claude Code requires CLI OAuth login. Run `claude auth login` and try again."));
1230
+ return;
1231
+ }
1217
1232
  // Look up existing session for resume
1218
1233
  const resumeKey = getCliResumeKey(engine, projectDir);
1219
1234
  const resumeSession = resume ? cliResumeSessions.get(resumeKey) : undefined;
@@ -1857,6 +1872,36 @@ export const server = http.createServer(async (req, res) => {
1857
1872
  return;
1858
1873
  }
1859
1874
 
1875
+ // GET /api/studio/git-diff — return changed files with old/new content for diff preview
1876
+ if (parsedUrl.pathname === "/api/studio/git-diff" && req.method === "GET") {
1877
+ const projDir = parsedUrl.searchParams.get("projectDir") || WORKSPACE_DIR;
1878
+ const resolved = resolveStudioProjectPath(projDir, WORKSPACE_DIR);
1879
+ try {
1880
+ const { execSync } = await import("node:child_process");
1881
+ const hasGit = fs.existsSync(path.join(resolved, ".git"));
1882
+ if (!hasGit) {
1883
+ sendJson(res, 200, { ok: true, files: [], message: "Not a git repository" });
1884
+ return;
1885
+ }
1886
+ const filesRaw = execSync("git diff --name-only", { cwd: resolved, encoding: "utf8", timeout: 3000 }).trim();
1887
+ const files = filesRaw ? filesRaw.split("\n").filter(Boolean) : [];
1888
+ // For each changed file, get old (HEAD) and new (working tree) content
1889
+ const changes = [];
1890
+ for (const f of files.slice(0, 20)) {
1891
+ try {
1892
+ let oldContent = "";
1893
+ try { oldContent = execSync(`git show HEAD:${f}`, { cwd: resolved, encoding: "utf8", timeout: 3000 }); } catch { /* new file */ }
1894
+ const newContent = fs.readFileSync(path.join(resolved, f), "utf8");
1895
+ changes.push({ path: f, oldContent, newContent });
1896
+ } catch { /* skip unreadable */ }
1897
+ }
1898
+ sendJson(res, 200, { ok: true, files, changes });
1899
+ } catch (e) {
1900
+ sendJson(res, 200, { ok: true, files: [], changes: [], error: e.message });
1901
+ }
1902
+ return;
1903
+ }
1904
+
1860
1905
  if (parsedUrl.pathname === "/api/studio/chat/unified" && req.method === "POST") {
1861
1906
  try {
1862
1907
  const body = await readBody(req);
@@ -313,7 +313,7 @@ function existsSync(path) {
313
313
  function getDefaultModelForCLI(cliName) {
314
314
  switch (cliName) {
315
315
  case "opencode":
316
- return process.env.CREWSWARM_OPENCODE_MODEL || "groq/moonshotai/kimi-k2-instruct-0905";
316
+ return process.env.CREWSWARM_OPENCODE_MODEL || "openai/gpt-5.4";
317
317
  case "cursor":
318
318
  return process.env.CURSOR_DEFAULT_MODEL || "gemini-3-flash";
319
319
  case "claude":
@@ -14,6 +14,7 @@ export function shouldDropPassthroughStderrLine(engine, line) {
14
14
  if (/rmcp::/i.test(l)) return true;
15
15
  if (/error decoding response body.*initialized notification/i.test(l))
16
16
  return true;
17
+ if (/\[Executor\]\s+\w+\s+\((OAuth|API)\)/i.test(l)) return true;
17
18
  if (
18
19
  engine === "codex" &&
19
20
  /worker quit with fatal/i.test(l) &&
@@ -198,7 +198,7 @@ export function getProjectMessageStats(projectId) {
198
198
 
199
199
  const stats = {
200
200
  total: messages.length,
201
- bySo: {},
201
+ bySource: {},
202
202
  byAgent: {},
203
203
  oldestMessage: null,
204
204
  newestMessage: null,
@@ -206,8 +206,6 @@ export function getProjectMessageStats(projectId) {
206
206
  assistantMessages: 0
207
207
  };
208
208
 
209
- stats.bySource = {};
210
-
211
209
  for (const msg of messages) {
212
210
  // Count by source
213
211
  stats.bySource[msg.source] = (stats.bySource[msg.source] || 0) + 1;
@@ -381,8 +379,8 @@ export function buildMessageTree(projectId, rootId = null) {
381
379
  if (msg.parentId && messageMap.has(msg.parentId)) {
382
380
  // Add to parent's children
383
381
  messageMap.get(msg.parentId).children.push(node);
384
- } else if (!msg.parentId || rootId === msg.id) {
385
- // Root node (no parent) or requested root
382
+ } else {
383
+ // Root node (no parent), requested root, or orphan (parentId references missing message)
386
384
  roots.push(node);
387
385
  }
388
386
  }
@@ -19,7 +19,7 @@ import { homedir } from "node:os";
19
19
  const CLI_PROCESS_LOG = join(homedir(), ".crewswarm", "logs", "cli-processes.jsonl");
20
20
  const CLI_PROCESS_STATE = join(homedir(), ".crewswarm", "cli-process-state.json");
21
21
 
22
- mkdirSync(join(homedir(), ".crewswarm", "logs"), { recursive: true });
22
+ try { mkdirSync(join(homedir(), ".crewswarm", "logs"), { recursive: true }); } catch { /* read-only homedir */ }
23
23
 
24
24
  // Active processes: { processId: { pid, agent, cli, task, startTime, lastActivity, status, chatId, sessionId } }
25
25
  let activeProcesses = new Map();
@@ -121,10 +121,11 @@ export function completeCLIProcess(processId, result) {
121
121
  log("info", "CLI process completed", { processId, ...result });
122
122
 
123
123
  // Remove from active list after 30s (keep recent history visible)
124
- setTimeout(() => {
124
+ const timer = setTimeout(() => {
125
125
  activeProcesses.delete(processId);
126
126
  saveState();
127
127
  }, 30000);
128
+ timer.unref(); // Don't prevent process exit in tests
128
129
  }
129
130
 
130
131
  /**
@@ -185,6 +185,7 @@ export function unlinkIdentity(contactId) {
185
185
  SET platform_links = NULL
186
186
  WHERE contact_id = ?
187
187
  `).run(contactId);
188
+ return true;
188
189
  }
189
190
 
190
191
  /**