wyrm-mcp 8.4.0 → 8.5.1
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/dist/branding.js +1 -0
- package/dist/http-auth.js +1 -1
- package/dist/http-fast.js +19 -19
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/ui-dashboard.js +167 -5
- package/dist/wyrm-manifest.json +1 -1
- package/package.json +1 -2
- package/ui/dragon-mark.svg +0 -931
package/dist/branding.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as d}from"node:fs";import{homedir as p}from"node:os";import{join as R}from"node:path";const n={product:"Wyrm",fleetLabel:"Fleet",fleetTitle:"Fleet Channel",fleetGrove:"fleet-warroom",fleetBlurb:"The multi-agent coordination channel. Findings, claims, fix-receipts and verdicts flow as structured work between agents."};function L(){try{const t=process.env.WYRM_BRAND_FILE||R(p(),".wyrm","brand.json");return JSON.parse(d(t,"utf8"))}catch{return{}}}function F(t=!1){const e=L(),l=i=>{const r=process.env[i];return r&&r.trim()?r.trim():void 0},o=(i,r,_)=>l(i)??(r&&r.trim()?r.trim():void 0)??_,a=!!(e.fleetLabel||e.fleetTitle||e.fleet==="on"||e.profile),c=(l("WYRM_UI_FLEET")||e.fleet||(a?"on":"auto")).toLowerCase(),u=c==="on"?!0:c==="off"?!1:t,f=l("WYRM_UI_BRAND_MARK_URL")??e.markUrl,s=l("WYRM_UI_BRAND_MARK")??e.mark,m=f?`<img src="${E(f)}" alt="" class="logo-mark" />`:s?B(s):"";return{product:o("WYRM_UI_BRAND_NAME",e.product,n.product),markHtml:m,showFleet:u,fleetLabel:o("WYRM_UI_FLEET_LABEL",e.fleetLabel,n.fleetLabel),fleetTitle:o("WYRM_UI_FLEET_TITLE",e.fleetTitle,n.fleetTitle),fleetGrove:o("WYRM_UI_FLEET_GROVE",e.fleetGrove,n.fleetGrove),fleetBlurb:l("WYRM_UI_FLEET_BLURB")??(e.fleetBlurb&&e.fleetBlurb.trim()?e.fleetBlurb.trim():void 0)??n.fleetBlurb}}function E(t){return String(t).replace(/["'<>]/g,"").slice(0,300)}function B(t){return String(t).replace(/[<>&"']/g,"").slice(0,24)}export{F as resolveBranding};
|
package/dist/http-auth.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{existsSync as w,readFileSync as I,writeFileSync as u,mkdirSync as R}from"fs";import{join as y}from"path";import{homedir as x}from"os";import{randomBytes as O,createHash as T}from"crypto";import{WyrmLogger as k}from"./logger.js";const g=y(x(),".wyrm"),a=y(g,"http-config.json"),L={enabled:!0,requests:100,windowMs:6e4},S=["http://localhost:3333","http://127.0.0.1:3333"],d=new Map;let c=null;const i=new k;function m(e){return T("sha256").update(e).digest("hex")}function P(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function M(){if(w(g)||R(g,{recursive:!0,mode:448}),w(a))try{const r=JSON.parse(I(a,"utf-8"));return{apiKeyHash:r.apiKeyHash,allowedOrigins:r.allowedOrigins||S,rateLimit:{...L,...r.rateLimit},requireAuth:r.requireAuth??!0,devMode:process.env.WYRM_DEV==="true"}}catch(r){i.error("Failed to load HTTP config, regenerating",{error:r.message})}const e=O(32).toString("hex");i.info("Generated new API key for HTTP server"),console.log(`
|
|
2
2
|
`+"\u2550".repeat(60)),console.log("\u{1F510} WYRM API KEY (save this securely, shown once):"),console.log(""),console.log(` ${e}`),console.log(""),console.log(" Use with: Authorization: Bearer <key>"),console.log("\u2550".repeat(60)+`
|
|
3
|
-
`);const t={apiKeyHash:m(e),allowedOrigins:S,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return u(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=M()),c}function N(e){const t=s();if(t.devMode){const h=e.socket.remoteAddress;if(h==="127.0.0.1"||h==="::1"||h==="::ffff:127.0.0.1")return!0}const r=e.socket.remoteAddress??"",n=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",o=(e.headers.host??"").split(":")[0].toLowerCase();if(n&&(o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]")&&e.headers["x-wyrm-origin"]==="ui")return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const l=e.headers.authorization;if(!l||!l.startsWith("Bearer "))return!1;const f=l.slice(7);if(!f||f.length<32)return!1;const C=m(f);return P(C,t.apiKeyHash)}function H(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",n=Date.now();if(d.size>1e4)for(const[A,l]of d)l.resetAt<n&&d.delete(A);let o=d.get(r);return(!o||o.resetAt<n)&&(o={count:0,resetAt:n+t.rateLimit.windowMs},d.set(r,o)),o.count++,{allowed:o.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-o.count),resetAt:o.resetAt}}function v(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function p(e){return{"Access-Control-Allow-Origin":v(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const D=["/health","/auth/status","/ui"];function J(e,t){if(e.method==="OPTIONS"){const o=p(e);return t.writeHead(204,o),t.end(),{error:!0}}const r=H(e);if(!r.allowed){const o=p(e);return t.writeHead(429,{...o,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const n=new URL(e.url||"/","http://localhost").pathname;if(D.includes(n))return{error:!1};if(!N(e)){const o=p(e);return t.writeHead(401,{...o,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function _(){const e=O(32).toString("hex"),t=s();return t.apiKeyHash=m(e),u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function U(e){const t=s();t.allowedOrigins=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function z(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},u(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function B(e){const t=s();t.requireAuth=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function G(){const e=s();e.devMode=!0}function X(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{J as authMiddleware,N as authenticate,H as checkRateLimit,G as enableDevMode,X as getAuthStatus,v as getCorsOrigin,p as getSecurityHeaders,_ as regenerateApiKey,U as setAllowedOrigins,z as setRateLimit,B as setRequireAuth};
|
|
3
|
+
`);const t={apiKeyHash:m(e),allowedOrigins:S,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return u(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=M()),c}function N(e){const t=s();if(t.devMode){const h=e.socket.remoteAddress;if(h==="127.0.0.1"||h==="::1"||h==="::ffff:127.0.0.1")return!0}const r=e.socket.remoteAddress??"",n=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",o=(e.headers.host??"").split(":")[0].toLowerCase();if(n&&(o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]")&&e.headers["x-wyrm-origin"]==="ui")return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const l=e.headers.authorization;if(!l||!l.startsWith("Bearer "))return!1;const f=l.slice(7);if(!f||f.length<32)return!1;const C=m(f);return P(C,t.apiKeyHash)}function H(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",n=Date.now();if(d.size>1e4)for(const[A,l]of d)l.resetAt<n&&d.delete(A);let o=d.get(r);return(!o||o.resetAt<n)&&(o={count:0,resetAt:n+t.rateLimit.windowMs},d.set(r,o)),o.count++,{allowed:o.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-o.count),resetAt:o.resetAt}}function v(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function p(e){return{"Access-Control-Allow-Origin":v(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const D=["/health","/auth/status","/ui"];function J(e,t){if(e.method==="OPTIONS"){const o=p(e);return t.writeHead(204,o),t.end(),{error:!0}}const r=H(e);if(!r.allowed){const o=p(e);return t.writeHead(429,{...o,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const n=new URL(e.url||"/","http://localhost").pathname;if(D.includes(n))return{error:!1};if(!N(e)){const o=p(e);return t.writeHead(401,{...o,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function _(){const e=O(32).toString("hex"),t=s();return t.apiKeyHash=m(e),u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function U(e){const t=s();t.allowedOrigins=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function z(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},u(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function B(e){const t=s();t.requireAuth=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function G(){const e=s();e.devMode=!0}function X(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{J as authMiddleware,N as authenticate,H as checkRateLimit,G as enableDevMode,X as getAuthStatus,v as getCorsOrigin,p as getSecurityHeaders,_ as regenerateApiKey,U as setAllowedOrigins,z as setRateLimit,B as setRequireAuth};
|
package/dist/http-fast.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import z from"http";import{WyrmDB as W}from"./database.js";import{cache as h,estimateTokens as Z,truncateToTokens as ee,timed as G}from"./performance.js";import{authMiddleware as te,getAuthStatus as k,getSecurityHeaders as b}from"./http-auth.js";import{WyrmLogger as re}from"./logger.js";import{sanitizeFtsQuery as U,validateProjectPath as ne}from"./security.js";import{readFileSync as R,existsSync as y,readdirSync as se,realpathSync as x}from"fs";import{homedir as oe}from"os";import{dirname as q,join as S}from"path";import{fileURLToPath as ae}from"url";const B=S(q(ae(import.meta.url)),"..","ui","dragon-mark.svg");let v=null;import{getUIDashboardHTML as ie}from"./ui-dashboard.js";import{resolveBranding as I}from"./branding.js";import{verifyLicense as $}from"./license.js";import{rerankConfigured as ce,rerankEgressHost as le}from"./rerank.js";import{MemoryArtifacts as de}from"./memory-artifacts.js";import{createVectorStore as ue}from"./vectors.js";import{GroundTruths as pe,computeStaleness as me}from"./intelligence.js";import{Causality as fe}from"./causality.js";import{wireTruthAutoCascade as Ee}from"./truth-cascade.js";import{FailurePatterns as he}from"./failure-patterns.js";import{handleDaemonWrite as _e}from"./daemon-write-endpoint.js";import{retryableWriteCause as Y,busyErrorBody as J}from"./sqlite-busy.js";import{sseFrame as Se,resolveStartCursor as K,SSE_KEEPALIVE as ye}from"./events-sse.js";import{readonlyBlocks as ge}from"./readonly-gate.js";import{resolveActorEnvelope as Te,runWithActor as Oe}from"./handlers/boundary.js";const j=parseInt(process.env.WYRM_PORT||"3333")||3333,g=process.env.WYRM_UI_READONLY==="1",Re=512*1024,M=500,ve=parseInt(process.env.WYRM_MAX_SSE_STREAMS||"64",10)||64,Ce=parseInt(process.env.WYRM_MAX_SSE_PER_IP||"8",10)||8;let w=0;const T=new Map;let O=null,C=null,L=null,N=null;function a(){return O||(O=new W(process.env.WYRM_DB_PATH)),O}function A(){if(!C){C=new de(a().getDatabase());try{const e=process.env.WYRM_VECTOR_PROVIDER||"auto";e!=="none"&&C.setVectorStore(ue({provider:e},a().getDatabase()))}catch{}}return C}function Le(){return L||(L=new pe(a().getDatabase()),Ee(L,new fe(a().getDatabase()))),L}function be(){return N||(N=new he(a().getDatabase())),N}function je(e){const t=O;O=e,C=null,L=null,N=null;try{t?.close()}catch{}}function Q(){const e=[],t=oe(),r=new Set;let n=[];try{n=se(t)}catch{return e}n.sort((s,o)=>o.localeCompare(s));for(const s of n){if(s!==".wyrm"&&!s.startsWith(".wyrm-"))continue;let o;try{o=x(S(t,s))}catch{continue}if(r.has(o))continue;const d=S(o,"wyrm.db");if(!y(d))continue;r.add(o);let i="Local",l="free";try{const c=S(o,"license.json");if(y(c)){const u=JSON.parse(R(c,"utf-8")),m=$(u);i=u.license&&u.license.issued_to||i,l=m.valid?m.tier:"free"}else{const u=S(o,"cloud.json");y(u)&&(i=JSON.parse(R(u,"utf-8")).email||i)}}catch{}const p=s.replace(/^\.wyrm-?/,"")||"default";e.push({name:p,dbPath:d,account:i,tier:l})}return e}const D=new re({level:"info"});function _(e,t,r){const n=Number.parseInt(String(e??""),10);return!Number.isFinite(n)||n<1?t:Math.min(n,r)}function P(e){const t=e==null?"":String(e);if(t)return a().getProject(t)||a().getProjectByName(t)}function E(e,t,r,n=200){const s=JSON.stringify(r),o=b(t);e.writeHead(n,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(s),"X-Tokens":String(Z(r)),...o}),e.end(s)}function Me(e,t,r){const n=b(t);e.writeHead(200,{...n,"Content-Type":"text/html; charset=utf-8","Content-Length":Buffer.byteLength(r),"Cache-Control":"no-store"}),e.end(r)}function Ne(e){return new Promise((t,r)=>{if(e.method==="GET"){t({});return}let n="",s=0;e.on("data",o=>{if(s+=o.length,s>Re){e.destroy(),r(new Error("Request body too large"));return}n+=o}),e.on("end",()=>{try{t(n?JSON.parse(n):{})}catch{r(new Error("Invalid JSON"))}}),e.on("error",r)})}const X={"GET /c":e=>{const t=`ctx:${e.p||"global"}`,r=h.get(t);if(r)return r;const n=e.p;let s;if(n){const o=a().getProject(n);if(!o)return{e:"not found"};const d=a().getPendingQuests(o.id),i=a().getRecentSessions(o.id,3);s={n:o.name,s:o.stack,q:d.length,qt:d.slice(0,5).map(l=>l.title),r:i[0]?.summary||i[0]?.notes?.slice(0,200)||null}}else{const o=a().getAllProjects(20),d=a().getAllPendingQuests();s={p:o.map(i=>({n:i.name,s:i.stack,q:a().getPendingQuests(i.id).length})),tq:d.length}}return h.set(t,s),s},"GET /p":()=>{const e=h.get("projects");if(e)return e;const r=a().getAllProjects(50).map(n=>({i:n.id,n:n.name,p:n.path,s:n.stack}));return h.set("projects",r),r},"POST /scan":e=>{const t=e.d||e.directory||process.env.HOME+"/Git Projects",r=ne(t);return r?(h.invalidate(),{found:a().scanForProjects(r,!0).length}):{e:"path not allowed"}},"GET /q":e=>{const t=e.p;if(t){const r=a().getProject(t);return r?a().getPendingQuests(r.id).map(n=>({i:n.id,t:n.title,p:n.priority[0]})):{e:"not found"}}return a().getAllPendingQuests().slice(0,20).map(r=>({i:r.id,t:r.title,p:r.priority[0]}))},"POST /q":e=>{const t=e.p,r=a().getProject(t);return r?(h.invalidate("ctx"),{i:a().addQuest(r.id,e.t,e.d,e.pr||"medium").id}):{e:"not found"}},"POST /qc":e=>{const t=typeof e.i=="number"?e.i:parseInt(String(e.i),10);return!t||t<1?{e:"id required"}:(h.invalidate("ctx"),a().updateQuest(t,"completed"),{ok:1})},"POST /s":e=>{const t=e.p;let r=a().getProject(t);if(r||(a().scanForProjects(t,!1),r=a().getProject(t)),!r)return{e:"not found"};let n=a().getTodaySession(r.id);return n||(n=a().createSession(r.id,{objectives:e.o||"",notes:""})),h.invalidate("ctx"),{i:n.id,d:n.date}},"POST /su":e=>{const t=e.i;return h.invalidate("ctx"),a().updateSession(t,{notes:e.n,summary:e.s,completed:e.c}),{ok:1}},"POST /x":e=>{const t=e.p,r=a().getProject(t);return r?(a().setContext(r.id,e.k,e.v),h.invalidate("ctx"),{ok:1}):{e:"not found"}},"GET /x":e=>{const t=e.p,r=a().getProject(t);return r?a().getAllContext(r.id):{e:"not found"}},"POST /g":e=>{const t=e.k,r=e.v;return!t||typeof t!="string"?{e:"k (key) required"}:r==null?{e:"v (value) required"}:(a().setGlobalContext(t,String(r)),h.invalidate(),{ok:1})},"GET /g":()=>a().getAllGlobalContext(),"GET /s":e=>{const t=e.q;if(!t)return{e:"query required"};const r=U(t);return{q:a().searchQuests(r).slice(0,5).map(s=>({i:s.id,t:s.title})),s:a().searchSessions(r).slice(0,3).map(s=>({i:s.id,d:s.date})),p:a().searchProjects(r).slice(0,3).map(s=>({n:s.name,p:s.path}))}},"POST /d":e=>{const t=e.p,r=a().getProject(t);return r?{i:a().insertData(r.id,e.c,e.k,e.v,e.m).id}:{e:"not found"}},"GET /d":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=_(e.l,20,100);return a().queryData(r.id,e.c,n).map(s=>({k:s.key,v:ee(s.value,100)}))},"GET /d/search":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=e.q;if(!n)return{e:"q (query) required"};const s=e.c,o=_(e.l,10,100);let d=a().searchData(n,r.id);return s&&(d=d.filter(i=>i.category===s)),d.slice(0,o).map(i=>({k:i.key,v:i.value,c:i.category,m:i.metadata}))},"GET /d/agg":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=e.c;if(!n)return{e:"c (category) required"};const s=e.stack_key,o=e.scanner,d=_(e.l,1e3,1e4),i={};for(const l of a().queryData(r.id,n,d)){let p;try{p=JSON.parse(l.value)}catch{continue}if(s&&p.stack_key!==s||o&&p.scanner!==o)continue;const c=String(p.scanner??""),u=String(p.payload_hash??"");if(!c||!u)continue;const m=`${c}:${u}`,f=i[m]??{s:c,ph:u,sk:String(p.stack_key??""),h:0,t:0};f.t+=1,p.hit&&(f.h+=1),i[m]=f}return Object.values(i).map(l=>({...l,r:l.t>0?l.h/l.t:0})).sort((l,p)=>p.r-l.r||p.h-l.h)},"POST /batch":e=>{const t=e.ops;if(!t||!Array.isArray(t))return{e:"ops required"};if(t.length>M)return{e:`max ${M} ops per batch`};const r=performance.now();return{r:t.map(s=>{const o=`${s.m} ${s.r}`,d=X[o];if(!d)return{e:"unknown"};try{return{d:d(s.a||{})}}catch{return{e:"op failed"}}}),ms:Math.round(performance.now()-r)}},"POST /write":e=>_e({db:a(),artifacts:A(),truths:Le(),failures:be()},e),"GET /syms":e=>{const t=String(e.q??"").slice(0,200);if(!t)return[];const r=_(e.limit,20,200),n=a().getDatabase();try{return n.prepare(`
|
|
3
3
|
SELECT symbol, kind, language, file_path, line, signature, project_id
|
|
4
4
|
FROM symbol_index
|
|
5
5
|
WHERE symbol LIKE ?
|
|
6
6
|
ORDER BY project_id, file_path, line
|
|
7
7
|
LIMIT ?
|
|
8
|
-
`).all(`%${t}%`,r)}catch{return[]}},"GET /failures":e=>{const t=String(e.target??"").slice(0,200),r=
|
|
8
|
+
`).all(`%${t}%`,r)}catch{return[]}},"GET /failures":e=>{const t=String(e.target??"").slice(0,200),r=_(e.limit,10,100),n=a().getDatabase();try{return n.prepare(`
|
|
9
9
|
SELECT id, scope, target, description, why_failed, occurrences,
|
|
10
10
|
severity, last_seen
|
|
11
11
|
FROM failure_patterns
|
|
12
12
|
WHERE resolved = 0 AND (target = ? OR target LIKE ?)
|
|
13
13
|
ORDER BY occurrences DESC, last_seen DESC
|
|
14
14
|
LIMIT ?
|
|
15
|
-
`).all(t,`%${t}%`,r)}catch{return[]}},"GET /truths":e=>{const t=String(e.q??"").slice(0,200),r=
|
|
15
|
+
`).all(t,`%${t}%`,r)}catch{return[]}},"GET /truths":e=>{const t=String(e.q??"").slice(0,200),r=_(e.limit,5,50);if(!t)return[];const n=a().getDatabase();try{return n.prepare(`
|
|
16
16
|
SELECT category, key, value, rationale, confidence, ttl_days
|
|
17
17
|
FROM ground_truths
|
|
18
18
|
WHERE is_current = 1
|
|
19
19
|
AND (key LIKE ? OR value LIKE ? OR rationale LIKE ?)
|
|
20
20
|
ORDER BY confidence DESC
|
|
21
21
|
LIMIT ?
|
|
22
|
-
`).all(`%${t}%`,`%${t}%`,`%${t}%`,r)}catch{return[]}},"GET /routing":e=>{const t=String(e.task_type??e.t??"").slice(0,200).trim();if(!t)return{e:"task_type required"};const r=
|
|
23
|
-
WHERE is_shared = 1 ${
|
|
24
|
-
ORDER BY ${
|
|
25
|
-
LIMIT ?`).all(
|
|
22
|
+
`).all(`%${t}%`,`%${t}%`,`%${t}%`,r)}catch{return[]}},"GET /routing":e=>{const t=String(e.task_type??e.t??"").slice(0,200).trim();if(!t)return{e:"task_type required"};const r=_(e.limit,10,100);let n;const s=e.p;if(s){const i=a().getProject(s);if(!i)return{e:"not found"};n=i.id}const o=e.since_days!=null?Number(e.since_days):void 0;return a().recallRouting(t,{projectId:n,limit:r,sinceDays:Number.isFinite(o)?o:void 0}).map(i=>({m:i.model_id,tools:i.tool_names,runs:i.runs,ok:i.successes,fail:i.failures,rate:Math.round(i.success_rate*1e3)/1e3,ms:i.avg_latency_ms,last:i.last_ts}))},"POST /routing":e=>{const t=String(e.task_type??"").slice(0,200).trim(),r=String(e.model_id??"").slice(0,200).trim();if(!t)return{e:"task_type required"};if(!r)return{e:"model_id required"};if(e.success===void 0||e.success===null)return{e:"success required"};let n;const s=e.p;if(s){const l=a().getProject(s);l&&(n=l.id)}const o=Array.isArray(e.tool_names)?e.tool_names.map(l=>String(l)):e.tool_names!=null?String(e.tool_names):null,d=e.latency_ms!=null?Number(e.latency_ms):null,i=e.success===!0||e.success===1||e.success==="1"||e.success==="true";try{return{i:a().recordRouting({projectId:n,taskType:t,modelId:r,toolNames:o,success:i,latencyMs:Number.isFinite(d)?d:null})}}catch(l){return{e:l.message}}},"GET /audit":e=>{const t=_(e.limit,50,500),r=e.kind,n=a().getDatabase();try{return r?n.prepare("SELECT * FROM audit_log WHERE event_kind = ? ORDER BY id DESC LIMIT ?").all(r,t):n.prepare("SELECT * FROM audit_log ORDER BY id DESC LIMIT ?").all(t)}catch{return[]}},"GET /sync/push":e=>{const t=e.since,r=_(e.limit,500,5e3),n=a().getDatabase(),s=[],o=t??"1970-01-01",d=[["session","sessions","created_at"],["quest","quests","created_at"],["truth","ground_truths","updated_at"],["artifact","memory_artifacts","last_accessed_at"],["edge","decision_edges","created_at"]],i=(()=>{try{return n.prepare("PRAGMA table_info(projects)").all().some(p=>p.name==="sync_policy")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":""}catch{return""}})();for(const[l,p,c]of d){if(s.length>=r)break;try{const u=n.prepare(`SELECT * FROM ${p}
|
|
23
|
+
WHERE is_shared = 1 ${i} AND ${c} > ?
|
|
24
|
+
ORDER BY ${c} ASC
|
|
25
|
+
LIMIT ?`).all(o,Math.max(1,r-s.length));for(const m of u)s.push({kind:l,id:m.id,project_id:m.project_id??null,updated_at:m[c]??null,payload:m})}catch{}}return{rows:s,count:s.length,cursor:new Date().toISOString()}},"GET /events":e=>{if(!a().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=P(e.project??e.p);if(!t)return{e:"not found"};const r=K(void 0,e.since),n=_(e.limit??e.l,100,1e3),o=e.shared==="1"||e.shared==="true"?a().eventsForPush(t.id,r,n):a().eventsSince(t.id,r,n),d=o.length?o[o.length-1].cursor:r;return{project:t.name,cursor:d,count:o.length,events:o}},"POST /events":e=>{if(!a().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=P(e.project??e.p);if(!t)return{e:"not found"};const r=Array.isArray(e.events)?e.events:e.event?[e.event]:[];if(r.length===0)return{e:"events[] required"};if(r.length>M)return{e:`max ${M} events per request`};let n=0,s=0,o=0;for(const d of r){const i=a().ingestRemoteEvent(t.id,d);i==="inserted"?n++:i==="echo"?o++:s++}return n>0&&h.invalidate("ctx"),{project:t.name,inserted:n,duplicate:s,echo:o}},"GET /stats":()=>{const{result:e,ms:t}=G(()=>a().getStats());return{...e,ms:t,cache:h.stats()}},"GET /health":async e=>{const t={ok:1,ts:Date.now()};if(!/^(1|true|yes)$/i.test(String(e.check_dependencies??"")))return t;const r={...t};try{const s=a().getDatabase().prepare("SELECT MAX(version) v FROM schema_versions").get().v;r.db={ok:1,migration:s}}catch{r.ok=0,r.db={ok:0}}try{const n=A().getVectorStore();if(n){const s=n.getProvider(),o=n.indexCoverage();r.vectors={provider:s.name,model:s.model,ready:await s.isReady(),indexed:o.activeCount,stale:o.stale?1:0,...o.stale?{indexed_under:Object.keys(o.otherModels),hint:"run wyrm_reindex"}:{},egress:s.remoteHost??"none"}}else r.vectors={provider:"none",ready:!1}}catch{r.vectors={ok:0}}return r.rerank={configured:ce()?1:0,provider:(process.env.WYRM_RERANK_PROVIDER||"llamacpp").trim()||"llamacpp",egress:le()??"none"},r},"GET /auth/status":()=>{const e=k();return{auth:e.requireAuth?1:0,origins:e.allowedOrigins.length}},"GET /ui/stats":()=>{const e=a().getDatabase(),t=e.prepare("SELECT COUNT(*) as c FROM projects").get().c,r=e.prepare("SELECT COUNT(*) as c FROM sessions").get().c,n=e.prepare("SELECT COUNT(*) as c FROM quests WHERE status IN ('pending','in_progress')").get().c,s=e.prepare("SELECT COUNT(*) as c FROM data_lake").get().c,o=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 0").get().c,d=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 1").get().c,i=e.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c,l=e.prepare(`
|
|
26
26
|
SELECT date, summary, objectives FROM sessions
|
|
27
27
|
ORDER BY created_at DESC LIMIT 8
|
|
28
|
-
`).all();return{projects:t,sessions:r,active_quests:n,data_points:s,artifacts:
|
|
28
|
+
`).all();return{projects:t,sessions:r,active_quests:n,data_points:s,artifacts:o,review_queue:d,truths:i,recent_sessions:l}},"GET /ui/memories":e=>{const t=a().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=e.kind?String(e.kind):"",o=e.search?String(e.search):"",d=(r-1)*n;let i,l,p,c;if(o){const f=U(o);s?(i=`
|
|
29
29
|
SELECT ma.*, p.name AS project_name
|
|
30
30
|
FROM memory_artifacts ma
|
|
31
31
|
JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
|
|
@@ -36,7 +36,7 @@ import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,est
|
|
|
36
36
|
SELECT COUNT(*) as c FROM memory_artifacts ma
|
|
37
37
|
JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
|
|
38
38
|
WHERE memory_artifacts_fts MATCH ? AND ma.kind = ?
|
|
39
|
-
AND ma.supersedes_id IS NULL`,
|
|
39
|
+
AND ma.supersedes_id IS NULL`,p=[f,s,n,d],c=[f,s]):(i=`
|
|
40
40
|
SELECT ma.*, p.name AS project_name
|
|
41
41
|
FROM memory_artifacts ma
|
|
42
42
|
JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
|
|
@@ -45,37 +45,37 @@ import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,est
|
|
|
45
45
|
ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l=`
|
|
46
46
|
SELECT COUNT(*) as c FROM memory_artifacts ma
|
|
47
47
|
JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
|
|
48
|
-
WHERE memory_artifacts_fts MATCH ? AND ma.supersedes_id IS NULL`,
|
|
48
|
+
WHERE memory_artifacts_fts MATCH ? AND ma.supersedes_id IS NULL`,p=[f,n,d],c=[f])}else s?(i=`
|
|
49
49
|
SELECT ma.*, p.name AS project_name
|
|
50
50
|
FROM memory_artifacts ma
|
|
51
51
|
LEFT JOIN projects p ON ma.project_id = p.id
|
|
52
52
|
WHERE ma.kind = ? AND ma.supersedes_id IS NULL
|
|
53
|
-
ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE kind = ? AND supersedes_id IS NULL",
|
|
53
|
+
ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE kind = ? AND supersedes_id IS NULL",p=[s,n,d],c=[s]):(i=`
|
|
54
54
|
SELECT ma.*, p.name AS project_name
|
|
55
55
|
FROM memory_artifacts ma
|
|
56
56
|
LEFT JOIN projects p ON ma.project_id = p.id
|
|
57
57
|
WHERE ma.supersedes_id IS NULL
|
|
58
|
-
ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE supersedes_id IS NULL",
|
|
58
|
+
ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE supersedes_id IS NULL",p=[n,d],c=[]);const u=t.prepare(i).all(...p),m=t.prepare(l).get(...c).c;return{items:u,total:m,page:r,limit:n}},"GET /ui/quests":()=>{const t=a().getDatabase().prepare(`
|
|
59
59
|
SELECT q.id, q.title, q.status, q.priority, p.name AS project_name
|
|
60
60
|
FROM quests q
|
|
61
61
|
LEFT JOIN projects p ON q.project_id = p.id
|
|
62
62
|
ORDER BY q.created_at DESC
|
|
63
|
-
`).all(),r={pending:[],in_progress:[],completed:[],abandoned:[]};for(const n of t)r[n.status]&&r[n.status].push(n);return r},"GET /ui/
|
|
63
|
+
`).all(),r={pending:[],in_progress:[],completed:[],abandoned:[]};for(const n of t)r[n.status]&&r[n.status].push(n);return r},"GET /ui/warroom":()=>{const e=a().getDatabase(),t=(c,...u)=>{try{const m=e.prepare(c).get(...u);return m?Object.values(m)[0]??0:0}catch{return 0}},r=(c,...u)=>{try{return e.prepare(c).all(...u)}catch{return[]}},n=I(!1).fleetGrove,o=(()=>{try{return e.prepare("SELECT id FROM projects WHERE name = ? LIMIT 1").get(n)}catch{return}})()?.id??-1,d=r("SELECT agent_id, agent_kind, last_heartbeat, ttl_seconds, role FROM agent_presence ORDER BY last_heartbeat DESC LIMIT 24").map(c=>({agent:c.agent_id,kind:c.agent_kind,role:c.role,last:c.last_heartbeat,alive:(()=>{try{const u=(Date.now()-new Date(c.last_heartbeat.replace(" ","T")+"Z").getTime())/1e3;return u>=0&&u<(c.ttl_seconds||300)}catch{return!1}})()})),i={findings:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND status = ?",o,"pending")+t("SELECT COUNT(*) n FROM failure_patterns WHERE project_id = ? AND resolved = 0",o),claimed:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND agent_id IS NOT NULL AND status IN ('pending','in_progress')",o),fixed:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND status = 'completed'",o)+t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%receipt%'",o),verified:t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%verdict%'",o)},l=[];for(const c of r("SELECT id, title, status, agent_id, COALESCE(updated_at, created_at) AS ts, COALESCE(description, '') AS description FROM quests WHERE project_id = ?",o))l.push({type:"request",id:c.id,title:c.title,status:c.status,agent:c.agent_id,ts:c.ts,detail:String(c.description).slice(0,240)});for(const c of r("SELECT id, COALESCE(problem,'') AS problem, COALESCE(validated_fix,'') AS validated_fix, COALESCE(tags,'') AS tags, created_at FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%warroom%'",o)){const u=String(c.tags).includes("verdict")?"verdict":String(c.tags).includes("receipt")?"receipt":"note";l.push({type:u,id:c.id,title:String(c.problem).slice(0,120),status:"",agent:null,ts:c.created_at,detail:String(c.validated_fix).slice(0,240)})}for(const c of r("SELECT id, scope, target, COALESCE(description,'') AS description, last_seen, resolved FROM failure_patterns WHERE project_id = ?",o))l.push({type:"finding",id:c.id,title:`[${c.scope}] ${c.target}`,status:c.resolved?"resolved":"open",agent:null,ts:c.last_seen,detail:String(c.description).slice(0,240)});l.sort((c,u)=>String(u.ts).localeCompare(String(c.ts)));const p={failure_blocks:t("SELECT COUNT(*) n FROM failure_blocks"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) n FROM token_savings_log"),rows_shared:t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND is_shared = 1",o)+t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND is_shared = 1",o)};return{grove:n,found:o!==-1,presence:d,loop:i,metrics:p,feed:l.slice(0,40)}},"GET /ui/truths":e=>{const t=a().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=(r-1)*n,o=t.prepare(`
|
|
64
64
|
SELECT gt.*, p.name AS project_name
|
|
65
65
|
FROM ground_truths gt
|
|
66
66
|
LEFT JOIN projects p ON gt.project_id = p.id
|
|
67
67
|
WHERE gt.is_current = 1
|
|
68
68
|
ORDER BY gt.updated_at DESC LIMIT ? OFFSET ?
|
|
69
|
-
`).all(n,s),
|
|
69
|
+
`).all(n,s),d=t.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c;return{items:o.map(l=>({...l,staleness:me(l)})),total:d,page:r,limit:n}},"GET /ui/review":()=>({items:a().getDatabase().prepare(`
|
|
70
70
|
SELECT ma.*, p.name AS project_name
|
|
71
71
|
FROM memory_artifacts ma
|
|
72
72
|
LEFT JOIN projects p ON ma.project_id = p.id
|
|
73
73
|
WHERE ma.needs_review = 1
|
|
74
74
|
ORDER BY ma.created_at DESC
|
|
75
|
-
`).all()}),"GET /ui/account":()=>{const e=
|
|
75
|
+
`).all()}),"GET /ui/account":()=>{const e=q(a().getDatabasePath());try{const t=S(e,"license.json");if(y(t)){const r=JSON.parse(R(t,"utf-8")),n=$(r);return{account:r.license&&r.license.issued_to||"unknown",tier:n.valid?n.tier:"free",valid:!!n.valid,expires:n.expiresAt||null,readonly:g}}}catch{}try{const t=S(e,"cloud.json");if(y(t))return{account:JSON.parse(R(t,"utf-8")).email||"Local",tier:"free",valid:!1,expires:null,readonly:g}}catch{}return{account:"Local",tier:"free",valid:!1,expires:null,readonly:g}},"GET /ui/impact":()=>{const e=a().getDatabase(),t=r=>{try{return e.prepare(r).get()?.n||0}catch{return 0}};return{failures_blocked:t("SELECT COALESCE(SUM(occurrences),0) AS n FROM failure_patterns"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) AS n FROM token_savings_log"),memories:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE supersedes_id IS NULL"),memories_week:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE created_at >= datetime('now','-7 days')"),truths:t("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1"),sessions:t("SELECT COUNT(*) AS n FROM sessions"),quests_completed:t("SELECT COUNT(*) AS n FROM quests WHERE status = 'completed'"),skills:t("SELECT COUNT(*) AS n FROM skills"),projects:t("SELECT COUNT(*) AS n FROM projects")}},"GET /ui/memory":e=>{const t=a().getDatabase(),r=parseInt(String(e&&e.id||"0"),10);if(!r)return{item:null};try{return{item:t.prepare(`SELECT ma.*, p.name AS project_name
|
|
76
76
|
FROM memory_artifacts ma LEFT JOIN projects p ON ma.project_id = p.id
|
|
77
|
-
WHERE ma.id = ?`).get(r)||null}}catch{return{item:null}}},"GET /ui/recall":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{items:[],total:0};const r=
|
|
78
|
-
`),tokens:
|
|
79
|
-
FROM skills ORDER BY category COLLATE NOCASE, name COLLATE NOCASE`).all();return{items:t,total:t.length}}catch{return{items:[],total:0}}},"GET /ui/homes":()=>{const e=Q();let t=
|
|
77
|
+
WHERE ma.id = ?`).get(r)||null}}catch{return{item:null}}},"GET /ui/recall":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{items:[],total:0};const r=_(e.limit,6,25),n=e.kind?String(e.kind):void 0,o=(await A().recallHybridGlobal(t,{limit:r,kind:n})).map(d=>({...d.artifact,relevance:Math.round(d.relevance_score*1e3)/1e3,match_type:d.match_type}));return{items:o,total:o.length}},"GET /ui/context":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{context:"",items:[],total:0,tokens:0};const r=_(e.limit,10,25),n=_(e.budget,900,2400),s=await A().recallHybridGlobal(t,{limit:r}),o=[],d=[];let i=0;for(const l of s){const p=l.artifact,c=String(p.problem??p.title??p.summary??p.key??p.kind??"").trim(),u=String(p.validated_fix??p.content??p.body??p.value??p.problem??"").replace(/\s+/g," ").trim(),m=`- ${c}${u?": "+u.slice(0,320):""}`,f=Math.ceil(m.length/4);i+f<=n?(o.push(m),i+=f):o.push(`- ${c.slice(0,64)} \u2026 (recall for detail)`),d.push({...p,relevance:Math.round(l.relevance_score*1e3)/1e3,match_type:l.match_type})}return{context:o.join(`
|
|
78
|
+
`),tokens:i,items:d,total:d.length}},"GET /ui/skills":()=>{const e=a().getDatabase();try{const t=e.prepare(`SELECT name, description, category, tier, usage_count, is_active
|
|
79
|
+
FROM skills ORDER BY category COLLATE NOCASE, name COLLATE NOCASE`).all();return{items:t,total:t.length}}catch{return{items:[],total:0}}},"GET /ui/homes":()=>{const e=Q();let t=a().getDatabasePath();try{t=x(t)}catch{}return{homes:e.map(r=>({...r,active:r.dbPath===t}))}},"POST /ui/switch":e=>{const t=String(e&&e.path||""),r=Q().find(n=>n.dbPath===t);if(!r)return{ok:!1,error:"unknown home"};try{return je(new W(r.dbPath)),{ok:!0,account:r.account,tier:r.tier}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}};function Ae(e,t,r){if(!a().liveMemoryEnabled()){E(t,e,{e:"live memory disabled",disabled:1},503);return}const n=P(r.project??r.p);if(!n){E(t,e,{e:"not found"},404);return}const s=e.socket.remoteAddress||"unknown";if(w>=ve){E(t,e,{e:"too many concurrent streams",retry_after:5},503);return}if((T.get(s)??0)>=Ce){E(t,e,{e:"too many concurrent streams from this client",retry_after:5},503);return}w++,T.set(s,(T.get(s)??0)+1);let o=K(e.headers["last-event-id"],r.since);const i=!(s==="127.0.0.1"||s==="::1"||s==="::ffff:127.0.0.1")||r.shared==="1"||r.shared==="true";t.writeHead(200,{...b(e),"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`retry: 3000
|
|
80
80
|
|
|
81
|
-
`);let l,
|
|
81
|
+
`);let l,p,c=!1;const u=()=>{if(c)return;c=!0,l&&clearInterval(l),p&&clearInterval(p),w--;const f=(T.get(s)??1)-1;f<=0?T.delete(s):T.set(s,f)},m=()=>{if(t.writableEnded){u();return}try{const f=i?a().eventsForPush(n.id,o,200):a().eventsSince(n.id,o,200);for(const H of f)o=H.cursor,t.write(Se(H))}catch{}};m(),l=setInterval(m,1e3),p=setInterval(()=>{try{t.write(ye)}catch{}},25e3),e.on("close",u),t.on("close",u),t.on("error",u)}const F=z.createServer(async(e,t)=>{if(!te(e,t).error)try{const n=new URL(e.url||"/",`http://localhost:${j}`);if(g&&ge(e.method||"GET",n.pathname)){E(t,e,{e:"read-only mode",readonly:1},403);return}let s;try{s=await Ne(e)}catch(u){if(u.message==="Invalid JSON"){E(t,e,{e:"Invalid JSON body"},400);return}throw u}if(n.searchParams.forEach((u,m)=>s[m]=u),e.method==="GET"&&n.pathname==="/dragon-mark.svg"){try{if(!v&&y(B)&&(v=R(B)),v){t.writeHead(200,{...b(e),"Content-Type":"image/svg+xml","Cache-Control":"public, max-age=86400","Content-Length":v.length}),t.end(v);return}}catch{}E(t,e,{e:"not found"},404);return}if(e.method==="GET"&&n.pathname==="/ui"){{const u=I(!1);let m=!1;try{m=!!a().getDatabase().prepare("SELECT 1 FROM projects WHERE name = ? LIMIT 1").get(u.fleetGrove)}catch{}Me(t,e,ie(I(m)))}return}if(e.method==="GET"&&n.pathname==="/events/stream"){Ae(e,t,s);return}const o=n.pathname.match(/^\/ui\/review\/(\d+)\/(approve|reject)$/);if(e.method==="POST"&&o){const u=parseInt(o[1],10),m=o[2],f=a().getDatabase();m==="approve"?f.prepare("UPDATE memory_artifacts SET needs_review = 0 WHERE id = ?").run(u):f.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(u),E(t,e,{ok:!0});return}const d=`${e.method} ${n.pathname}`,i=X[d];if(!i){if(n.pathname==="/"){E(t,e,{wyrm:"3.0",auth:k().requireAuth?"required":"disabled",tip:"GET /c for quick context"});return}E(t,e,{e:"not found"},404);return}const l=Te({header:e.headers["wyrm-actor"]}),{result:p,ms:c}=G(()=>Oe(l,()=>i(s)));t.setHeader("X-Time-Ms",String(c)),p instanceof Promise?p.then(u=>E(t,e,u)).catch(u=>{const m=Y(u);if(m!==null){const f=J("http",m);t.setHeader("Retry-After",String(Math.ceil(f.error.retry_after_ms/1e3))),E(t,e,f,503);return}D.error("Fast API request failed",{path:e.url,error:u.message}),E(t,e,{e:"Internal server error"},500)}):E(t,e,p)}catch(n){const s=Y(n);if(s!==null){const o=J("http",s);t.setHeader("Retry-After",String(Math.ceil(o.error.retry_after_ms/1e3))),E(t,e,o,503);return}D.error("Fast API request failed",{path:e.url,error:n.message}),E(t,e,{e:"Internal server error"},500)}}),V=()=>{F.close(()=>{try{O?.close()}catch{}process.exit(0)})};process.on("SIGINT",V),process.on("SIGTERM",V);import{fileURLToPath as De}from"url";const ke=De(import.meta.url);if(process.argv[1]===ke){const e=process.env.WYRM_BIND_HOST||"127.0.0.1";F.listen(j,e,()=>{D.info("Wyrm Fast API started",{port:j,host:e}),console.log(`\u{F115D} Wyrm Fast API on ${e}:${j}`),console.log(` Auth: ${k().requireAuth?"required":"disabled"}`),g&&console.log(" Read-only: writes + off-box egress blocked (safe to expose)"),e!=="127.0.0.1"&&e!=="::1"&&e!=="localhost"&&(D.warn("Wyrm Fast API bound to a NON-loopback host \u2014 reachable beyond localhost; ensure auth is required",{host:e}),console.log(` \u26A0 bound to ${e}: reachable beyond localhost \u2014 make sure auth is required`),g||console.log(" \u26A0 not in read-only mode; set WYRM_UI_READONLY=1 for a public bind"))})}export{F as server};
|
package/dist/tool-manifest.json
CHANGED
package/dist/ui-dashboard.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
function e()
|
|
1
|
+
import{resolveBranding as a}from"./branding.js";function r(t){const e=t??a(!1);return`<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'">
|
|
7
|
-
<title
|
|
7
|
+
<title>${e.product} Dashboard</title>
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
10
|
<style>
|
|
@@ -221,13 +221,70 @@ main { padding: calc(var(--nav-h) + 24px) 24px 24px; max-width: 1200px; margin:
|
|
|
221
221
|
.modal-section .ms-body { color: var(--muted); font-size: 13px; white-space: pre-wrap; line-height: 1.65; }
|
|
222
222
|
.item.clickable { cursor: pointer; }
|
|
223
223
|
.item.clickable:hover { border-color: var(--accent-dim); }
|
|
224
|
+
|
|
225
|
+
/* \u2500\u2500 FLEET CHANNEL (operator-branded) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
226
|
+
.tab-warroom { color: var(--pos) !important; font-weight: 600; }
|
|
227
|
+
.tab-warroom.active { color: var(--pos) !important; background: rgba(52,211,153,0.10); }
|
|
228
|
+
.wr-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 4px; }
|
|
229
|
+
.wr-title { font-size: 22px; font-weight: 800; letter-spacing: -0.01em; }
|
|
230
|
+
.wr-grove { font-family: var(--mono); font-size: 11px; color: var(--muted); border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px; }
|
|
231
|
+
.wr-live { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 11px; color: var(--pos); margin-left: auto; }
|
|
232
|
+
.wr-live .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pos); box-shadow: 0 0 8px var(--pos); animation: wrPulse 1.8s ease-in-out infinite; }
|
|
233
|
+
.wr-sub { color: var(--muted); font-size: 13px; margin-bottom: 22px; max-width: 640px; }
|
|
234
|
+
@keyframes wrPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
|
|
235
|
+
|
|
236
|
+
/* The signature: the loop pipeline. findings -> claimed -> fixed -> verified. */
|
|
237
|
+
.wr-loop { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr; align-items: stretch; gap: 0; margin: 8px 0 30px; }
|
|
238
|
+
.wr-stage { background: linear-gradient(180deg, #0c0c0c, #070707); border: 1px solid var(--border); border-radius: 14px; padding: 20px 18px; position: relative; overflow: hidden; }
|
|
239
|
+
.wr-stage::before { content: ''; position: absolute; inset: 0 0 auto 0; height: 2px; background: var(--stage-c, var(--neu)); opacity: 0.5; }
|
|
240
|
+
.wr-stage .wr-k { font-size: 34px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1; }
|
|
241
|
+
.wr-stage .wr-l { font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.16em; color: var(--muted); margin-top: 8px; }
|
|
242
|
+
.wr-stage .wr-c { position: absolute; top: 16px; right: 16px; width: 8px; height: 8px; border-radius: 50%; background: var(--stage-c, var(--neu)); box-shadow: 0 0 10px var(--stage-c, var(--neu)); }
|
|
243
|
+
.wr-s-find { --stage-c: var(--neg); }
|
|
244
|
+
.wr-s-claim { --stage-c: var(--accent); }
|
|
245
|
+
.wr-s-fix { --stage-c: oklch(0.80 0.13 200); }
|
|
246
|
+
.wr-s-verify { --stage-c: var(--pos); }
|
|
247
|
+
/* Connectors carry a travelling pulse \u2014 work flowing down the pipe. */
|
|
248
|
+
.wr-conn { position: relative; align-self: center; width: 100%; min-width: 26px; height: 2px; background: var(--border); margin: 0 -1px; }
|
|
249
|
+
.wr-conn::after { content: ''; position: absolute; top: -1px; left: 0; width: 22px; height: 4px; border-radius: 4px; background: linear-gradient(90deg, transparent, var(--pos), transparent); animation: wrFlow 2.6s linear infinite; }
|
|
250
|
+
@keyframes wrFlow { from { left: -22px; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } to { left: 100%; opacity: 0; } }
|
|
251
|
+
|
|
252
|
+
/* Presence board \u2014 agent LEDs. */
|
|
253
|
+
.wr-presence { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 26px; }
|
|
254
|
+
.wr-agent { display: inline-flex; align-items: center; gap: 8px; background: var(--card); border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px 6px 10px; font-size: 12px; }
|
|
255
|
+
.wr-agent .led { width: 8px; height: 8px; border-radius: 50%; background: var(--neu); }
|
|
256
|
+
.wr-agent.alive .led { background: var(--pos); box-shadow: 0 0 8px var(--pos); animation: wrPulse 2s ease-in-out infinite; }
|
|
257
|
+
.wr-agent .nm { font-family: var(--mono); color: var(--text); }
|
|
258
|
+
.wr-agent .kd { color: var(--muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
259
|
+
.wr-agent.dead { opacity: 0.5; }
|
|
260
|
+
|
|
261
|
+
/* Metrics strip. */
|
|
262
|
+
.wr-metrics { display: flex; gap: 26px; padding: 14px 20px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 28px; }
|
|
263
|
+
.wr-metrics .m .mv { font-size: 20px; font-weight: 700; color: var(--pos); font-variant-numeric: tabular-nums; }
|
|
264
|
+
.wr-metrics .m .ml { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); margin-top: 3px; }
|
|
265
|
+
|
|
266
|
+
/* Activity feed \u2014 type-coded left rail. */
|
|
267
|
+
.wr-feed { display: flex; flex-direction: column; gap: 10px; }
|
|
268
|
+
.wr-row { display: grid; grid-template-columns: auto 1fr auto; gap: 14px; align-items: start; background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--row-c, var(--neu)); border-radius: 10px; padding: 13px 16px; }
|
|
269
|
+
.wr-row .wr-badge { font-family: var(--mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--row-c, var(--neu)); border: 1px solid var(--row-c, var(--neu)); border-radius: 5px; padding: 3px 7px; align-self: start; white-space: nowrap; }
|
|
270
|
+
.wr-row .wr-t { font-weight: 600; color: var(--text); font-size: 13.5px; }
|
|
271
|
+
.wr-row .wr-d { color: var(--muted); font-size: 12px; margin-top: 4px; line-height: 1.55; }
|
|
272
|
+
.wr-row .wr-m { font-family: var(--mono); font-size: 10.5px; color: var(--muted); text-align: right; white-space: nowrap; }
|
|
273
|
+
.wr-row .wr-m .who { color: var(--accent); }
|
|
274
|
+
.wr-t-finding { --row-c: var(--neg); }
|
|
275
|
+
.wr-t-request { --row-c: var(--neu); }
|
|
276
|
+
.wr-t-receipt { --row-c: oklch(0.80 0.13 200); }
|
|
277
|
+
.wr-t-verdict { --row-c: var(--pos); }
|
|
278
|
+
.wr-empty { text-align: center; color: var(--muted); padding: 40px 0; font-family: var(--mono); font-size: 12px; }
|
|
279
|
+
@media (max-width: 760px) { .wr-loop { grid-template-columns: 1fr; gap: 10px; } .wr-conn { display: none; } }
|
|
224
280
|
</style>
|
|
225
281
|
</head>
|
|
226
282
|
<body>
|
|
227
283
|
<nav>
|
|
228
|
-
<div class="logo"
|
|
284
|
+
<div class="logo">${e.markHtml}${e.product}</div>
|
|
229
285
|
<div class="tabs" role="tablist">
|
|
230
286
|
<button class="tab-btn active" data-tab="overview" role="tab">Overview</button>
|
|
287
|
+
${e.showFleet?`<button class="tab-btn tab-warroom" data-tab="warroom" role="tab">${e.fleetLabel}</button>`:""}
|
|
231
288
|
<button class="tab-btn" data-tab="impact" role="tab">Impact</button>
|
|
232
289
|
<button class="tab-btn" data-tab="memories" role="tab">Memories</button>
|
|
233
290
|
<button class="tab-btn" data-tab="quests" role="tab">Quests</button>
|
|
@@ -240,6 +297,7 @@ main { padding: calc(var(--nav-h) + 24px) 24px 24px; max-width: 1200px; margin:
|
|
|
240
297
|
</nav>
|
|
241
298
|
<main>
|
|
242
299
|
<div id="pane-overview" class="pane active"></div>
|
|
300
|
+
${e.showFleet?'<div id="pane-warroom" class="pane"></div>':""}
|
|
243
301
|
<div id="pane-impact" class="pane"></div>
|
|
244
302
|
<div id="pane-memories" class="pane"></div>
|
|
245
303
|
<div id="pane-quests" class="pane"></div>
|
|
@@ -375,7 +433,7 @@ function staleBar(staleness) {
|
|
|
375
433
|
}
|
|
376
434
|
|
|
377
435
|
// \u2500\u2500 Router \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
378
|
-
const TABS = ['overview', 'impact', 'memories', 'quests', 'truths', 'skills', 'review'];
|
|
436
|
+
const TABS = ['overview', ${e.showFleet?"'warroom', ":""}'impact', 'memories', 'quests', 'truths', 'skills', 'review'];
|
|
379
437
|
let currentTab = '';
|
|
380
438
|
// Set from GET /ui/account.readonly at boot (WYRM_UI_READONLY=1 on the server).
|
|
381
439
|
// The server 403s every write regardless; this just hides the controls (UX).
|
|
@@ -406,6 +464,109 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
|
406
464
|
});
|
|
407
465
|
window.addEventListener('hashchange', router);
|
|
408
466
|
|
|
467
|
+
// \u2500\u2500 Fleet channel (operator-branded) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
468
|
+
function wrRelTime(ts) {
|
|
469
|
+
try {
|
|
470
|
+
const then = new Date(String(ts).replace(' ', 'T') + (String(ts).includes('Z') ? '' : 'Z')).getTime();
|
|
471
|
+
const s = Math.max(0, (Date.now() - then) / 1000);
|
|
472
|
+
if (s < 60) return Math.floor(s) + 's ago';
|
|
473
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
|
474
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
|
475
|
+
return Math.floor(s / 86400) + 'd ago';
|
|
476
|
+
} catch(e) { return String(ts || ''); }
|
|
477
|
+
}
|
|
478
|
+
function wrNum(n) { return (n == null ? 0 : n).toLocaleString(); }
|
|
479
|
+
|
|
480
|
+
async function loadWarroom() {
|
|
481
|
+
const pane = document.getElementById('pane-warroom');
|
|
482
|
+
clearEl(pane);
|
|
483
|
+
|
|
484
|
+
// Header
|
|
485
|
+
const head = el('div', 'wr-head');
|
|
486
|
+
head.appendChild(el('span', 'wr-title', ${JSON.stringify(e.fleetTitle)}));
|
|
487
|
+
head.appendChild(el('span', 'wr-grove', 'grove: ' + ${JSON.stringify(e.fleetGrove)}));
|
|
488
|
+
const live = el('span', 'wr-live'); live.appendChild(el('span', 'dot')); live.appendChild(el('span', null, 'LIVE'));
|
|
489
|
+
head.appendChild(live);
|
|
490
|
+
pane.appendChild(head);
|
|
491
|
+
pane.appendChild(el('div', 'wr-sub', ${JSON.stringify(e.fleetBlurb)}));
|
|
492
|
+
|
|
493
|
+
let data;
|
|
494
|
+
try { data = await apiFetch('/ui/warroom'); }
|
|
495
|
+
catch(e) { pane.appendChild(el('div', 'wr-empty', 'War room unavailable.')); return; }
|
|
496
|
+
|
|
497
|
+
if (!data.found) {
|
|
498
|
+
pane.appendChild(el('div', 'wr-empty', 'fleet-warroom grove not found on this device yet.'));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// The loop pipeline (signature element)
|
|
503
|
+
const loop = el('div', 'wr-loop');
|
|
504
|
+
const stages = [
|
|
505
|
+
['wr-s-find', 'findings', data.loop.findings, 'Findings'],
|
|
506
|
+
['wr-s-claim', 'claimed', data.loop.claimed, 'Claimed'],
|
|
507
|
+
['wr-s-fix', 'fixed', data.loop.fixed, 'Fixed'],
|
|
508
|
+
['wr-s-verify', 'verified', data.loop.verified, 'Verified'],
|
|
509
|
+
];
|
|
510
|
+
stages.forEach((st, i) => {
|
|
511
|
+
const node = el('div', 'wr-stage ' + st[0]);
|
|
512
|
+
node.appendChild(el('span', 'wr-c'));
|
|
513
|
+
node.appendChild(el('div', 'wr-k', String(st[2] ?? 0)));
|
|
514
|
+
node.appendChild(el('div', 'wr-l', st[3]));
|
|
515
|
+
loop.appendChild(node);
|
|
516
|
+
if (i < stages.length - 1) loop.appendChild(el('div', 'wr-conn'));
|
|
517
|
+
});
|
|
518
|
+
pane.appendChild(loop);
|
|
519
|
+
|
|
520
|
+
// Presence board
|
|
521
|
+
const pres = el('div', 'wr-presence');
|
|
522
|
+
if (!data.presence.length) {
|
|
523
|
+
const a = el('div', 'wr-agent dead'); a.appendChild(el('span', 'led'));
|
|
524
|
+
a.appendChild(el('span', 'nm', 'no agents on the board'));
|
|
525
|
+
pres.appendChild(a);
|
|
526
|
+
} else {
|
|
527
|
+
data.presence.forEach(p => {
|
|
528
|
+
const a = el('div', 'wr-agent ' + (p.alive ? 'alive' : 'dead'));
|
|
529
|
+
a.appendChild(el('span', 'led'));
|
|
530
|
+
a.appendChild(el('span', 'nm', p.agent));
|
|
531
|
+
a.appendChild(el('span', 'kd', p.kind + (p.role ? ' \xB7 ' + p.role : '')));
|
|
532
|
+
pres.appendChild(a);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
pane.appendChild(pres);
|
|
536
|
+
|
|
537
|
+
// Metrics strip (the "does it help" numbers)
|
|
538
|
+
const met = el('div', 'wr-metrics');
|
|
539
|
+
[['repeats blocked', data.metrics.failure_blocks], ['tokens saved', data.metrics.tokens_saved], ['rows shared', data.metrics.rows_shared]].forEach(m => {
|
|
540
|
+
const c = el('div', 'm');
|
|
541
|
+
c.appendChild(el('div', 'mv', wrNum(m[1])));
|
|
542
|
+
c.appendChild(el('div', 'ml', m[0]));
|
|
543
|
+
met.appendChild(c);
|
|
544
|
+
});
|
|
545
|
+
pane.appendChild(met);
|
|
546
|
+
|
|
547
|
+
// Activity feed
|
|
548
|
+
pane.appendChild(el('div', 'section-title', 'Activity'));
|
|
549
|
+
const feed = el('div', 'wr-feed');
|
|
550
|
+
if (!data.feed.length) {
|
|
551
|
+
feed.appendChild(el('div', 'wr-empty', 'No war-room activity yet. The first finding a fleet agent files will appear here.'));
|
|
552
|
+
} else {
|
|
553
|
+
data.feed.forEach(r => {
|
|
554
|
+
const row = el('div', 'wr-row wr-t-' + (r.type || 'note'));
|
|
555
|
+
row.appendChild(el('span', 'wr-badge', r.type));
|
|
556
|
+
const mid = el('div');
|
|
557
|
+
mid.appendChild(el('div', 'wr-t', truncate(r.title, 90) + (r.status ? ' \xB7 ' + r.status : '')));
|
|
558
|
+
if (r.detail) mid.appendChild(el('div', 'wr-d', truncate(r.detail, 200)));
|
|
559
|
+
row.appendChild(mid);
|
|
560
|
+
const meta = el('div', 'wr-m');
|
|
561
|
+
if (r.agent) { const w = el('div'); w.appendChild(el('span', 'who', r.agent)); meta.appendChild(w); }
|
|
562
|
+
meta.appendChild(el('div', null, wrRelTime(r.ts)));
|
|
563
|
+
row.appendChild(meta);
|
|
564
|
+
feed.appendChild(row);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
pane.appendChild(feed);
|
|
568
|
+
}
|
|
569
|
+
|
|
409
570
|
// \u2500\u2500 Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
410
571
|
async function loadOverview() {
|
|
411
572
|
const pane = document.getElementById('pane-overview');
|
|
@@ -1001,6 +1162,7 @@ function buildPagination(current, total, onPage) {
|
|
|
1001
1162
|
// \u2500\u2500 Tab loader \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1002
1163
|
function loadTab(name) {
|
|
1003
1164
|
if (name === 'overview') loadOverview();
|
|
1165
|
+
else if (name === 'warroom') loadWarroom();
|
|
1004
1166
|
else if (name === 'impact') loadImpact();
|
|
1005
1167
|
else if (name === 'memories') { initMemoriesPane(); loadMemories(memPage, memKind, memSearch); }
|
|
1006
1168
|
else if (name === 'quests') loadQuests();
|
|
@@ -1016,4 +1178,4 @@ loadAccount().then(router);
|
|
|
1016
1178
|
})();
|
|
1017
1179
|
<\/script>
|
|
1018
1180
|
</body>
|
|
1019
|
-
</html>`}export{
|
|
1181
|
+
</html>`}export{r as getUIDashboardHTML};
|
package/dist/wyrm-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wyrm-mcp",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.1",
|
|
4
4
|
"mcpName": "lk.ghosts/wyrm",
|
|
5
5
|
"description": "Local-first persistent memory for AI agents over MCP. Ground truths, negative learning (recorded failures block repeats), decision causality, hybrid recall, live memory streams, run-attributed fleet memory — a structured SQLite memory on your machine, no cloud or LLM required. Claude / Copilot / Cursor / Windsurf / Codex.",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
-
"ui",
|
|
21
20
|
"skills/wyrm-getting-started",
|
|
22
21
|
"skills/wyrm-memory-and-recall",
|
|
23
22
|
"skills/wyrm-nvidia-nim",
|