chainlesschain 0.45.75 → 0.45.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +52 -15
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-sBrYoc3A.js → Analytics-Dd2DjBH5.js} +2 -2
  5. package/src/assets/web-panel/assets/AppLayout-CP9fATUN.js +1 -0
  6. package/src/assets/web-panel/assets/AppLayout-cxfKLu-m.css +1 -0
  7. package/src/assets/web-panel/assets/Backup-D6Tc7sf3.js +1 -0
  8. package/src/assets/web-panel/assets/Chat-DDUJZJ9I.js +1 -0
  9. package/src/assets/web-panel/assets/Chat-DfR76jyX.css +1 -0
  10. package/src/assets/web-panel/assets/Cowork-CPqYhoMI.css +1 -0
  11. package/src/assets/web-panel/assets/Cowork-XRFqGfqJ.js +48 -0
  12. package/src/assets/web-panel/assets/{Cron-CNs03iHJ.js → Cron-BnWzy_ZB.js} +2 -2
  13. package/src/assets/web-panel/assets/{Dashboard-DanoHPSI.js → Dashboard-D2vCkoGu.js} +1 -1
  14. package/src/assets/web-panel/assets/{Git-CCMVr3Y8.js → Git-DYlvK4sh.js} +2 -2
  15. package/src/assets/web-panel/assets/{Logs-BY6A0UNG.js → Logs-4VgUbfP0.js} +2 -2
  16. package/src/assets/web-panel/assets/{McpTools-CrBVYlg6.js → McpTools-ChaiHoWY.js} +2 -2
  17. package/src/assets/web-panel/assets/{Memory-CWx3SpUt.js → Memory-PFtpuOwf.js} +2 -2
  18. package/src/assets/web-panel/assets/{Notes-1LcGD49x.js → Notes-wc_n6Rh1.js} +2 -2
  19. package/src/assets/web-panel/assets/{Organization-Dx2DhbkM.js → Organization-D1qUa8NQ.js} +4 -4
  20. package/src/assets/web-panel/assets/{P2P-B16fjqfJ.js → P2P-DIG2gnR8.js} +2 -2
  21. package/src/assets/web-panel/assets/{Permissions-BQbC9FzG.js → Permissions-CpE-Ar1e.js} +3 -3
  22. package/src/assets/web-panel/assets/{Projects-CjhZbNYm.js → Projects-GjuS-C6U.js} +2 -2
  23. package/src/assets/web-panel/assets/{Providers-ivOAQtHM.js → Providers-CCfGeqh_.js} +2 -2
  24. package/src/assets/web-panel/assets/{RssFeed-BrsErdrU.js → RssFeed-5TkrXK7Z.js} +1 -1
  25. package/src/assets/web-panel/assets/{Security-DnEvJU5h.js → Security-CcfBWT1D.js} +3 -3
  26. package/src/assets/web-panel/assets/{Services-7jQywNbl.js → Services-Cnm5Zs5h.js} +1 -1
  27. package/src/assets/web-panel/assets/{Skills-CLlblJcG.js → Skills-BHapMb9h.js} +1 -1
  28. package/src/assets/web-panel/assets/{Tasks-CmJBC1cf.js → Tasks-DPb9OMck.js} +1 -1
  29. package/src/assets/web-panel/assets/Templates-Dij5t-rf.js +1 -0
  30. package/src/assets/web-panel/assets/{Wallet-3iYASEx_.js → Wallet-BJV5KmWA.js} +4 -4
  31. package/src/assets/web-panel/assets/{WebAuthn-s3Hzd9db.js → WebAuthn-DLkvYwSc.js} +5 -5
  32. package/src/assets/web-panel/assets/{antd-gZyc63Qr.js → antd-BQNxIyr-.js} +82 -82
  33. package/src/assets/web-panel/assets/github-dark-Dfs9RUU9.css +1 -0
  34. package/src/assets/web-panel/assets/index-CB5YlndO.js +2 -0
  35. package/src/assets/web-panel/assets/{markdown-Bv7nG63L.js → markdown-BeVIhIzs.js} +1 -1
  36. package/src/assets/web-panel/index.html +2 -2
  37. package/src/commands/learning.js +273 -0
  38. package/src/commands/lowcode.js +23 -8
  39. package/src/gateways/discord/discord-formatter.js +89 -0
  40. package/src/gateways/gateway-base.js +189 -0
  41. package/src/gateways/telegram/telegram-formatter.js +93 -0
  42. package/src/gateways/ws/action-protocol.js +54 -1
  43. package/src/gateways/ws/message-dispatcher.js +1 -0
  44. package/src/gateways/ws/ws-server.js +10 -1
  45. package/src/index.js +2 -0
  46. package/src/lib/app-builder.js +136 -8
  47. package/src/lib/autonomous-agent.js +8 -1
  48. package/src/lib/cli-context-engineering.js +15 -0
  49. package/src/lib/cowork-task-runner.js +101 -0
  50. package/src/lib/cowork-task-templates.js +493 -0
  51. package/src/lib/execution-backend.js +239 -0
  52. package/src/lib/hook-manager.js +2 -0
  53. package/src/lib/iteration-budget.js +175 -0
  54. package/src/lib/learning/learning-hooks.js +117 -0
  55. package/src/lib/learning/learning-tables.js +66 -0
  56. package/src/lib/learning/outcome-feedback.js +243 -0
  57. package/src/lib/learning/reflection-engine.js +323 -0
  58. package/src/lib/learning/skill-improver.js +536 -0
  59. package/src/lib/learning/skill-synthesizer.js +315 -0
  60. package/src/lib/learning/trajectory-store.js +409 -0
  61. package/src/lib/plugin-autodiscovery.js +224 -0
  62. package/src/lib/session-search.js +193 -0
  63. package/src/lib/sub-agent-context.js +7 -2
  64. package/src/lib/user-profile.js +172 -0
  65. package/src/lib/web-ui-server.js +1 -1
  66. package/src/repl/agent-repl.js +109 -0
  67. package/src/runtime/agent-core.js +75 -4
  68. package/src/runtime/coding-agent-contract-shared.cjs +35 -0
  69. package/src/runtime/coding-agent-policy.cjs +10 -0
  70. package/src/assets/web-panel/assets/AppLayout-2RCrdXxl.js +0 -1
  71. package/src/assets/web-panel/assets/AppLayout-D9pBLPC3.css +0 -1
  72. package/src/assets/web-panel/assets/Backup-D68fenbD.js +0 -1
  73. package/src/assets/web-panel/assets/Chat-B2nB8o_F.js +0 -1
  74. package/src/assets/web-panel/assets/Chat-DB46afPg.css +0 -1
  75. package/src/assets/web-panel/assets/Templates-RXT8-DNk.js +0 -1
  76. package/src/assets/web-panel/assets/index-CyGtHm63.js +0 -2
@@ -0,0 +1 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
@@ -0,0 +1,2 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./AppLayout-CP9fATUN.js","./vendor-CN0Iv_qZ.js","./ws-CU7Gvoom.js","./_plugin-vue_export-helper-DlAUqK2U.js","./antd-BQNxIyr-.js","./AppLayout-cxfKLu-m.css","./Dashboard-D2vCkoGu.js","./chat-DWBA4-cl.js","./Dashboard-CKeMmCoT.css","./Chat-DDUJZJ9I.js","./markdown-BeVIhIzs.js","./Chat-DfR76jyX.css","./github-dark-Dfs9RUU9.css","./Cowork-XRFqGfqJ.js","./Cowork-CPqYhoMI.css","./Services-Cnm5Zs5h.js","./Services-C8Qs6KXv.css","./Logs-4VgUbfP0.js","./Logs-Gf_Mv9Nx.css","./Skills-BHapMb9h.js","./parsers-DftYMnlk.js","./Skills-BdjRyorN.css","./Providers-CCfGeqh_.js","./Providers-BEakqcO5.css","./McpTools-ChaiHoWY.js","./McpTools-CyhSLDwf.css","./Notes-wc_n6Rh1.js","./Notes-BG69sJKi.css","./Memory-PFtpuOwf.js","./Memory-DRghrGJr.css","./Cron-BnWzy_ZB.js","./Tasks-DPb9OMck.js","./Tasks-BJjN_YEm.css","./Security-CcfBWT1D.js","./Security-Dwxw7rfP.css","./Permissions-CpE-Ar1e.js","./Permissions-C9WlkGl-.css","./P2P-DIG2gnR8.js","./P2P-OEzOeMZX.css","./Git-DYlvK4sh.js","./Git-DGcuBXST.css","./Projects-GjuS-C6U.js","./Projects-DxKelI5h.css","./Wallet-BJV5KmWA.js","./Wallet-DnIumafl.css","./Organization-D1qUa8NQ.js","./Organization-DdOOM4ic.css","./Analytics-Dd2DjBH5.js","./Analytics-B4OM8S8X.css","./Templates-Dij5t-rf.js","./Templates-DOY_oZnm.css","./Backup-D6Tc7sf3.js","./Backup-fZqtfC1m.css","./RssFeed-5TkrXK7Z.js","./RssFeed-BlFC20eg.css","./WebAuthn-DLkvYwSc.js","./WebAuthn-CNPl2VQR.css"])))=>i.map(i=>d[i]);
2
+ import{S as B,U as L,V as I,f as T,c as E,o as R,W as O,u as k,X as y,Y as V,Z as x,k as D,R as S,_ as w}from"./vendor-CN0Iv_qZ.js";import{a as g,A as M}from"./antd-BQNxIyr-.js";(function(){const a=document.createElement("link").relList;if(a&&a.supports&&a.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))m(t);new MutationObserver(t=>{for(const o of t)if(o.type==="childList")for(const n of o.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&m(n)}).observe(document,{childList:!0,subtree:!0});function c(t){const o={};return t.integrity&&(o.integrity=t.integrity),t.referrerPolicy&&(o.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?o.credentials="include":t.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function m(t){if(t.ep)return;t.ep=!0;const o=c(t);fetch(t.href,o)}})();const C="modulepreload",N=function(s,a){return new URL(s,a).href},P={},e=function(a,c,m){let t=Promise.resolve();if(c&&c.length>0){let b=function(i){return Promise.all(i.map(d=>Promise.resolve(d).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};const n=document.getElementsByTagName("link"),r=document.querySelector("meta[property=csp-nonce]"),u=r?.nonce||r?.getAttribute("nonce");t=b(c.map(i=>{if(i=N(i,m),i in P)return;P[i]=!0;const d=i.endsWith(".css"),p=d?'[rel="stylesheet"]':"";if(m)for(let f=n.length-1;f>=0;f--){const _=n[f];if(_.href===i&&(!d||_.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${p}`))return;const l=document.createElement("link");if(l.rel=d?"stylesheet":C,d||(l.as="script"),l.crossOrigin="",l.href=i,u&&l.setAttribute("nonce",u),document.head.appendChild(l),d)return new Promise((f,_)=>{l.addEventListener("load",f),l.addEventListener("error",()=>_(new Error(`Unable to preload CSS for ${i}`)))})}))}function o(n){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=n,window.dispatchEvent(r),!r.defaultPrevented)throw n}return t.then(n=>{for(const r of n||[])r.status==="rejected"&&o(r.reason);return a().catch(o)})},U=[{path:"/",component:()=>e(()=>import("./AppLayout-CP9fATUN.js"),__vite__mapDeps([0,1,2,3,4,5]),import.meta.url),children:[{path:"",redirect:"/dashboard"},{path:"dashboard",name:"Dashboard",component:()=>e(()=>import("./Dashboard-D2vCkoGu.js"),__vite__mapDeps([6,1,2,7,3,4,8]),import.meta.url)},{path:"chat",name:"Chat",component:()=>e(()=>import("./Chat-DDUJZJ9I.js"),__vite__mapDeps([9,1,10,4,7,2,3,11,12]),import.meta.url)},{path:"cowork",name:"Cowork",component:()=>e(()=>import("./Cowork-XRFqGfqJ.js"),__vite__mapDeps([13,1,10,4,2,7,3,14,12]),import.meta.url)},{path:"services",name:"Services",component:()=>e(()=>import("./Services-Cnm5Zs5h.js"),__vite__mapDeps([15,2,1,3,4,16]),import.meta.url)},{path:"logs",name:"Logs",component:()=>e(()=>import("./Logs-4VgUbfP0.js"),__vite__mapDeps([17,2,1,3,4,18]),import.meta.url)},{path:"skills",name:"Skills",component:()=>e(()=>import("./Skills-BHapMb9h.js"),__vite__mapDeps([19,1,2,20,7,3,4,21]),import.meta.url)},{path:"providers",name:"Providers",component:()=>e(()=>import("./Providers-CCfGeqh_.js"),__vite__mapDeps([22,1,2,20,3,4,23]),import.meta.url)},{path:"mcp",name:"McpTools",component:()=>e(()=>import("./McpTools-ChaiHoWY.js"),__vite__mapDeps([24,2,1,3,4,25]),import.meta.url)},{path:"notes",name:"Notes",component:()=>e(()=>import("./Notes-wc_n6Rh1.js"),__vite__mapDeps([26,2,1,3,4,27]),import.meta.url)},{path:"memory",name:"Memory",component:()=>e(()=>import("./Memory-PFtpuOwf.js"),__vite__mapDeps([28,2,1,3,4,29]),import.meta.url)},{path:"cron",name:"Cron",component:()=>e(()=>import("./Cron-BnWzy_ZB.js"),__vite__mapDeps([30,2,1,4]),import.meta.url)},{path:"tasks",name:"Tasks",component:()=>e(()=>import("./Tasks-DPb9OMck.js"),__vite__mapDeps([31,1,2,3,4,32]),import.meta.url)},{path:"security",name:"Security",component:()=>e(()=>import("./Security-CcfBWT1D.js"),__vite__mapDeps([33,2,1,3,4,34]),import.meta.url)},{path:"permissions",name:"Permissions",component:()=>e(()=>import("./Permissions-CpE-Ar1e.js"),__vite__mapDeps([35,2,1,3,4,36]),import.meta.url)},{path:"p2p",name:"P2P",component:()=>e(()=>import("./P2P-DIG2gnR8.js"),__vite__mapDeps([37,2,1,3,4,38]),import.meta.url)},{path:"git",name:"Git",component:()=>e(()=>import("./Git-DYlvK4sh.js"),__vite__mapDeps([39,2,1,3,4,40]),import.meta.url)},{path:"projects",name:"Projects",component:()=>e(()=>import("./Projects-GjuS-C6U.js"),__vite__mapDeps([41,2,1,3,4,42]),import.meta.url)},{path:"wallet",name:"Wallet",component:()=>e(()=>import("./Wallet-BJV5KmWA.js"),__vite__mapDeps([43,2,1,3,4,44]),import.meta.url)},{path:"organization",name:"Organization",component:()=>e(()=>import("./Organization-D1qUa8NQ.js"),__vite__mapDeps([45,1,2,3,4,46]),import.meta.url)},{path:"analytics",name:"Analytics",component:()=>e(()=>import("./Analytics-Dd2DjBH5.js"),__vite__mapDeps([47,2,1,3,4,48]),import.meta.url)},{path:"templates",name:"Templates",component:()=>e(()=>import("./Templates-Dij5t-rf.js"),__vite__mapDeps([49,1,2,3,4,50]),import.meta.url)},{path:"backup",name:"Backup",component:()=>e(()=>import("./Backup-D6Tc7sf3.js"),__vite__mapDeps([51,2,1,3,4,52]),import.meta.url)},{path:"rssfeed",name:"RssFeed",component:()=>e(()=>import("./RssFeed-5TkrXK7Z.js"),__vite__mapDeps([53,1,2,3,4,54]),import.meta.url)},{path:"webauthn",name:"WebAuthn",component:()=>e(()=>import("./WebAuthn-DLkvYwSc.js"),__vite__mapDeps([55,2,1,3,4,56]),import.meta.url)}]}],F=B({history:L(),routes:U}),A="cc_theme",v={dark:{label:"暗黑",icon:"🌑",antd:{algorithm:g.darkAlgorithm,token:{colorPrimary:"#1677ff",colorBgBase:"#141414",colorBgContainer:"#1f1f1f",colorBgElevated:"#2a2a2a",borderRadius:8,fontFamily:'system-ui, -apple-system, "Segoe UI", sans-serif'},components:{Layout:{siderBg:"#1c1c1c",headerBg:"#1c1c1c",bodyBg:"#141414"},Menu:{darkItemBg:"#1c1c1c",darkSubMenuItemBg:"#171717"}}},vars:{"--bg-base":"#141414","--bg-sidebar":"#1c1c1c","--bg-header":"#1c1c1c","--bg-card":"#1f1f1f","--bg-card-hover":"#262626","--border-color":"#252525","--border-subtle":"#1e1e1e","--text-primary":"#e0e0e0","--text-secondary":"#888","--text-muted":"#444","--logo-text":"#ffffff","--menu-mode":"dark","--shadow-card":"0 2px 8px rgba(0,0,0,.45)","--group-title":"#3a3a3a"}},light:{label:"亮白",icon:"☀️",antd:{algorithm:g.defaultAlgorithm,token:{colorPrimary:"#1677ff",colorBgBase:"#ffffff",colorBgContainer:"#ffffff",colorBgElevated:"#ffffff",borderRadius:8,fontFamily:'system-ui, -apple-system, "Segoe UI", sans-serif'},components:{Layout:{siderBg:"#ffffff",headerBg:"#ffffff",bodyBg:"#f4f6fb"},Menu:{itemBg:"#ffffff"}}},vars:{"--bg-base":"#f4f6fb","--bg-sidebar":"#ffffff","--bg-header":"#ffffff","--bg-card":"#ffffff","--bg-card-hover":"#f0f4ff","--border-color":"#e8edf5","--border-subtle":"#f0f0f0","--text-primary":"#1a1a2e","--text-secondary":"#5a6474","--text-muted":"#b0b8c8","--logo-text":"#1a1a2e","--menu-mode":"light","--shadow-card":"0 2px 12px rgba(0,0,0,.07)","--group-title":"#aab0bc"}},blue:{label:"深蓝",icon:"🌊",antd:{algorithm:g.darkAlgorithm,token:{colorPrimary:"#2f80ed",colorBgBase:"#0d1117",colorBgContainer:"#161b22",colorBgElevated:"#1c2230",borderRadius:8,fontFamily:'system-ui, -apple-system, "Segoe UI", sans-serif'},components:{Layout:{siderBg:"#0f1923",headerBg:"#0f1923",bodyBg:"#0d1117"},Menu:{darkItemBg:"#0f1923",darkSubMenuItemBg:"#0b1520"}}},vars:{"--bg-base":"#0d1117","--bg-sidebar":"#0f1923","--bg-header":"#0f1923","--bg-card":"#161b22","--bg-card-hover":"#1c2230","--border-color":"#21303f","--border-subtle":"#182030","--text-primary":"#c9d8ef","--text-secondary":"#6e8caa","--text-muted":"#2d4060","--logo-text":"#e0eeff","--menu-mode":"dark","--shadow-card":"0 2px 8px rgba(0,40,80,.5)","--group-title":"#2d4060"}},green:{label:"翠绿",icon:"🌿",antd:{algorithm:g.darkAlgorithm,token:{colorPrimary:"#29a270",colorBgBase:"#0a1a12",colorBgContainer:"#0f2318",colorBgElevated:"#152e20",borderRadius:8,fontFamily:'system-ui, -apple-system, "Segoe UI", sans-serif'},components:{Layout:{siderBg:"#0c1e14",headerBg:"#0c1e14",bodyBg:"#0a1a12"},Menu:{darkItemBg:"#0c1e14",darkSubMenuItemBg:"#091810"}}},vars:{"--bg-base":"#0a1a12","--bg-sidebar":"#0c1e14","--bg-header":"#0c1e14","--bg-card":"#0f2318","--bg-card-hover":"#152e20","--border-color":"#1a3828","--border-subtle":"#122a1c","--text-primary":"#c0e8c8","--text-secondary":"#5a9a6a","--text-muted":"#1e4028","--logo-text":"#d8f0e0","--menu-mode":"dark","--shadow-card":"0 2px 8px rgba(0,40,20,.5)","--group-title":"#1e4028"}}},j=I("theme",()=>{const s=T(localStorage.getItem(A)||"light"),a=E(()=>v[s.value]||v.dark),c=E(()=>a.value.antd),m=E(()=>s.value!=="light");function t(){const r=a.value.vars,u=document.documentElement;for(const[b,i]of Object.entries(r))u.style.setProperty(b,i);u.setAttribute("data-theme",s.value)}function o(r){v[r]&&(s.value=r,localStorage.setItem(A,r),t())}function n(){t()}return{current:s,config:a,antdTheme:c,isDark:m,setTheme:o,init:n}}),W={__name:"App",setup(s){const a=j();return R(()=>a.init()),(c,m)=>{const t=y("router-view"),o=y("a-config-provider");return V(),O(o,{theme:k(a).antdTheme},{default:x(()=>[D(t)]),_:1},8,["theme"])}}},h=S(W);h.use(w());h.use(F);h.use(M);h.mount("#app");export{v as T,j as u};
@@ -1,4 +1,4 @@
1
- import{g as Yc}from"./antd-gZyc63Qr.js";function Ni(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}let He=Ni();function Mc(a){He=a}const Lc=/[&<>"']/,qc=new RegExp(Lc.source,"g"),xc=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,Hc=new RegExp(xc.source,"g"),Vc={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},$i=a=>Vc[a];function Ne(a,e){if(e){if(Lc.test(a))return a.replace(qc,$i)}else if(xc.test(a))return a.replace(Hc,$i);return a}const zc=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;function Wc(a){return a.replace(zc,(e,t)=>(t=t.toLowerCase(),t==="colon"?":":t.charAt(0)==="#"?t.charAt(1)==="x"?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""))}const $c=/(^|[^\[])\^/g;function Z(a,e){let t=typeof a=="string"?a:a.source;e=e||"";const n={replace:(r,i)=>{let o=typeof i=="string"?i:i.source;return o=o.replace($c,"$1"),t=t.replace(r,o),n},getRegex:()=>new RegExp(t,e)};return n}function Ki(a){try{a=encodeURI(a).replace(/%25/g,"%")}catch{return null}return a}const pt={exec:()=>null};function Qi(a,e){const t=a.replace(/\|/g,(i,o,s)=>{let l=!1,_=o;for(;--_>=0&&s[_]==="\\";)l=!l;return l?"|":" |"}),n=t.split(/ \|/);let r=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;r<n.length;r++)n[r]=n[r].trim().replace(/\\\|/g,"|");return n}function yt(a,e,t){const n=a.length;if(n===0)return"";let r=0;for(;r<n&&a.charAt(n-r-1)===e;)r++;return a.slice(0,n-r)}function Kc(a,e){if(a.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<a.length;n++)if(a[n]==="\\")n++;else if(a[n]===e[0])t++;else if(a[n]===e[1]&&(t--,t<0))return n;return-1}function Xi(a,e,t,n){const r=e.href,i=e.title?Ne(e.title):null,o=a[1].replace(/\\([\[\]])/g,"$1");if(a[0].charAt(0)!=="!"){n.state.inLink=!0;const s={type:"link",raw:t,href:r,title:i,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,s}return{type:"image",raw:t,href:r,title:i,text:Ne(o)}}function Qc(a,e){const t=a.match(/^(\s+)(?:```)/);if(t===null)return e;const n=t[1];return e.split(`
1
+ import{g as Yc}from"./antd-BQNxIyr-.js";function Ni(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}let He=Ni();function Mc(a){He=a}const Lc=/[&<>"']/,qc=new RegExp(Lc.source,"g"),xc=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,Hc=new RegExp(xc.source,"g"),Vc={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},$i=a=>Vc[a];function Ne(a,e){if(e){if(Lc.test(a))return a.replace(qc,$i)}else if(xc.test(a))return a.replace(Hc,$i);return a}const zc=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;function Wc(a){return a.replace(zc,(e,t)=>(t=t.toLowerCase(),t==="colon"?":":t.charAt(0)==="#"?t.charAt(1)==="x"?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""))}const $c=/(^|[^\[])\^/g;function Z(a,e){let t=typeof a=="string"?a:a.source;e=e||"";const n={replace:(r,i)=>{let o=typeof i=="string"?i:i.source;return o=o.replace($c,"$1"),t=t.replace(r,o),n},getRegex:()=>new RegExp(t,e)};return n}function Ki(a){try{a=encodeURI(a).replace(/%25/g,"%")}catch{return null}return a}const pt={exec:()=>null};function Qi(a,e){const t=a.replace(/\|/g,(i,o,s)=>{let l=!1,_=o;for(;--_>=0&&s[_]==="\\";)l=!l;return l?"|":" |"}),n=t.split(/ \|/);let r=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;r<n.length;r++)n[r]=n[r].trim().replace(/\\\|/g,"|");return n}function yt(a,e,t){const n=a.length;if(n===0)return"";let r=0;for(;r<n&&a.charAt(n-r-1)===e;)r++;return a.slice(0,n-r)}function Kc(a,e){if(a.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<a.length;n++)if(a[n]==="\\")n++;else if(a[n]===e[0])t++;else if(a[n]===e[1]&&(t--,t<0))return n;return-1}function Xi(a,e,t,n){const r=e.href,i=e.title?Ne(e.title):null,o=a[1].replace(/\\([\[\]])/g,"$1");if(a[0].charAt(0)!=="!"){n.state.inLink=!0;const s={type:"link",raw:t,href:r,title:i,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,s}return{type:"image",raw:t,href:r,title:i,text:Ne(o)}}function Qc(a,e){const t=a.match(/^(\s+)(?:```)/);if(t===null)return e;const n=t[1];return e.split(`
2
2
  `).map(r=>{const i=r.match(/^\s+/);if(i===null)return r;const[o]=i;return o.length>=n.length?r.slice(n.length):r}).join(`
3
3
  `)}class Lt{options;rules;lexer;constructor(e){this.options=e||He}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const n=t[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:yt(n,`
4
4
  `)}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const n=t[0],r=Qc(n,t[3]||"");return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:r}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(/#$/.test(n)){const r=yt(n,"#");(this.options.pedantic||!r||/ $/.test(r))&&(n=r.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let n=t[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,`
@@ -8,9 +8,9 @@
8
8
  // Injected by web-ui-server.js at serve time
9
9
  window.__CC_CONFIG__ = __CC_CONFIG_PLACEHOLDER__;
10
10
  </script>
11
- <script type="module" crossorigin src="./assets/index-CyGtHm63.js"></script>
11
+ <script type="module" crossorigin src="./assets/index-CB5YlndO.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="./assets/vendor-CN0Iv_qZ.js">
13
- <link rel="modulepreload" crossorigin href="./assets/antd-gZyc63Qr.js">
13
+ <link rel="modulepreload" crossorigin href="./assets/antd-BQNxIyr-.js">
14
14
  <link rel="stylesheet" crossorigin href="./assets/index-CyGyEIVX.css">
15
15
  </head>
16
16
  <body>
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Autonomous Learning Loop commands
3
+ * chainlesschain learning stats|trajectories|reflect|synthesize|improve|cleanup
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import { logger } from "../lib/logger.js";
9
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
10
+
11
+ export function registerLearningCommand(program) {
12
+ const learning = program
13
+ .command("learning")
14
+ .description(
15
+ "Autonomous learning loop — trajectories, reflection, skill synthesis",
16
+ );
17
+
18
+ // learning stats
19
+ learning
20
+ .command("stats")
21
+ .description("Show learning loop statistics")
22
+ .option("--json", "Output as JSON")
23
+ .action(async (options) => {
24
+ try {
25
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
26
+ if (!ctx.db) {
27
+ logger.error("Database not available");
28
+ process.exit(1);
29
+ }
30
+ const db = ctx.db.getDatabase();
31
+ const { TrajectoryStore } =
32
+ await import("../lib/learning/trajectory-store.js");
33
+ const store = new TrajectoryStore(db);
34
+ const stats = store.getStats();
35
+
36
+ if (options.json) {
37
+ console.log(JSON.stringify(stats, null, 2));
38
+ } else {
39
+ logger.log(chalk.bold("Learning Loop Statistics"));
40
+ logger.log(` Total trajectories: ${chalk.cyan(stats.total)}`);
41
+ logger.log(` Complex (6+ tools): ${chalk.cyan(stats.complex)}`);
42
+ logger.log(` Scored: ${chalk.cyan(stats.scored)}`);
43
+ logger.log(
44
+ ` Skills synthesized: ${chalk.cyan(stats.synthesized)}`,
45
+ );
46
+ }
47
+
48
+ await shutdown();
49
+ } catch (err) {
50
+ logger.error(`Failed: ${err.message}`);
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ // learning trajectories
56
+ learning
57
+ .command("trajectories")
58
+ .description("List recent trajectories")
59
+ .option("-n, --limit <n>", "Number of trajectories", "20")
60
+ .option("--session <id>", "Filter by session ID")
61
+ .option("--json", "Output as JSON")
62
+ .action(async (options) => {
63
+ try {
64
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
65
+ if (!ctx.db) {
66
+ logger.error("Database not available");
67
+ process.exit(1);
68
+ }
69
+ const db = ctx.db.getDatabase();
70
+ const { TrajectoryStore } =
71
+ await import("../lib/learning/trajectory-store.js");
72
+ const store = new TrajectoryStore(db);
73
+
74
+ const trajs = store.getRecent({
75
+ limit: parseInt(options.limit, 10),
76
+ sessionId: options.session,
77
+ });
78
+
79
+ if (options.json) {
80
+ console.log(JSON.stringify(trajs, null, 2));
81
+ } else {
82
+ if (trajs.length === 0) {
83
+ logger.log(chalk.gray("No trajectories recorded yet."));
84
+ } else {
85
+ logger.log(chalk.bold(`Recent Trajectories (${trajs.length})`));
86
+ for (const t of trajs) {
87
+ const scoreStr =
88
+ t.outcomeScore != null
89
+ ? chalk.cyan(t.outcomeScore.toFixed(2))
90
+ : chalk.gray("unscored");
91
+ const synthStr = t.synthesizedSkill
92
+ ? chalk.green(` → ${t.synthesizedSkill}`)
93
+ : "";
94
+ logger.log(
95
+ ` ${chalk.dim(t.id.slice(0, 8))} | ${t.complexityLevel.padEnd(8)} | ` +
96
+ `${t.toolCount} tools | score: ${scoreStr}${synthStr}`,
97
+ );
98
+ if (t.userIntent) {
99
+ logger.log(` ${chalk.dim(t.userIntent.slice(0, 80))}`);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ await shutdown();
106
+ } catch (err) {
107
+ logger.error(`Failed: ${err.message}`);
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+ // learning reflect
113
+ learning
114
+ .command("reflect")
115
+ .description("Run a reflection cycle and generate report")
116
+ .option("--json", "Output as JSON")
117
+ .action(async (options) => {
118
+ try {
119
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
120
+ if (!ctx.db) {
121
+ logger.error("Database not available");
122
+ process.exit(1);
123
+ }
124
+ const db = ctx.db.getDatabase();
125
+ const { TrajectoryStore } =
126
+ await import("../lib/learning/trajectory-store.js");
127
+ const { ReflectionEngine } =
128
+ await import("../lib/learning/reflection-engine.js");
129
+ const store = new TrajectoryStore(db);
130
+ const engine = new ReflectionEngine(db, null, store);
131
+
132
+ const spinner = ora("Running reflection...").start();
133
+ const report = await engine.reflect();
134
+ spinner.succeed("Reflection complete");
135
+
136
+ if (options.json) {
137
+ console.log(JSON.stringify(report, null, 2));
138
+ } else {
139
+ logger.log(chalk.bold("Reflection Report"));
140
+ logger.log(` Period: ${chalk.dim(report.timestamp)}`);
141
+ logger.log(
142
+ ` Trajectories: ${chalk.cyan(report.totalTrajectories)}`,
143
+ );
144
+ logger.log(
145
+ ` Avg score: ${chalk.cyan(report.avgScore?.toFixed(2) || "N/A")}`,
146
+ );
147
+
148
+ const trendColor =
149
+ report.trend === "improving"
150
+ ? chalk.green
151
+ : report.trend === "declining"
152
+ ? chalk.red
153
+ : chalk.gray;
154
+ logger.log(` Trend: ${trendColor(report.trend)}`);
155
+
156
+ if (report.topTools && report.topTools.length > 0) {
157
+ logger.log(chalk.bold("\n Top Tools:"));
158
+ for (const t of report.topTools.slice(0, 5)) {
159
+ logger.log(
160
+ ` ${t.tool}: ${t.count}x (${(t.errorRate * 100).toFixed(0)}% error)`,
161
+ );
162
+ }
163
+ }
164
+
165
+ if (report.errorProneTools && report.errorProneTools.length > 0) {
166
+ logger.log(chalk.bold("\n Error-prone Tools:"));
167
+ for (const t of report.errorProneTools) {
168
+ logger.log(
169
+ ` ${chalk.red(t.tool)}: ${(t.errorRate * 100).toFixed(0)}% error rate`,
170
+ );
171
+ }
172
+ }
173
+
174
+ if (report.note) {
175
+ logger.log(chalk.gray(`\n Note: ${report.note}`));
176
+ }
177
+ }
178
+
179
+ await shutdown();
180
+ } catch (err) {
181
+ logger.error(`Failed: ${err.message}`);
182
+ process.exit(1);
183
+ }
184
+ });
185
+
186
+ // learning synthesize
187
+ learning
188
+ .command("synthesize")
189
+ .description("Synthesize new skills from eligible trajectories")
190
+ .option("--json", "Output as JSON")
191
+ .action(async (options) => {
192
+ try {
193
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
194
+ if (!ctx.db) {
195
+ logger.error("Database not available");
196
+ process.exit(1);
197
+ }
198
+ const db = ctx.db.getDatabase();
199
+ const { TrajectoryStore } =
200
+ await import("../lib/learning/trajectory-store.js");
201
+ const { SkillSynthesizer } =
202
+ await import("../lib/learning/skill-synthesizer.js");
203
+ const store = new TrajectoryStore(db);
204
+ const synthesizer = new SkillSynthesizer(db, null, store);
205
+
206
+ const spinner = ora("Scanning for synthesizable patterns...").start();
207
+ const result = await synthesizer.synthesize();
208
+ spinner.succeed("Synthesis complete");
209
+
210
+ if (options.json) {
211
+ console.log(JSON.stringify(result, null, 2));
212
+ } else {
213
+ if (result.created.length > 0) {
214
+ logger.log(
215
+ chalk.green(`Created ${result.created.length} skill(s):`),
216
+ );
217
+ for (const name of result.created) {
218
+ logger.log(` ${chalk.cyan(name)}`);
219
+ }
220
+ } else {
221
+ logger.log(chalk.gray("No new skills synthesized."));
222
+ }
223
+
224
+ if (result.skipped.length > 0) {
225
+ logger.log(
226
+ chalk.dim(`\nSkipped ${result.skipped.length} candidate(s)`),
227
+ );
228
+ }
229
+ }
230
+
231
+ await shutdown();
232
+ } catch (err) {
233
+ logger.error(`Failed: ${err.message}`);
234
+ process.exit(1);
235
+ }
236
+ });
237
+
238
+ // learning cleanup
239
+ learning
240
+ .command("cleanup")
241
+ .description("Delete old trajectories beyond retention period")
242
+ .option("--days <n>", "Retention days", "90")
243
+ .option("--json", "Output as JSON")
244
+ .action(async (options) => {
245
+ try {
246
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
247
+ if (!ctx.db) {
248
+ logger.error("Database not available");
249
+ process.exit(1);
250
+ }
251
+ const db = ctx.db.getDatabase();
252
+ const { TrajectoryStore } =
253
+ await import("../lib/learning/trajectory-store.js");
254
+ const store = new TrajectoryStore(db);
255
+
256
+ const days = parseInt(options.days, 10);
257
+ const spinner = ora(
258
+ `Cleaning up trajectories older than ${days} days...`,
259
+ ).start();
260
+ const deleted = store.cleanup(days);
261
+ spinner.succeed(`Cleanup complete: ${deleted} trajectories deleted`);
262
+
263
+ if (options.json) {
264
+ console.log(JSON.stringify({ deleted, retentionDays: days }));
265
+ }
266
+
267
+ await shutdown();
268
+ } catch (err) {
269
+ logger.error(`Failed: ${err.message}`);
270
+ process.exit(1);
271
+ }
272
+ });
273
+ }
@@ -19,6 +19,7 @@ import {
19
19
  rollbackApp,
20
20
  exportApp,
21
21
  listApps,
22
+ deployApp,
22
23
  } from "../lib/app-builder.js";
23
24
 
24
25
  export function registerLowcodeCommand(program) {
@@ -307,14 +308,28 @@ export function registerLowcodeCommand(program) {
307
308
  // lowcode deploy <app-id>
308
309
  lowcode
309
310
  .command("deploy")
310
- .description("Deploy application (placeholder)")
311
+ .description("Deploy application as static HTML bundle")
311
312
  .argument("<app-id>", "Application ID")
312
- .action(async (appId) => {
313
- logger.log(
314
- chalk.yellow(
315
- `Deploy for app ${chalk.cyan(appId)} is a placeholder. ` +
316
- "Full deployment support coming in a future release.",
317
- ),
318
- );
313
+ .option("-o, --output <dir>", "Output directory for the deploy bundle")
314
+ .action(async (appId, options) => {
315
+ const spinner = ora("Deploying application...").start();
316
+ let ctx;
317
+ try {
318
+ ctx = await bootstrap();
319
+ ensureLowcodeTables(ctx.db);
320
+ const result = deployApp(ctx.db, appId, {
321
+ outputDir: options.output || undefined,
322
+ });
323
+ spinner.succeed(
324
+ chalk.green(`App ${chalk.cyan(appId)} deployed successfully`),
325
+ );
326
+ logger.log(` Output: ${chalk.cyan(result.outputDir)}`);
327
+ logger.log(` Files: ${result.files.join(", ")}`);
328
+ logger.log(` Deployed: ${result.deployedAt}`);
329
+ } catch (err) {
330
+ spinner.fail(chalk.red(`Deploy failed: ${err.message}`));
331
+ } finally {
332
+ if (ctx) await shutdown(ctx);
333
+ }
319
334
  });
320
335
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Discord Formatter — converts agent responses to Discord-compatible markup.
3
+ *
4
+ * Discord supports a broad subset of Markdown. Main limit is 2000 chars per message.
5
+ *
6
+ * @module discord-formatter
7
+ */
8
+
9
+ const DISCORD_MAX_LENGTH = 2000;
10
+
11
+ /**
12
+ * Format an agent response for Discord.
13
+ * Discord natively supports Markdown, so minimal conversion needed.
14
+ * @param {string} response
15
+ * @param {object} [options]
16
+ * @param {number} [options.maxLength=2000]
17
+ * @returns {string}
18
+ */
19
+ export function formatForDiscord(response, options = {}) {
20
+ if (!response) return "";
21
+ const maxLength = options.maxLength || DISCORD_MAX_LENGTH;
22
+
23
+ let text = response;
24
+
25
+ if (text.length > maxLength) {
26
+ text = text.substring(0, maxLength - 3) + "...";
27
+ }
28
+
29
+ return text;
30
+ }
31
+
32
+ /**
33
+ * Split a response into Discord-sized chunks.
34
+ * Tries to split at code block or newline boundaries.
35
+ * @param {string} text
36
+ * @param {number} [maxLength=2000]
37
+ * @returns {string[]}
38
+ */
39
+ export function splitForDiscord(text, maxLength = DISCORD_MAX_LENGTH) {
40
+ if (!text || text.length <= maxLength) return [text || ""];
41
+
42
+ const chunks = [];
43
+ let remaining = text;
44
+
45
+ while (remaining.length > 0) {
46
+ if (remaining.length <= maxLength) {
47
+ chunks.push(remaining);
48
+ break;
49
+ }
50
+
51
+ // Try to split at code block boundary
52
+ let splitIdx = remaining.lastIndexOf("\n```", maxLength);
53
+ if (splitIdx > maxLength * 0.3) {
54
+ splitIdx += 1; // Include the newline
55
+ } else {
56
+ // Try to split at newline
57
+ splitIdx = remaining.lastIndexOf("\n", maxLength);
58
+ if (splitIdx < maxLength * 0.3) {
59
+ splitIdx = maxLength;
60
+ }
61
+ }
62
+
63
+ chunks.push(remaining.substring(0, splitIdx));
64
+ remaining = remaining.substring(splitIdx).trimStart();
65
+ }
66
+
67
+ return chunks;
68
+ }
69
+
70
+ /**
71
+ * Format a code block for Discord with syntax highlighting.
72
+ * @param {string} code
73
+ * @param {string} [language=""]
74
+ * @returns {string}
75
+ */
76
+ export function codeBlock(code, language = "") {
77
+ return `\`\`\`${language}\n${code}\n\`\`\``;
78
+ }
79
+
80
+ /**
81
+ * Format an embed-like block for Discord (using quote blocks).
82
+ * @param {string} title
83
+ * @param {string} content
84
+ * @returns {string}
85
+ */
86
+ export function quoteBlock(title, content) {
87
+ const lines = content.split("\n").map((l) => `> ${l}`);
88
+ return `**${title}**\n${lines.join("\n")}`;
89
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * GatewayBase — shared foundation for messaging platform gateways.
3
+ *
4
+ * Each gateway (Telegram, Discord, etc.) extends this class.
5
+ * Provides: session-per-chat management, message mapping, rate limiting,
6
+ * and integration with the agent loop.
7
+ *
8
+ * @module gateway-base
9
+ */
10
+
11
+ import { EventEmitter } from "node:events";
12
+
13
+ // ─── Constants ──────────────────────────────────────────────────────
14
+
15
+ const DEFAULT_MAX_RESPONSE_LENGTH = 4000;
16
+ const DEFAULT_RATE_LIMIT_WINDOW = 60000; // 1 minute
17
+ const DEFAULT_RATE_LIMIT_MAX = 20; // messages per window
18
+
19
+ // ─── GatewayBase ────────────────────────────────────────────────────
20
+
21
+ export class GatewayBase extends EventEmitter {
22
+ /**
23
+ * @param {object} options
24
+ * @param {string} options.platform - Platform name (e.g. "telegram", "discord")
25
+ * @param {number} [options.maxResponseLength] - Max chars per response
26
+ * @param {number} [options.rateLimitWindow] - Rate limit window in ms
27
+ * @param {number} [options.rateLimitMax] - Max messages per window
28
+ */
29
+ constructor(options = {}) {
30
+ super();
31
+ this.platform = options.platform || "unknown";
32
+ this.maxResponseLength =
33
+ options.maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH;
34
+ this.rateLimitWindow = options.rateLimitWindow || DEFAULT_RATE_LIMIT_WINDOW;
35
+ this.rateLimitMax = options.rateLimitMax || DEFAULT_RATE_LIMIT_MAX;
36
+
37
+ /** @type {Map<string, { messages: object[], lastActivity: number }>} */
38
+ this.sessions = new Map();
39
+
40
+ /** @type {Map<string, number[]>} */
41
+ this._rateLimitBuckets = new Map();
42
+
43
+ this._running = false;
44
+ }
45
+
46
+ // ── Lifecycle ───────────────────────────────────────────────────
47
+
48
+ /** Start the gateway. Override in subclass. */
49
+ async start() {
50
+ this._running = true;
51
+ this.emit("started", { platform: this.platform });
52
+ }
53
+
54
+ /** Stop the gateway. Override in subclass. */
55
+ async stop() {
56
+ this._running = false;
57
+ this.sessions.clear();
58
+ this._rateLimitBuckets.clear();
59
+ this.emit("stopped", { platform: this.platform });
60
+ }
61
+
62
+ /** @returns {boolean} */
63
+ isRunning() {
64
+ return this._running;
65
+ }
66
+
67
+ // ── Session management ──────────────────────────────────────────
68
+
69
+ /**
70
+ * Get or create a session for a chat.
71
+ * @param {string} chatId - Platform-specific chat identifier
72
+ * @returns {{ messages: object[], lastActivity: number, isNew: boolean }}
73
+ */
74
+ getOrCreateSession(chatId) {
75
+ if (this.sessions.has(chatId)) {
76
+ const session = this.sessions.get(chatId);
77
+ session.lastActivity = Date.now();
78
+ return { ...session, isNew: false };
79
+ }
80
+
81
+ const session = {
82
+ messages: [],
83
+ lastActivity: Date.now(),
84
+ };
85
+ this.sessions.set(chatId, session);
86
+ return { ...session, isNew: true };
87
+ }
88
+
89
+ /**
90
+ * Add a message to a chat session.
91
+ * @param {string} chatId
92
+ * @param {string} role - "user" | "assistant"
93
+ * @param {string} content
94
+ */
95
+ addMessage(chatId, role, content) {
96
+ const session = this.sessions.get(chatId);
97
+ if (session) {
98
+ session.messages.push({ role, content });
99
+ session.lastActivity = Date.now();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Clear a chat session.
105
+ * @param {string} chatId
106
+ */
107
+ clearSession(chatId) {
108
+ this.sessions.delete(chatId);
109
+ }
110
+
111
+ /**
112
+ * Get active session count.
113
+ * @returns {number}
114
+ */
115
+ getSessionCount() {
116
+ return this.sessions.size;
117
+ }
118
+
119
+ // ── Rate limiting ───────────────────────────────────────────────
120
+
121
+ /**
122
+ * Check if a chat is rate-limited.
123
+ * @param {string} chatId
124
+ * @returns {boolean}
125
+ */
126
+ isRateLimited(chatId) {
127
+ const now = Date.now();
128
+ const bucket = this._rateLimitBuckets.get(chatId) || [];
129
+ // Clean old entries
130
+ const recent = bucket.filter((ts) => now - ts < this.rateLimitWindow);
131
+ this._rateLimitBuckets.set(chatId, recent);
132
+ return recent.length >= this.rateLimitMax;
133
+ }
134
+
135
+ /**
136
+ * Record a message for rate limiting.
137
+ * @param {string} chatId
138
+ */
139
+ recordMessage(chatId) {
140
+ const bucket = this._rateLimitBuckets.get(chatId) || [];
141
+ bucket.push(Date.now());
142
+ this._rateLimitBuckets.set(chatId, bucket);
143
+ }
144
+
145
+ // ── Message formatting ──────────────────────────────────────────
146
+
147
+ /**
148
+ * Split a long response into chunks.
149
+ * @param {string} text
150
+ * @param {number} [maxLength]
151
+ * @returns {string[]}
152
+ */
153
+ splitResponse(text, maxLength) {
154
+ const limit = maxLength || this.maxResponseLength;
155
+ if (!text || text.length <= limit) return [text || ""];
156
+
157
+ const chunks = [];
158
+ let remaining = text;
159
+ while (remaining.length > 0) {
160
+ if (remaining.length <= limit) {
161
+ chunks.push(remaining);
162
+ break;
163
+ }
164
+ // Try to split at last newline within limit
165
+ let splitIdx = remaining.lastIndexOf("\n", limit);
166
+ if (splitIdx < limit * 0.5) {
167
+ // No good newline split point — split at limit
168
+ splitIdx = limit;
169
+ }
170
+ chunks.push(remaining.substring(0, splitIdx));
171
+ remaining = remaining.substring(splitIdx).trimStart();
172
+ }
173
+ return chunks;
174
+ }
175
+
176
+ // ── Stats ───────────────────────────────────────────────────────
177
+
178
+ /**
179
+ * Get gateway statistics.
180
+ * @returns {{ platform: string, running: boolean, sessions: number }}
181
+ */
182
+ getStats() {
183
+ return {
184
+ platform: this.platform,
185
+ running: this._running,
186
+ sessions: this.sessions.size,
187
+ };
188
+ }
189
+ }