crewswarm 0.9.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -1
- package/README.md +58 -9
- package/apps/dashboard/README.md +49 -0
- package/apps/dashboard/dist/assets/{index-D-sRshvg.css → index-C5-vlIwl.css} +1 -1
- package/apps/dashboard/dist/assets/index-CSooN9fi.js +2 -0
- package/apps/dashboard/dist/assets/index-CSooN9fi.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DcXD5TQY.js +1 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DcXD5TQY.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-Ea5K-rsb.js +1 -0
- package/apps/dashboard/dist/index.html +85 -7
- package/apps/dashboard/dist/index.html.br +0 -0
- package/contrib/openclaw-plugin/index.ts +20 -11
- package/install.sh +2 -2
- package/lib/autoharness/index.mjs +151 -1
- package/lib/chat/history.mjs +1 -1
- package/lib/contacts/identity-linker.mjs +24 -3
- package/lib/contacts/index.mjs +2 -1
- package/lib/crew-lead/chat-handler.mjs +56 -33
- package/lib/crew-lead/llm-caller.mjs +71 -14
- package/lib/crew-lead/prompts.mjs +4 -2
- package/lib/crew-lead/wave-dispatcher.mjs +53 -3
- package/lib/crew-lead/worktree.mjs +258 -0
- package/lib/crew-lead/ws-router.mjs +43 -0
- package/lib/engines/rt-envelope.mjs +4 -1
- package/lib/memory/relevance-scorer.mjs +199 -0
- package/lib/memory/shared-adapter.mjs +85 -19
- package/package.json +10 -3
- package/scripts/dashboard.mjs +398 -28
- package/scripts/health-check.mjs +70 -28
- package/scripts/install-docker.sh +1 -1
- package/scripts/restart-all-from-repo.sh +25 -21
- package/scripts/start.mjs +81 -26
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js +0 -2
- package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
- package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js +0 -1
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +0 -1
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as t,b as s,g as e,e as a,s as n,p as i}from"./core-utils-CmOkXgzi.js";let l=()=>{},r=()=>{};function o(t={}){l=t.hideAllViews||l,r=t.setNavActive||r}let c=null,d=null,p="";function u(){l(),document.getElementById("testingView").classList.add("active"),r("navTesting"),t.activeTab="testing",s(),y(),b(),E(),async function(){try{const t=await e("/api/tests/stale");$=new Set((t.stale||[]).map(t=>t.file))}catch{}}(),e("/api/tests/progress").then(t=>{t.running&&!k&&(x(),k=setInterval(x,2e3),L())}).catch(()=>{}),c&&clearInterval(c),c=setInterval(()=>{document.getElementById("testingView").classList.contains("active")?(y(),b()):(clearInterval(c),c=null)},3e4)}function f(t){return!t||t<=0?"-":t>=6e4?(t/6e4).toFixed(1)+"m":t>=1e3?(t/1e3).toFixed(1)+"s":Math.round(t)+"ms"}function h(t){if(!t)return"-";const s=new Date(t);return s.toLocaleDateString(void 0,{month:"short",day:"numeric"})+" "+s.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})}function v(t,s){const e=t+s;return 0===e?"-":(t/e*100).toFixed(0)+"%"}const g={unit:"Unit",integration:"Integration",e2e:"E2E",playwright:"Playwright","crew-cli":"crew-cli",all:"All",unknown:"Other"},m={unit:"#818cf8",integration:"#34d399",e2e:"#fbbf24",playwright:"#f472b6","crew-cli":"#10b981",all:"#60a5fa",unknown:"#94a3b8"};let $=new Set;async function y(){var t;const s=document.getElementById("testingContent");if(s)try{const n=await e("/api/tests/summary");if(!n.latest&&!n.fileCounts)return void(s.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:m.unit},{key:"integration",label:"Integration",files:l.integration,tests:r.integration,cmd:"test:integration",color:m.integration},{key:"e2e",label:"E2E",files:l.e2e,tests:r.e2e,cmd:"test:e2e",color:m.e2e},{key:"playwright",label:"Playwright",files:l.playwright,tests:r.playwright,cmd:"test:playwright",color:"#f472b6"},{key:"crew-cli",label:"crew-cli",files:l["crew-cli"],tests:r["crew-cli"],cmd:"test",color:"#10b981"}];for(const t of o){const s=t.tests?`<span class="test-launch-tests">${t.tests} tests</span>`:"",e=t.cmd?`<button class="test-launch-btn" data-action="runTests" data-arg="${t.cmd}">▶ Run</button>`:'<span class="meta" style="font-size:10px">npx playwright test</span>';i+=`\n <div class="test-launch-card" style="border-color:${t.color}30">\n <div class="test-launch-header">\n <span class="test-launch-name" style="color:${t.color}">${t.label}</span>\n ${e}\n </div>\n <div class="test-launch-counts">\n <span class="test-launch-files">${t.files||0} files</span>\n ${s}\n </div>\n </div>`}const c=Object.values(r).reduce((t,s)=>t+(s||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 ${c?`<span class="test-launch-tests">${c}+ 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 s of["unit","integration","e2e","playwright","crew-cli","all"]){const e=null==(t=n.suites)?void 0:t[s];if(!e||!e.total&&!e.passed&&!e.failed)continue;const l=(e.passed||0)+(e.failed||0),r=e.failed>0?"test-status-fail":"test-status-pass",o=e.failed>0?"FAIL":"PASS",c=m[s];let d="";if(e.tests&&e.tests.length>0){const t=new Map;for(const s of e.tests){const e=s.file||"unknown";t.has(e)||t.set(e,{pass:0,fail:0,skip:0});const a=t.get(e);"pass"===s.status?a.pass++:"fail"===s.status?a.fail++:"skip"===s.status&&a.skip++}d='<div class="test-file-list">';for(const[e,n]of t){const t=e.split("/").pop(),i=e.replace(/^\/.*?CrewSwarm\//,""),l=$.has(i)||$.has(e)?'<span class="test-stale-badge" title="Source changed since last run">⚠️ stale</span>':"";d+=`\n <div class="test-file-row">\n <span class="test-file-dot">${n.fail>0?"🔴":"🟢"}</span>\n <span class="test-file-name" title="${a(e)}">${a(t)}</span>\n ${l}\n <span class="test-file-counts"><span class="test-color-pass">${n.pass}p</span> <span class="${n.fail>0?"test-color-fail":""}">${n.fail}f</span></span>\n <button class="test-file-run-btn" data-action="runSingleFile" data-arg="${a(s)}" data-arg2="${a(i)}" title="Run this file only">▶</button>\n </div>`}d+="</div>"}i+=`\n <div class="test-suite-card">\n <div class="test-suite-header">\n <span class="test-suite-name" style="color:${c}">${g[s]}</span>\n <span class="test-summary-status ${r}">${o}</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 · ${f(e.duration_ms)} · ${h(e.timestamp)}\n </div>\n <div class="test-progress-bar">\n <div class="test-progress-pass" style="width:${l>0?(e.passed||0)/l*100:0}%"></div>\n <div class="test-progress-fail" style="width:${l>0?(e.failed||0)/l*100:0}%"></div>\n </div>\n ${d}\n </div>`}i+="</div>";const d=[];for(const t of Object.values(n.suites||{}))t.failures&&d.push(...t.failures);if(d.length>0){i+=`<div class="test-section-title">Failures (${d.length})</div>`;for(const t of d){const s="fail-"+Math.random().toString(36).slice(2),e=(t.file||"").replace(/^\/.*?CrewSwarm\//,""),n=t.rerun_command||"",l=(t.error||"").split("\n").slice(0,10).join("\n"),r=`${(t.name||"").toLowerCase().replace(/[^a-z0-9]+/g,"-").slice(0,80)}/test-failed-1.png`,o=(t.file||"").includes(".spec.")||(t.file||"").includes("playwright")?`\n <div class="test-failure-screenshot" id="ss-${a(s)}">\n <img src="/api/tests/screenshot?path=${encodeURIComponent(r)}"\n class="test-screenshot-thumb"\n alt="Failure screenshot"\n onerror="this.parentElement.style.display='none'"\n onclick="this.classList.toggle('test-screenshot-expanded')"\n title="Click to expand" />\n </div>`:"";i+=`\n <div class="test-failure-card test-failure-expandable" id="${a(s)}">\n <div class="test-failure-header" data-action="toggleFailure" data-arg="${a(s)}">\n <span class="test-failure-toggle">▶</span>\n <span class="test-failure-name">${a(t.name)}</span>\n <span class="test-failure-file-inline">${a(t.file||"")}</span>\n ${t.classification&&"unknown"!==t.classification?`<span class="test-failure-class">${a(t.classification)}</span>`:""}\n </div>\n <div class="test-failure-detail" style="display:none">\n ${t.error?`<pre class="test-failure-error">${a(String(t.error).slice(0,500))}</pre>`:""}\n ${l?`<pre class="test-failure-stack">${a(l)}</pre>`:""}\n ${o}\n <div class="test-failure-actions">\n ${e?`<a class="test-failure-link" href="#" onclick="return false" title="${a(e)}">${a(e.split("/").pop())}</a>`:""}\n ${n?`<div class="test-failure-rerun">\n <code>${a(n)}</code>\n <button class="test-copy-btn" data-action="copyText" data-arg="${a(n)}" title="Copy rerun command">Copy</button>\n </div>`:""}\n </div>\n </div>\n </div>`}}const p=[];for(const[t,s]of Object.entries(n.suites||{}))s.skips&&p.push(...s.skips.map(s=>({...s,suite:t})));if(p.length>0){i+='<details class="test-skips-section">',i+=`<summary class="test-section-title" style="cursor:pointer">Skipped (${p.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 t of p.slice(0,50))i+=`<tr><td>${a(t.name)}</td><td class="meta">${a(t.file)}</td><td><span class="test-cat-badge test-cat-${a(t.suite)}">${a(t.suite)}</span></td></tr>`;p.length>50&&(i+=`<tr><td colspan="3" class="meta">...and ${p.length-50} more</td></tr>`),i+="</tbody></table></details>"}s.innerHTML=i}catch(n){s.innerHTML=`<div class="empty-state">Failed to load test results: ${a(n.message)}</div>`}}async function b(){const t=document.getElementById("testingHistory");if(t)try{const s=await e("/api/tests/history");if(!s.history||0===s.history.length)return void(t.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 t of s.history.slice(0,25)){const s=t.failed>0?"test-color-fail":"test-color-pass",e=g[t.suite]||t.suite||"?",i=m[t.suite]||m.unknown;n+=`\n <tr data-action="loadRunDetail" data-arg="${a(t.runId)}" style="cursor:pointer" class="${t.failed>0?"test-row-fail":""}">\n <td class="meta" style="white-space:nowrap">${h(t.timestamp)}</td>\n <td><span class="test-cat-badge" style="background:${i}20;color:${i}">${e}</span></td>\n <td class="${s}" style="font-weight:600">${t.failed>0?"FAIL":"PASS"} ▸</td>\n <td class="num">${t.passed}</td>\n <td class="num ${t.failed>0?"test-color-fail":""}">${t.failed}</td>\n <td class="num ${t.skipped>0?"test-color-skip":""}">${t.skipped}</td>\n <td class="num"><strong>${t.total}</strong></td>\n <td class="num">${f(t.duration_ms)}</td>\n <td class="num">${v(t.passed,t.failed)}</td>\n </tr>`}n+="</tbody></table>",n+='<div id="testingRunDetail"></div>',t.innerHTML=n}catch(s){t.innerHTML=`<div class="meta">Failed to load history: ${a(s.message)}</div>`}}async function w(t){var s,n,i;const l=document.getElementById("testingRunDetail");if(l){l.innerHTML='<div class="meta" style="padding:12px">Loading run detail...</div>';try{const r=await e("/api/tests/run-detail?runId="+encodeURIComponent(t));if(r.error)return void(l.innerHTML=`<div class="meta">${a(r.error)}</div>`);let o=`<div class="test-section-title">Run Detail: ${a(t)} <span class="meta" style="font-weight:400;font-size:11px;text-transform:none">${h(r.timestamp)}</span></div>`;o+=`<div class="test-suite-meta" style="margin-bottom:12px">${r.passed} pass, ${r.failed} fail, ${r.skipped} skip, ${r.total} total · ${f(r.duration_ms)} · ${v(r.passed,r.failed)} pass rate</div>`;if(!((null==(s=r.failures)?void 0:s.length)>0||(null==(n=r.skips)?void 0:n.length)>0)&&r.total>0&&(o+='<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>'),r.failures&&r.failures.length>0){o+=`<div class="test-section-title">Failures (${r.failures.length})</div>`;for(const t of r.failures){const s="rd-fail-"+Math.random().toString(36).slice(2),e=t.rerun_command||(null==(i=t.selector)?void 0:i.command)||"";o+=`\n <div class="test-failure-card test-failure-expandable" id="${a(s)}">\n <div class="test-failure-header" data-action="toggleFailure" data-arg="${a(s)}">\n <span class="test-failure-toggle">▶</span>\n <span class="test-failure-name">${a(t.name)}</span>\n </div>\n <div class="test-failure-detail" style="display:none">\n <div class="test-failure-file">${a(t.file)}</div>\n ${t.error?`<pre class="test-failure-error">${a(String(t.error).slice(0,500))}</pre>`:""}\n ${t.error_stack?`<pre class="test-failure-stack">${a(String(t.error_stack).split("\n").slice(0,10).join("\n"))}</pre>`:""}\n <div class="test-failure-actions">\n ${e?`<div class="test-failure-rerun">\n <code>${a(e)}</code>\n <button class="test-copy-btn" data-action="copyText" data-arg="${a(e)}" title="Copy rerun command">Copy</button>\n </div>`:""}\n </div>\n </div>\n </div>`}}if(r.skips&&r.skips.length>0){o+=`<details><summary class="test-section-title" style="cursor:pointer">Skipped (${r.skips.length})</summary>`,o+='<table class="test-groups-table"><thead><tr><th>Test</th><th>File</th></tr></thead><tbody>';for(const t of r.skips.slice(0,50))o+=`<tr><td>${a(t.name)}</td><td class="meta">${a(t.file)}</td></tr>`;r.skips.length>50&&(o+=`<tr><td colspan="2" class="meta">...and ${r.skips.length-50} more</td></tr>`),o+="</tbody></table></details>"}l.innerHTML=o,l.scrollIntoView({behavior:"smooth",block:"nearest"})}catch(r){l.innerHTML=`<div class="meta">Failed: ${a(r.message)}</div>`}}}let k=null;function x(){const t=document.getElementById("testProgressBar");t&&e("/api/tests/progress").then(s=>{var e;if(!s.running&&!s.finished)return void(t.innerHTML="");const n=((s.finished||Date.now())-s.started)/1e3,i=n>=60?(n/60).toFixed(1)+"m":Math.round(n)+"s",l=s.passed+s.failed+s.skipped,r=g[null==(e=s.suite)?void 0:e.replace("test:","")]||s.suite||"Tests";if(s.running){const e=s.current_file?s.current_file.split("/").pop():"";t.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 <button class="test-stop-btn" data-action="stopTests" title="Stop running tests">■ Stop</button>\n </div>\n <div class="test-progress-live-stats">\n <span class="test-color-pass">${s.passed} pass</span>\n <span class="test-color-fail">${s.failed} fail</span>\n <span class="test-color-skip">${s.skipped} skip</span>\n <span>${s.files_done}${s.files_total?"/"+s.files_total:""} 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:${s.files_total>0?s.files_done/s.files_total*100:l>0?s.passed/l*100:0}%;transition:width 0.3s;background:#22c55e"></div>\n <div class="test-progress-fail" style="width:${s.files_total>0?0:l>0?s.failed/l*100:0}%;transition:width 0.3s"></div>\n </div>\n </div>`}else{const e=s.failed>0?"test-color-fail":"test-color-pass",n=s.failed>0?"FAILED":"PASSED";t.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">${s.passed} pass</span>\n <span class="test-color-fail">${s.failed} fail</span>\n <span class="test-color-skip">${s.skipped} skip</span>\n <span>${s.files_done} files</span>\n </div>\n </div>`,k&&(clearInterval(k),k=null),M(),y(),b(),E(),setTimeout(()=>{t&&(t.innerHTML="")},1e4)}}).catch(()=>{})}async function S(t){try{n(`Starting ${t}...`),await i("/api/tests/run",{suite:t}),k&&clearInterval(k),x(),k=setInterval(x,2e3),L()}catch(s){n("Failed to start tests: "+s.message,!0)}}async function T(){try{(await i("/api/tests/stop",{})).stopped?(n("Tests stopped."),k&&(clearInterval(k),k=null),M(),x(),setTimeout(()=>refreshTestData(),1e3)):n("No running tests to stop.")}catch(t){n("Failed to stop tests: "+t.message,!0)}}async function I(t,s){const e={unit:"test:unit",integration:"test:integration",e2e:"test:e2e",all:"test:all",unknown:"test:unit"}[t]||"test:unit";try{n(`Running ${s.split("/").pop()}...`),await i("/api/tests/run",{suite:e,file:s}),k&&clearInterval(k),x(),k=setInterval(x,2e3),L()}catch(a){n("Failed to start test: "+a.message,!0)}}function L(){M(),p="";const t=function(){var t;let s=document.getElementById("testStreamPanel");if(!s){const e=document.getElementById("testProgressBar");if(!e)return null;s=document.createElement("div"),s.id="testStreamPanel",s.className="test-stream-panel",s.style.display="none",s.innerHTML='\n <div class="test-stream-header">\n <span>Live Output</span>\n <button class="test-stream-close" onclick="document.getElementById(\'testStreamPanel\').style.display=\'none\'">✕</button>\n </div>\n <pre id="testStreamPre" class="test-stream-pre"></pre>',null==(t=e.parentNode)||t.insertBefore(s,e.nextSibling)}return s}();t&&(t.style.display="block",t.querySelector("pre").textContent=""),d=new EventSource("/api/tests/stream"),d.onmessage=t=>{try{const s=JSON.parse(t.data);s.reset?p=s.text||"":s.text&&(p+=s.text),s.done&&M();const e=document.getElementById("testStreamPre");e&&(e.textContent=p,e.scrollTop=e.scrollHeight)}catch{}},d.onerror=()=>{M()}}function M(){d&&(d.close(),d=null)}function F(t){const s=document.getElementById(t);if(!s)return;const e=s.querySelector(".test-failure-detail"),a=s.querySelector(".test-failure-toggle");if(!e)return;const n="none"!==e.style.display;e.style.display=n?"none":"block",a&&(a.textContent=n?"▶":"▼")}async function E(){const t=document.getElementById("testingChart");if(t)try{const s=((await e("/api/tests/history")).history||[]).slice(0,20).reverse();if(0===s.length)return void(t.innerHTML="");const a=600,n=120,i=30,l=2,r=Math.max(...s.map(t=>(t.passed||0)+(t.failed||0)),1),o=Math.floor((a-2*i-l*(s.length-1))/s.length);let c="",d="";s.forEach((t,e)=>{const a=i+e*(o+l),p=(t.passed||0)+(t.failed||0),u=p>0?Math.round((t.passed||0)/r*(n-i)):0,f=p>0?Math.round((t.failed||0)/r*(n-i)):0,v=n-i-u-f;if(f>0&&(c+=`<rect x="${a}" y="${n-i-f}" width="${o}" height="${f}" fill="#ef4444" rx="1" opacity="0.85"><title>${h(t.timestamp)} — ${t.failed} fail</title></rect>`),u>0&&(c+=`<rect x="${a}" y="${v}" width="${o}" height="${u}" fill="#22c55e" rx="1" opacity="0.85"><title>${h(t.timestamp)} — ${t.passed} pass</title></rect>`),e%5==0||e===s.length-1){const s=t.timestamp?new Date(t.timestamp).toLocaleDateString(void 0,{month:"short",day:"numeric"}):"";d+=`<text x="${a+o/2}" y="${n-2}" text-anchor="middle" font-size="9" fill="var(--text-3, #888)">${s}</text>`}});const p=`<svg viewBox="0 0 ${a} ${n}" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:${a}px;height:${n}px;display:block">\n <line x1="${i}" y1="${n-i}" x2="${a-i}" y2="${n-i}" stroke="var(--border,#333)" stroke-width="1"/>\n ${c}\n ${d}\n </svg>`;t.innerHTML=`\n <div class="test-section-title">Run History (last ${s.length})\n <span class="test-chart-legend">\n <span style="color:#22c55e">■</span> Pass\n <span style="color:#ef4444">■</span> Fail\n </span>\n </div>\n <div class="test-chart-wrap">${p}</div>`}catch{}}function H(t){var s;null==(s=navigator.clipboard)||s.writeText(t).then(()=>{n("Copied to clipboard")}).catch(()=>{n("Copy failed",!0)})}export{T as a,S as b,H as c,o as i,w as l,I as r,u as s,F as t};
|
|
@@ -6,7 +6,7 @@
|
|
|
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-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CSooN9fi.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">
|
|
@@ -20,7 +20,7 @@
|
|
|
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-testing-tab-
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/tab-testing-tab-Ea5K-rsb.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/tab-skills-tab-DR7PJ7NB.js">
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/tab-contacts-tab-DiOyMYth.js">
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/tab-engines-tab-BsdZVvU0.js">
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/assets/tab-settings-tab-CuvH_Fj_.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="/assets/tab-comms-tab-kguqTIzD.js">
|
|
32
32
|
<link rel="modulepreload" crossorigin href="/assets/tab-usage-tab-BIOOnB-Y.js">
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/tab-spending-tab-
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/tab-spending-tab-DcXD5TQY.js">
|
|
34
34
|
<link rel="modulepreload" crossorigin href="/assets/tab-pm-loop-tab-DiAPTJXu.js">
|
|
35
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
35
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C5-vlIwl.css">
|
|
36
36
|
</head>
|
|
37
37
|
<body>
|
|
38
38
|
<!-- Skip link for keyboard navigation -->
|
|
@@ -3018,6 +3018,31 @@
|
|
|
3018
3018
|
—
|
|
3019
3019
|
</div>
|
|
3020
3020
|
</div>
|
|
3021
|
+
<div
|
|
3022
|
+
style="
|
|
3023
|
+
text-align: center;
|
|
3024
|
+
font-size: 20px;
|
|
3025
|
+
color: var(--text-3);
|
|
3026
|
+
line-height: 2;
|
|
3027
|
+
"
|
|
3028
|
+
>
|
|
3029
|
+
+
|
|
3030
|
+
</div>
|
|
3031
|
+
<div style="text-align: center">
|
|
3032
|
+
<div style="font-size: 11px; color: var(--text-3)">
|
|
3033
|
+
crew-cli
|
|
3034
|
+
</div>
|
|
3035
|
+
<div
|
|
3036
|
+
style="
|
|
3037
|
+
font-size: 20px;
|
|
3038
|
+
font-weight: 700;
|
|
3039
|
+
color: var(--purple, #a78bfa);
|
|
3040
|
+
"
|
|
3041
|
+
id="gtCrewCliCost"
|
|
3042
|
+
>
|
|
3043
|
+
—
|
|
3044
|
+
</div>
|
|
3045
|
+
</div>
|
|
3021
3046
|
<div
|
|
3022
3047
|
style="
|
|
3023
3048
|
text-align: center;
|
|
@@ -3079,7 +3104,7 @@
|
|
|
3079
3104
|
<select
|
|
3080
3105
|
id="spendingDays"
|
|
3081
3106
|
style="font-size: 11px; padding: 3px 6px"
|
|
3082
|
-
data-onchange="
|
|
3107
|
+
data-onchange="loadAllUsage"
|
|
3083
3108
|
>
|
|
3084
3109
|
<option value="1" selected>Today</option>
|
|
3085
3110
|
<option value="7">Last 7 days</option>
|
|
@@ -3204,8 +3229,9 @@
|
|
|
3204
3229
|
<select
|
|
3205
3230
|
id="ocStatsDays"
|
|
3206
3231
|
style="font-size: 11px; padding: 3px 6px"
|
|
3207
|
-
data-onchange="
|
|
3232
|
+
data-onchange="loadAllUsage"
|
|
3208
3233
|
>
|
|
3234
|
+
<option value="1">Today</option>
|
|
3209
3235
|
<option value="7">Last 7 days</option>
|
|
3210
3236
|
<option value="14" selected>Last 14 days</option>
|
|
3211
3237
|
<option value="30">Last 30 days</option>
|
|
@@ -3225,6 +3251,56 @@
|
|
|
3225
3251
|
</div>
|
|
3226
3252
|
</div>
|
|
3227
3253
|
</div>
|
|
3254
|
+
|
|
3255
|
+
<!-- crew-cli Usage -->
|
|
3256
|
+
<div class="card">
|
|
3257
|
+
<div
|
|
3258
|
+
style="
|
|
3259
|
+
display: flex;
|
|
3260
|
+
align-items: center;
|
|
3261
|
+
justify-content: space-between;
|
|
3262
|
+
margin-bottom: 10px;
|
|
3263
|
+
"
|
|
3264
|
+
>
|
|
3265
|
+
<div>
|
|
3266
|
+
<span class="card-title" style="margin: 0"
|
|
3267
|
+
>🛠️ crew-cli Usage</span
|
|
3268
|
+
>
|
|
3269
|
+
<span
|
|
3270
|
+
style="
|
|
3271
|
+
font-size: 11px;
|
|
3272
|
+
font-weight: 400;
|
|
3273
|
+
color: var(--text-3);
|
|
3274
|
+
"
|
|
3275
|
+
>(Direct LLM calls from crew-cli sessions)</span
|
|
3276
|
+
>
|
|
3277
|
+
</div>
|
|
3278
|
+
<div style="display: flex; gap: 6px; align-items: center">
|
|
3279
|
+
<select
|
|
3280
|
+
id="crewCliDays"
|
|
3281
|
+
style="font-size: 11px; padding: 3px 6px"
|
|
3282
|
+
data-onchange="loadAllUsage"
|
|
3283
|
+
>
|
|
3284
|
+
<option value="1">Today</option>
|
|
3285
|
+
<option value="7">Last 7 days</option>
|
|
3286
|
+
<option value="14" selected>Last 14 days</option>
|
|
3287
|
+
<option value="30">Last 30 days</option>
|
|
3288
|
+
</select>
|
|
3289
|
+
<button
|
|
3290
|
+
data-action="loadCrewCliStats"
|
|
3291
|
+
class="btn-ghost"
|
|
3292
|
+
style="font-size: 11px"
|
|
3293
|
+
>
|
|
3294
|
+
↻ Refresh
|
|
3295
|
+
</button>
|
|
3296
|
+
</div>
|
|
3297
|
+
</div>
|
|
3298
|
+
<div id="crewCliStatsWidget">
|
|
3299
|
+
<div style="color: var(--text-3); font-size: 12px">
|
|
3300
|
+
Loading…
|
|
3301
|
+
</div>
|
|
3302
|
+
</div>
|
|
3303
|
+
</div>
|
|
3228
3304
|
</div>
|
|
3229
3305
|
|
|
3230
3306
|
<!-- Security: Command allowlist + Env vars -->
|
|
@@ -5578,10 +5654,12 @@
|
|
|
5578
5654
|
</button>
|
|
5579
5655
|
</div>
|
|
5580
5656
|
</div>
|
|
5581
|
-
<div id="testProgressBar"></div>
|
|
5582
5657
|
<div id="testingContent">
|
|
5583
5658
|
<div class="meta" style="padding: 20px">Loading test results...</div>
|
|
5584
5659
|
</div>
|
|
5660
|
+
<div id="testProgressBar"></div>
|
|
5661
|
+
<div id="testingChart"></div>
|
|
5662
|
+
<div id="testingCoverage"></div>
|
|
5585
5663
|
<div id="testingHistory"></div>
|
|
5586
5664
|
</div>
|
|
5587
5665
|
|
|
Binary file
|
|
@@ -41,7 +41,16 @@ interface StatusResult {
|
|
|
41
41
|
error?: string;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
interface OpenClawApi {
|
|
45
|
+
config?: { plugins?: { entries?: { crewswarm?: { config?: CrewSwarmConfig } } } };
|
|
46
|
+
registerTool(def: Record<string, unknown>): void;
|
|
47
|
+
registerCommand(def: Record<string, unknown>): void;
|
|
48
|
+
registerGatewayMethod(name: string, handler: (ctx: Record<string, unknown>) => Promise<void>): void;
|
|
49
|
+
registerService(def: { id: string; start(): Promise<void>; stop(): void }): void;
|
|
50
|
+
logger?: { info(msg: string): void; warn(msg: string): void };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getConfig(api: OpenClawApi): CrewSwarmConfig {
|
|
45
54
|
return api.config?.plugins?.entries?.crewswarm?.config ?? {};
|
|
46
55
|
}
|
|
47
56
|
|
|
@@ -74,8 +83,8 @@ async function apiDispatch(
|
|
|
74
83
|
body: JSON.stringify(body),
|
|
75
84
|
});
|
|
76
85
|
return res.json() as Promise<DispatchResult>;
|
|
77
|
-
} catch (e:
|
|
78
|
-
return { ok: false, error: `Network error: ${e.message}` };
|
|
86
|
+
} catch (e: unknown) {
|
|
87
|
+
return { ok: false, error: `Network error: ${(e as Error).message}` };
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
|
|
@@ -87,8 +96,8 @@ async function apiStatus(
|
|
|
87
96
|
try {
|
|
88
97
|
const res = await fetch(`${base}/api/status/${taskId}`, { headers });
|
|
89
98
|
return res.json() as Promise<StatusResult>;
|
|
90
|
-
} catch (e:
|
|
91
|
-
return { ok: false, taskId, status: "unknown", error: `Network error: ${e.message}` };
|
|
99
|
+
} catch (e: unknown) {
|
|
100
|
+
return { ok: false, taskId, status: "unknown", error: `Network error: ${(e as Error).message}` };
|
|
92
101
|
}
|
|
93
102
|
}
|
|
94
103
|
|
|
@@ -107,7 +116,7 @@ async function apiAgents(
|
|
|
107
116
|
|
|
108
117
|
/** Dispatch and wait for result, polling until done or timeout */
|
|
109
118
|
async function dispatchAndWait(
|
|
110
|
-
api:
|
|
119
|
+
api: OpenClawApi,
|
|
111
120
|
agent: string,
|
|
112
121
|
task: string,
|
|
113
122
|
verify?: string,
|
|
@@ -139,7 +148,7 @@ async function dispatchAndWait(
|
|
|
139
148
|
return `Timeout: ${agent} did not complete within ${timeoutMs / 1000}s (taskId: ${taskId})`;
|
|
140
149
|
}
|
|
141
150
|
|
|
142
|
-
export default function register(api:
|
|
151
|
+
export default function register(api: OpenClawApi) {
|
|
143
152
|
// ── Agent tools ───────────────────────────────────────────────────────────
|
|
144
153
|
|
|
145
154
|
api.registerTool({
|
|
@@ -231,7 +240,7 @@ export default function register(api: any) {
|
|
|
231
240
|
description: "Dispatch a task to CrewSwarm. Usage: /crewswarm <agent> <task>",
|
|
232
241
|
acceptsArgs: true,
|
|
233
242
|
requireAuth: true,
|
|
234
|
-
handler: async (ctx:
|
|
243
|
+
handler: async (ctx: { args?: string }) => {
|
|
235
244
|
const args = (ctx.args ?? "").trim();
|
|
236
245
|
if (!args) {
|
|
237
246
|
const cfg = getConfig(api);
|
|
@@ -254,7 +263,7 @@ export default function register(api: any) {
|
|
|
254
263
|
|
|
255
264
|
// ── Gateway RPC ───────────────────────────────────────────────────────────
|
|
256
265
|
|
|
257
|
-
api.registerGatewayMethod("crewswarm.dispatch", async ({ params, respond }:
|
|
266
|
+
api.registerGatewayMethod("crewswarm.dispatch", async ({ params, respond }: { params?: Record<string, string>; respond(ok: boolean, data: Record<string, unknown>): void }) => {
|
|
258
267
|
const { agent, task, verify, done } = params ?? {};
|
|
259
268
|
if (!agent || !task) {
|
|
260
269
|
respond(false, { error: "agent and task are required" });
|
|
@@ -265,7 +274,7 @@ export default function register(api: any) {
|
|
|
265
274
|
respond(dispatch.ok, dispatch);
|
|
266
275
|
});
|
|
267
276
|
|
|
268
|
-
api.registerGatewayMethod("crewswarm.status", async ({ params, respond }:
|
|
277
|
+
api.registerGatewayMethod("crewswarm.status", async ({ params, respond }: { params?: Record<string, string>; respond(ok: boolean, data: Record<string, unknown>): void }) => {
|
|
269
278
|
const { taskId } = params ?? {};
|
|
270
279
|
if (!taskId) { respond(false, { error: "taskId required" }); return; }
|
|
271
280
|
const cfg = getConfig(api);
|
|
@@ -273,7 +282,7 @@ export default function register(api: any) {
|
|
|
273
282
|
respond(s.ok, s);
|
|
274
283
|
});
|
|
275
284
|
|
|
276
|
-
api.registerGatewayMethod("crewswarm.agents", async ({ respond }:
|
|
285
|
+
api.registerGatewayMethod("crewswarm.agents", async ({ respond }: { respond(ok: boolean, data: Record<string, unknown>): void }) => {
|
|
277
286
|
const cfg = getConfig(api);
|
|
278
287
|
const agents = await apiAgents(baseUrl(cfg), authHeaders(cfg));
|
|
279
288
|
respond(true, { agents });
|
package/install.sh
CHANGED
|
@@ -121,7 +121,7 @@ if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
EOF
|
|
124
|
-
success "Created ~/.crewswarm/
|
|
124
|
+
success "Created ~/.crewswarm/config.json (RT token: $RT_TOKEN)"
|
|
125
125
|
else
|
|
126
126
|
success "~/.crewswarm/crewswarm.json already exists — keeping it"
|
|
127
127
|
fi
|
|
@@ -307,7 +307,7 @@ elif [[ "$SHELL" == *"bash"* ]]; then
|
|
|
307
307
|
SHELL_RC="$HOME/.bash_profile"
|
|
308
308
|
fi
|
|
309
309
|
|
|
310
|
-
BIN_ALIAS="alias crew-cli='node $REPO_DIR/crew-cli.mjs'"
|
|
310
|
+
BIN_ALIAS="alias crew-cli='node $REPO_DIR/crew-cli/dist/crew.mjs'"
|
|
311
311
|
if [[ -n "$SHELL_RC" ]] && ! grep -q "crew-cli" "$SHELL_RC" 2>/dev/null; then
|
|
312
312
|
echo "" >> "$SHELL_RC"
|
|
313
313
|
echo "# CrewSwarm" >> "$SHELL_RC"
|
|
@@ -95,6 +95,73 @@ function classifyFailureReason(text = "") {
|
|
|
95
95
|
return "generic_failure";
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function isVerificationCommand(command = "") {
|
|
99
|
+
const text = String(command || "").trim().toLowerCase();
|
|
100
|
+
if (!text) return false;
|
|
101
|
+
return (
|
|
102
|
+
/\b(node\s+--test|npm\s+test|npm\s+run\s+test|pnpm\s+test|pnpm\s+run\s+test|yarn\s+test|pytest|go\s+test|cargo\s+test|bun\s+test)\b/.test(text) ||
|
|
103
|
+
/\b(tsc\b|tsc\s+--noemit|npm\s+run\s+build|pnpm\s+build|yarn\s+build|vite\s+build|next\s+build|npm\s+run\s+lint|pnpm\s+lint|yarn\s+lint)\b/.test(text)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function clamp01(value) {
|
|
108
|
+
if (!Number.isFinite(value)) return 0;
|
|
109
|
+
return Math.max(0, Math.min(1, value));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function scoreTaskTrajectory(trace = {}) {
|
|
113
|
+
const actions = Array.isArray(trace.actions) ? trace.actions : [];
|
|
114
|
+
const commands = actions.filter((action) => action?.tool === "run_cmd");
|
|
115
|
+
const verificationCommands = commands.filter((action) => isVerificationCommand(action.command));
|
|
116
|
+
const writeActions = actions.filter((action) => action?.tool === "write_file" || action?.tool === "append_file");
|
|
117
|
+
const readActions = actions.filter((action) => action?.tool === "read_file");
|
|
118
|
+
|
|
119
|
+
const commandPrefixCounts = new Map();
|
|
120
|
+
const targetCounts = new Map();
|
|
121
|
+
for (const action of actions) {
|
|
122
|
+
if (action?.commandPrefix) {
|
|
123
|
+
commandPrefixCounts.set(action.commandPrefix, (commandPrefixCounts.get(action.commandPrefix) || 0) + 1);
|
|
124
|
+
}
|
|
125
|
+
if (action?.target) {
|
|
126
|
+
targetCounts.set(action.target, (targetCounts.get(action.target) || 0) + 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const repeatedCommandPrefixes = [...commandPrefixCounts.values()].filter((count) => count > 1).length;
|
|
131
|
+
const repeatedTargets = [...targetCounts.values()].filter((count) => count > 1).length;
|
|
132
|
+
const uniqueTools = new Set(actions.map((action) => action?.tool).filter(Boolean)).size;
|
|
133
|
+
const readBeforeWriteRatio = writeActions.length === 0
|
|
134
|
+
? 1
|
|
135
|
+
: clamp01(readActions.length / writeActions.length);
|
|
136
|
+
const verificationScore = commands.length === 0
|
|
137
|
+
? 0
|
|
138
|
+
: clamp01(verificationCommands.length / commands.length);
|
|
139
|
+
const churnPenalty = clamp01((repeatedCommandPrefixes * 0.12) + (repeatedTargets * 0.08));
|
|
140
|
+
const diversityScore = clamp01(uniqueTools / 4);
|
|
141
|
+
|
|
142
|
+
let score = 0;
|
|
143
|
+
score += trace.success ? 0.45 : 0.15;
|
|
144
|
+
score += verificationScore * 0.20;
|
|
145
|
+
score += readBeforeWriteRatio * 0.20;
|
|
146
|
+
score += diversityScore * 0.15;
|
|
147
|
+
score -= churnPenalty;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
actionCount: actions.length,
|
|
151
|
+
commandCount: commands.length,
|
|
152
|
+
verificationCommandCount: verificationCommands.length,
|
|
153
|
+
hasVerification: verificationCommands.length > 0,
|
|
154
|
+
writeCount: writeActions.length,
|
|
155
|
+
readCount: readActions.length,
|
|
156
|
+
uniqueToolCount: uniqueTools,
|
|
157
|
+
repeatedCommandPrefixes,
|
|
158
|
+
repeatedTargets,
|
|
159
|
+
readBeforeWriteRatio: Number(readBeforeWriteRatio.toFixed(3)),
|
|
160
|
+
verificationScore: Number(verificationScore.toFixed(3)),
|
|
161
|
+
trajectoryScore: Number(clamp01(score).toFixed(3)),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
98
165
|
export function getAutoHarnessPaths(agentId, projectId = "global") {
|
|
99
166
|
const rootDir = resolveAutoHarnessRoot();
|
|
100
167
|
if (!rootDir) return null;
|
|
@@ -174,11 +241,17 @@ export function recordTaskTrace({
|
|
|
174
241
|
error,
|
|
175
242
|
engineUsed,
|
|
176
243
|
success,
|
|
244
|
+
metrics,
|
|
177
245
|
}) {
|
|
178
246
|
if (!agentId) return;
|
|
179
247
|
const paths = getAutoHarnessPaths(agentId, projectId);
|
|
180
248
|
if (!paths) return;
|
|
181
249
|
const { taskTraceFile } = paths;
|
|
250
|
+
const actions = extractToolActions(reply);
|
|
251
|
+
const derivedMetrics = scoreTaskTrajectory({
|
|
252
|
+
success: Boolean(success),
|
|
253
|
+
actions,
|
|
254
|
+
});
|
|
182
255
|
appendJsonl(taskTraceFile, {
|
|
183
256
|
ts: new Date().toISOString(),
|
|
184
257
|
agentId,
|
|
@@ -191,7 +264,10 @@ export function recordTaskTrace({
|
|
|
191
264
|
errorClass: classifyFailureReason(error),
|
|
192
265
|
engineUsed: engineUsed || null,
|
|
193
266
|
success: Boolean(success),
|
|
194
|
-
actions
|
|
267
|
+
actions,
|
|
268
|
+
metrics: metrics && typeof metrics === "object"
|
|
269
|
+
? { ...derivedMetrics, ...metrics }
|
|
270
|
+
: derivedMetrics,
|
|
195
271
|
});
|
|
196
272
|
}
|
|
197
273
|
|
|
@@ -342,6 +418,7 @@ export function scoreHarness(agentId, projectId = "global") {
|
|
|
342
418
|
}
|
|
343
419
|
const { toolTraceFile } = paths;
|
|
344
420
|
const traces = loadJsonl(toolTraceFile);
|
|
421
|
+
const taskTraces = loadJsonl(paths.taskTraceFile);
|
|
345
422
|
|
|
346
423
|
const stats = {
|
|
347
424
|
traces: traces.length,
|
|
@@ -380,12 +457,85 @@ export function scoreHarness(agentId, projectId = "global") {
|
|
|
380
457
|
const recall =
|
|
381
458
|
stats.badOutcomes > 0 ? stats.blockedBadOutcomes / stats.badOutcomes : 0;
|
|
382
459
|
|
|
460
|
+
const taskMetrics = taskTraces
|
|
461
|
+
.map((trace) => trace?.metrics && typeof trace.metrics === "object"
|
|
462
|
+
? trace.metrics
|
|
463
|
+
: scoreTaskTrajectory(trace))
|
|
464
|
+
.filter(Boolean);
|
|
465
|
+
|
|
466
|
+
const taskStats = {
|
|
467
|
+
tasks: taskMetrics.length,
|
|
468
|
+
avgTrajectoryScore: taskMetrics.length
|
|
469
|
+
? Number((taskMetrics.reduce((sum, item) => sum + Number(item.trajectoryScore || 0), 0) / taskMetrics.length).toFixed(3))
|
|
470
|
+
: 0,
|
|
471
|
+
verificationRate: taskMetrics.length
|
|
472
|
+
? Number((taskMetrics.filter((item) => item.hasVerification).length / taskMetrics.length).toFixed(3))
|
|
473
|
+
: 0,
|
|
474
|
+
avgReadBeforeWriteRatio: taskMetrics.length
|
|
475
|
+
? Number((taskMetrics.reduce((sum, item) => sum + Number(item.readBeforeWriteRatio || 0), 0) / taskMetrics.length).toFixed(3))
|
|
476
|
+
: 0,
|
|
477
|
+
};
|
|
478
|
+
|
|
383
479
|
return {
|
|
384
480
|
harness,
|
|
385
481
|
stats: {
|
|
386
482
|
...stats,
|
|
387
483
|
precision: Number(precision.toFixed(3)),
|
|
388
484
|
recall: Number(recall.toFixed(3)),
|
|
485
|
+
taskStats,
|
|
389
486
|
},
|
|
390
487
|
};
|
|
391
488
|
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Extract trajectory feedback from task traces for the adaptive weight system.
|
|
492
|
+
* Returns data in the format expected by action-ranking.ts loadAdaptiveWeights().
|
|
493
|
+
*/
|
|
494
|
+
export function extractTrajectoryFeedback(agentId, projectId = "global") {
|
|
495
|
+
const paths = getAutoHarnessPaths(agentId, projectId);
|
|
496
|
+
if (!paths) return [];
|
|
497
|
+
|
|
498
|
+
const taskTraces = loadJsonl(paths.taskTraceFile);
|
|
499
|
+
if (!taskTraces.length) return [];
|
|
500
|
+
|
|
501
|
+
const READ_TOOLS = new Set(["read_file", "read_many_files", "glob", "grep_search", "list_directory", "lsp"]);
|
|
502
|
+
const SEARCH_TOOLS = new Set(["grep_search", "glob", "search_files", "find_definition"]);
|
|
503
|
+
const EDIT_TOOLS = new Set(["replace", "edit", "append_file", "write_file", "notebook_edit"]);
|
|
504
|
+
const SHELL_TOOLS = new Set(["run_shell_command", "shell", "run_cmd", "check_background_task"]);
|
|
505
|
+
|
|
506
|
+
function classifyAction(tool) {
|
|
507
|
+
if (READ_TOOLS.has(tool)) return "read";
|
|
508
|
+
if (SEARCH_TOOLS.has(tool)) return "search";
|
|
509
|
+
if (EDIT_TOOLS.has(tool)) return "edit";
|
|
510
|
+
if (SHELL_TOOLS.has(tool)) return "verify";
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function detectMode(task = "") {
|
|
515
|
+
const t = task.toLowerCase();
|
|
516
|
+
if (/(failing tests?|test failure|fix tests?|fix the test|test.*(fail|broken))/.test(t)) return "test_repair";
|
|
517
|
+
if (/(fix|bug|broken|error|regression|crash)/.test(t)) return "bugfix";
|
|
518
|
+
if (/(refactor|cleanup|restructure|rename|simplify)/.test(t)) return "refactor";
|
|
519
|
+
if (/(add|implement|create|build|support|introduce)/.test(t)) return "feature";
|
|
520
|
+
return "analysis";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return taskTraces.map((trace) => {
|
|
524
|
+
const actions = Array.isArray(trace.actions) ? trace.actions : [];
|
|
525
|
+
const total = actions.length || 1;
|
|
526
|
+
const dist = { read: 0, search: 0, edit: 0, test: 0, build: 0, verify: 0, delegate: 0 };
|
|
527
|
+
|
|
528
|
+
for (const action of actions) {
|
|
529
|
+
const type = classifyAction(action?.tool);
|
|
530
|
+
if (type && type in dist) dist[type] += 1 / total;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const metrics = trace.metrics || scoreTaskTrajectory(trace);
|
|
534
|
+
return {
|
|
535
|
+
mode: detectMode(trace.task || trace.agentId || ""),
|
|
536
|
+
score: Number(metrics.trajectoryScore || 0),
|
|
537
|
+
toolDistribution: dist,
|
|
538
|
+
success: Boolean(trace.success),
|
|
539
|
+
};
|
|
540
|
+
});
|
|
541
|
+
}
|
package/lib/chat/history.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { existsSync, mkdirSync } from 'fs';
|
|
9
9
|
import { dirname, join } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
|
+
import { getStatePath } from '../runtime/paths.mjs';
|
|
11
12
|
|
|
12
13
|
// Try to import better-sqlite3, but make it optional
|
|
13
14
|
let Database;
|
|
@@ -27,7 +28,7 @@ function getDb() {
|
|
|
27
28
|
|
|
28
29
|
if (_db) return _db;
|
|
29
30
|
|
|
30
|
-
const dbPath =
|
|
31
|
+
const dbPath = process.env.CREWSWARM_CONTACTS_DB_PATH || getStatePath('contacts.db');
|
|
31
32
|
const dir = dirname(dbPath);
|
|
32
33
|
|
|
33
34
|
if (!existsSync(dir)) {
|
|
@@ -40,8 +41,28 @@ function getDb() {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
function initSchema(db) {
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
db.exec(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS contacts (
|
|
46
|
+
contact_id TEXT PRIMARY KEY,
|
|
47
|
+
platform TEXT NOT NULL,
|
|
48
|
+
display_name TEXT,
|
|
49
|
+
phone_number TEXT,
|
|
50
|
+
email TEXT,
|
|
51
|
+
avatar_url TEXT,
|
|
52
|
+
preferences TEXT,
|
|
53
|
+
tags TEXT,
|
|
54
|
+
notes TEXT,
|
|
55
|
+
platform_links TEXT,
|
|
56
|
+
first_seen INTEGER NOT NULL,
|
|
57
|
+
last_seen INTEGER NOT NULL,
|
|
58
|
+
message_count INTEGER DEFAULT 0,
|
|
59
|
+
last_location TEXT,
|
|
60
|
+
timezone TEXT,
|
|
61
|
+
language TEXT DEFAULT 'en'
|
|
62
|
+
);
|
|
63
|
+
`);
|
|
64
|
+
|
|
65
|
+
// Just ensure platform_links column exists for older DBs.
|
|
45
66
|
try {
|
|
46
67
|
db.exec(`
|
|
47
68
|
ALTER TABLE contacts ADD COLUMN platform_links TEXT;
|
package/lib/contacts/index.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { createRequire } from 'module';
|
|
|
12
12
|
import { existsSync, mkdirSync } from 'fs';
|
|
13
13
|
import { dirname, join } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
|
+
import { getStatePath } from '../runtime/paths.mjs';
|
|
15
16
|
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
|
|
@@ -37,7 +38,7 @@ function getDb() {
|
|
|
37
38
|
|
|
38
39
|
if (_db) return _db;
|
|
39
40
|
|
|
40
|
-
const dbPath =
|
|
41
|
+
const dbPath = process.env.CREWSWARM_CONTACTS_DB_PATH || getStatePath('contacts.db');
|
|
41
42
|
const dir = dirname(dbPath);
|
|
42
43
|
|
|
43
44
|
if (!existsSync(dir)) {
|