toolnet-memory 0.3.12 → 0.3.13

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.
@@ -1,48 +1,8 @@
1
- import*as h from"fs";import*as g from"path";function z(n){let e=g.resolve(n),t=g.parse(e).root;for(;e!==t;){let r=g.join(e,".toolnet");if(h.existsSync(r)&&h.statSync(r).isDirectory())return e;e=g.dirname(e)}return null}var G=[{id:"openai-compatible",label:"OpenAI-compatible",requiresApiKey:!0,requiresBaseUrl:!0,transport:"openai-compatible"},{id:"alibaba",label:"Alibaba / DashScope",requiresApiKey:!0,requiresBaseUrl:!0,transport:"openai-compatible"},{id:"openrouter",label:"OpenRouter",defaultBaseUrl:"https://openrouter.ai/api/v1",requiresApiKey:!0,transport:"openai-compatible"},{id:"groq",label:"Groq",defaultBaseUrl:"https://api.groq.com/openai/v1",requiresApiKey:!0,transport:"openai-compatible"},{id:"deepseek",label:"DeepSeek",defaultBaseUrl:"https://api.deepseek.com",defaultModel:"deepseek-v4-flash",requiresApiKey:!0,transport:"openai-compatible"},{id:"nvidia",label:"NVIDIA NIM",defaultBaseUrl:"https://integrate.api.nvidia.com/v1",defaultModel:"deepseek-ai/deepseek-v4-pro",requiresApiKey:!0,transport:"openai-compatible"},{id:"gemini",label:"Gemini",defaultBaseUrl:"https://generativelanguage.googleapis.com/v1beta",requiresApiKey:!0,transport:"gemini"},{id:"huggingface",label:"Hugging Face",defaultBaseUrl:"https://router.huggingface.co/v1",requiresApiKey:!0,transport:"openai-compatible"},{id:"ollama",label:"Ollama / Local",defaultBaseUrl:"http://127.0.0.1:11434/v1",requiresApiKey:!1,transport:"openai-compatible"},{id:"custom",label:"Custom endpoint",requiresApiKey:!1,requiresBaseUrl:!0,transport:"openai-compatible"},{id:"cloudflare",label:"Cloudflare Workers AI",requiresApiKey:!0,requiresAccountId:!0,transport:"cloudflare"}];function y(n){let e=G.find(t=>t.id===n);if(!e)throw new Error(`Unsupported AI provider: ${n}`);return e}function b(n){return G.some(e=>e.id===n)}function s(n){return process.env[n]?.trim()||void 0}function l(...n){return n.find(e=>!!e?.trim())}function H(){if(s("GROQ_API_KEY"))return"groq";if(s("DEEPSEEK_API_KEY"))return"deepseek";if(s("NVIDIA_API_KEY")||s("NVIDIA_NIM_API_KEY"))return"nvidia";if(s("OPENROUTER_API_KEY"))return"openrouter";if(s("ALIBABA_API_KEY")||s("DASHSCOPE_API_KEY"))return"alibaba";if(s("GEMINI_API_KEY")||s("GOOGLE_API_KEY"))return"gemini";if(s("CLOUDFLARE_API_TOKEN")&&s("CLOUDFLARE_ACCOUNT_ID"))return"cloudflare";if(s("HF_TOKEN"))return"huggingface";if(s("OLLAMA_MODEL")||s("OLLAMA_BASE_URL"))return"ollama"}function q(){let n=s("TOOLNET_LLM_PROVIDER");return n&&b(n)?n:H()??"openai-compatible"}function x(n){let e=y(n);switch(n){case"alibaba":return{provider:n,apiKey:l(s("ALIBABA_API_KEY"),s("DASHSCOPE_API_KEY")),baseUrl:l(s("ALIBABA_BASE_URL"),s("DASHSCOPE_BASE_URL"),e.defaultBaseUrl),model:l(s("ALIBABA_MODEL"),s("DASHSCOPE_MODEL"))};case"openrouter":return{provider:n,apiKey:s("OPENROUTER_API_KEY"),baseUrl:l(s("OPENROUTER_BASE_URL"),e.defaultBaseUrl),model:s("OPENROUTER_MODEL")};case"groq":return{provider:n,apiKey:s("GROQ_API_KEY"),baseUrl:l(s("GROQ_BASE_URL"),e.defaultBaseUrl),model:s("GROQ_MODEL")};case"deepseek":return{provider:n,apiKey:s("DEEPSEEK_API_KEY"),baseUrl:l(s("DEEPSEEK_BASE_URL"),e.defaultBaseUrl),model:l(s("DEEPSEEK_MODEL"),e.defaultModel)};case"nvidia":return{provider:n,apiKey:l(s("NVIDIA_API_KEY"),s("NVIDIA_NIM_API_KEY")),baseUrl:l(s("NVIDIA_BASE_URL"),s("NVIDIA_NIM_BASE_URL"),e.defaultBaseUrl),model:l(s("NVIDIA_MODEL"),s("NVIDIA_NIM_MODEL"),e.defaultModel)};case"gemini":return{provider:n,apiKey:l(s("GEMINI_API_KEY"),s("GOOGLE_API_KEY")),baseUrl:l(s("GEMINI_BASE_URL"),e.defaultBaseUrl),model:s("GEMINI_MODEL")};case"huggingface":return{provider:n,apiKey:s("HF_TOKEN"),baseUrl:l(s("HF_INFERENCE_BASE_URL"),e.defaultBaseUrl),model:l(s("HF_LLM_MODEL"),s("HF_MODEL"))};case"ollama":return{provider:n,apiKey:s("OLLAMA_API_KEY"),baseUrl:l(s("OLLAMA_BASE_URL"),e.defaultBaseUrl),model:s("OLLAMA_MODEL")};case"cloudflare":return{provider:n,accountId:s("CLOUDFLARE_ACCOUNT_ID"),apiKey:s("CLOUDFLARE_API_TOKEN"),baseUrl:s("CLOUDFLARE_AI_BASE_URL"),model:s("CLOUDFLARE_MODEL")};case"custom":return{provider:n,apiKey:s("CUSTOM_AI_API_KEY"),baseUrl:s("CUSTOM_AI_BASE_URL"),model:s("CUSTOM_AI_MODEL")};default:return{provider:"openai-compatible",apiKey:l(s("OPENAI_API_KEY"),s("MODEL_API_KEY")),baseUrl:l(s("OPENAI_BASE_URL"),s("MODEL_BASE_URL")),model:l(s("OPENAI_MODEL"),s("MODEL_NAME"))}}}function ue(){let n=s("TOOLNET_LLM_PROVIDER"),e=n&&b(n)?n:q(),t=y(e),r=x(e);return{provider:e,apiKey:l(s("TOOLNET_LLM_API_KEY"),r.apiKey),baseUrl:l(s("TOOLNET_LLM_BASE_URL"),r.baseUrl,t.defaultBaseUrl),model:l(s("TOOLNET_LLM_MODEL"),r.model,t.defaultModel),accountId:l(s("TOOLNET_LLM_ACCOUNT_ID"),r.accountId)}}function de(){let n=s("TOOLNET_EMBEDDING_PROVIDER");return n==="local"?"local":n&&b(n)?n:s("HF_TOKEN")||s("HF_EMBEDDING_MODEL")?"huggingface":"local"}function me(){let n=de();if(n==="local")return{provider:"local",model:l(s("TOOLNET_EMBEDDING_MODEL"),s("LOCAL_EMBEDDING_MODEL"))};let e=y(n),t,r,o,i;switch(n){case"huggingface":t=s("HF_TOKEN"),r=s("HF_INFERENCE_BASE_URL"),o=s("HF_EMBEDDING_MODEL");break;case"openai-compatible":t=s("OPENAI_API_KEY"),r=s("OPENAI_BASE_URL"),o=s("OPENAI_EMBEDDING_MODEL");break;case"cloudflare":t=s("CLOUDFLARE_API_TOKEN"),r=s("CLOUDFLARE_AI_BASE_URL"),o=s("CLOUDFLARE_EMBEDDING_MODEL"),i=s("CLOUDFLARE_ACCOUNT_ID");break;default:t=x(n).apiKey,r=x(n).baseUrl,o=s(`${n.toUpperCase().replace(/-/g,"_")}_EMBEDDING_MODEL`)}let c=l(s("TOOLNET_EMBEDDING_ACCOUNT_ID"),i),m=l(s("TOOLNET_EMBEDDING_BASE_URL"),r,e.defaultBaseUrl),u=n==="cloudflare"&&!m&&c?`https://api.cloudflare.com/client/v4/accounts/${c}/ai/v1`:m;return{provider:n,apiKey:l(s("TOOLNET_EMBEDDING_API_KEY"),t),baseUrl:u,model:l(s("TOOLNET_EMBEDDING_MODEL"),o),accountId:c}}function F(){let n=ue(),e=me();return{llm:n,embedding:e,legacy:{llm:!s("TOOLNET_LLM_PROVIDER")&&!!H(),embedding:!s("TOOLNET_EMBEDDING_PROVIDER")&&!!(s("HF_TOKEN")||s("HF_EMBEDDING_MODEL"))}}}function W(n=q()){let e=F().llm;if(n===e.provider)return{id:n,apiKey:e.apiKey,baseUrl:e.baseUrl,model:e.model,accountId:e.accountId};let t=x(n);return{id:n,apiKey:t.apiKey,baseUrl:t.baseUrl,model:t.model,accountId:t.accountId}}function v(n,e){return s(`TOOLNET_LLM_FALLBACK_${n}_${e}`)}function Y(){let n=[];for(let e of[1,2]){let t=v(e,"PROVIDER");if(!t||!b(t))continue;let r=y(t);n.push({slot:e,config:{provider:t,apiKey:v(e,"API_KEY"),baseUrl:l(v(e,"BASE_URL"),r.defaultBaseUrl),model:l(v(e,"MODEL"),r.defaultModel),accountId:v(e,"ACCOUNT_ID")}})}return n}function V(){let n=s("TOOLNET_LLM_FALLBACK_COOLDOWN_MS"),e=s("TOOLNET_LLM_MAX_RETRIES"),t=Number(n??6e4),r=Number(e??1);return{cooldownMs:Number.isFinite(t)&&t>=0?t:6e4,maxRetries:Number.isFinite(r)&&r>=0?Math.floor(r):1}}var A=class extends Error{status;constructor(e,t){super(e),this.name="AiHttpError",this.status=t}};async function k(n,e,t=3e4){let r=new AbortController,o=setTimeout(()=>r.abort(),t);o.unref?.();try{let i=await fetch(n,{...e,signal:r.signal}),c=await i.text();if(!i.ok){let m=c;try{let u=JSON.parse(c);m=u.error?.message??u.message??c}catch{}throw new A(m||`HTTP ${i.status}`,i.status)}return c.trim()?JSON.parse(c):{}}finally{clearTimeout(o)}}function w(n,e){return`${n.replace(/\/+$/,"")}/${e.replace(/^\/+/,"")}`}var E=class{constructor(e){this.config=e}config;id="cloudflare";model(){let e=this.config.model?.trim();if(!e)throw new Error("cloudflare: MODEL is not configured");return e}async generate(e){let t=this.config.accountId?.trim(),r=this.config.apiKey?.trim();if(!t)throw new Error("cloudflare: ACCOUNT ID is not configured");if(!r)throw new Error("cloudflare: API TOKEN is not configured");let o=this.model(),c=`${(this.config.baseUrl?.trim()||`https://api.cloudflare.com/client/v4/accounts/${t}/ai/run`).replace(/\/+$/,"")}/${o}`,m=await k(c,{method:"POST",headers:{authorization:`Bearer ${r}`,"content-type":"application/json",...this.config.headers},body:JSON.stringify({messages:e.messages,temperature:e.temperature,max_tokens:e.maxTokens})}),u=m.result?.response?.trim();if(!u)throw new Error(m.errors?.[0]?.message??"cloudflare: empty model response");return{text:u,provider:"cloudflare",model:o}}async healthCheck(){let e=Date.now();try{return{ok:!0,provider:"cloudflare",model:(await this.generate({messages:[{role:"user",content:"Reply exactly: OK"}],temperature:0,maxTokens:8})).model,message:"Provider reachable",latencyMs:Date.now()-e}}catch(t){return{ok:!1,provider:"cloudflare",model:this.config.model,message:t instanceof Error?t.message:String(t),latencyMs:Date.now()-e}}}};var I=class{constructor(e){this.config=e}config;id="gemini";model(){let e=this.config.model?.trim();if(!e)throw new Error("gemini: MODEL is not configured");return e.replace(/^models\//,"")}async generate(e){let t=this.config.apiKey?.trim();if(!t)throw new Error("gemini: API KEY is not configured");let r=this.config.baseUrl?.trim()||"https://generativelanguage.googleapis.com/v1beta",o=this.model(),i=e.messages.filter(d=>d.role==="system"),c=e.messages.filter(d=>d.role!=="system").map(d=>({role:d.role==="assistant"?"model":"user",parts:[{text:d.content}]})),m=`${w(r,`models/${encodeURIComponent(o)}:generateContent`)}?key=${encodeURIComponent(t)}`,u=await k(m,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({...i.length?{systemInstruction:{parts:[{text:i.map(d=>d.content).join(`
2
-
3
- `)}]}}:{},contents:c,generationConfig:{temperature:e.temperature,maxOutputTokens:e.maxTokens}})}),f=u.candidates?.[0]?.content?.parts?.map(d=>d.text??"").join("").trim();if(!f)throw new Error("gemini: empty model response");return{text:f,provider:"gemini",model:o,usage:u.usageMetadata?{inputTokens:u.usageMetadata.promptTokenCount,outputTokens:u.usageMetadata.candidatesTokenCount,totalTokens:u.usageMetadata.totalTokenCount}:void 0}}async healthCheck(){let e=Date.now();try{return{ok:!0,provider:"gemini",model:(await this.generate({messages:[{role:"user",content:"Reply exactly: OK"}],temperature:0,maxTokens:8})).model,message:"Provider reachable",latencyMs:Date.now()-e}}catch(t){return{ok:!1,provider:"gemini",model:this.config.model,message:t instanceof Error?t.message:String(t),latencyMs:Date.now()-e}}}};var _=class{constructor(e){this.config=e;this.id=e.id}config;id;baseUrl(){let e=this.config.baseUrl?.trim();if(!e)throw new Error(`${this.id}: BASE URL is not configured`);return e}model(){let e=this.config.model?.trim();if(!e)throw new Error(`${this.id}: MODEL is not configured`);return e}headers(){let e={"content-type":"application/json",...this.config.headers};return this.config.apiKey&&(e.authorization=`Bearer ${this.config.apiKey}`),e}async generate(e){let t=this.model(),r=await k(w(this.baseUrl(),"chat/completions"),{method:"POST",headers:this.headers(),body:JSON.stringify({model:t,messages:e.messages,temperature:e.temperature,max_tokens:e.maxTokens,...this.id==="alibaba"?{enable_thinking:!1}:{}})}),o=r.choices?.[0]?.message?.content?.trim();if(!o)throw new Error(`${this.id}: empty model response`);return{text:o,provider:this.id,model:t,usage:r.usage?{inputTokens:r.usage.prompt_tokens,outputTokens:r.usage.completion_tokens,totalTokens:r.usage.total_tokens}:void 0}}async healthCheck(){let e=Date.now();try{let t=await this.generate({messages:[{role:"user",content:"Reply exactly: OK"}],temperature:0,maxTokens:8});return{ok:!0,provider:this.id,model:t.model,message:"Provider reachable",latencyMs:Date.now()-e}}catch(t){return{ok:!1,provider:this.id,model:this.config.model,message:t instanceof Error?t.message:String(t),latencyMs:Date.now()-e}}}};function J(n=W()){switch(y(n.id).transport){case"gemini":return new I(n);case"cloudflare":return new E(n);default:return new _(n)}}function pe(n){return new Promise(e=>{setTimeout(e,n)})}function fe(n){if(n instanceof A){let t=n.status;return t===void 0||t===408||t===409||t===425||t===429?!0:t>=500}if(!(n instanceof Error))return!0;let e=n.message.toLowerCase();return e.includes("401")||e.includes("403")||e.includes("400")||e.includes("invalid api key")||e.includes("unauthorized")||e.includes("forbidden")?!1:e.includes("timeout")||e.includes("timed out")||e.includes("abort")||e.includes("network")||e.includes("fetch failed")||e.includes("econnreset")||e.includes("econnrefused")||e.includes("429")||e.includes("rate limit")||e.includes("500")||e.includes("502")||e.includes("503")||e.includes("504")}var C=class{id;config;providers;states=new Map;cooldownMs;maxRetries;constructor(e,t=[],r={}){this.id=e.config.id,this.config=e.config,this.cooldownMs=r.cooldownMs??6e4,this.maxRetries=Math.max(0,r.maxRetries??1),this.providers=[e,...t].map(o=>({provider:J(o.config),label:o.label}))}key(e){let t=this.providers[e];return`${e}:${t.provider.id}:${t.provider.config.model??""}`}state(e){let t=this.key(e),r=this.states.get(t);return r||(r={failures:0,cooldownUntil:0},this.states.set(t,r)),r}available(e){return this.state(e).cooldownUntil<=Date.now()}success(e){let t=this.state(e);t.failures=0,t.cooldownUntil=0,t.lastError=void 0}fail(e,t){let r=this.state(e);r.failures+=1,r.lastError=t instanceof Error?t.message:String(t),r.cooldownUntil=Date.now()+this.cooldownMs}async generate(e){let t=[];for(let r=0;r<this.providers.length;r+=1){if(!this.available(r)){t.push(`${this.providers[r].label}: cooldown`);continue}let o=this.providers[r];for(let i=0;i<=this.maxRetries;i+=1)try{let c=await o.provider.generate(e);return this.success(r),c}catch(c){let m=c instanceof Error?c.message:String(c),u=fe(c);if(t.push(`${o.label}: ${m}`),!u)throw c;if(i<this.maxRetries){await pe(Math.min(250*2**i,2e3));continue}this.fail(r,c)}}throw new Error(`All ToolNet LLM providers failed: ${t.join(" | ")}`)}async healthCheck(){let e=Date.now();try{let t=await this.generate({messages:[{role:"user",content:"Reply exactly: OK"}],temperature:0,maxTokens:8});return{ok:!0,provider:t.provider,model:t.model,message:"Fallback chain reachable",latencyMs:Date.now()-e}}catch(t){return{ok:!1,provider:this.id,model:this.config.model,message:t instanceof Error?t.message:String(t),latencyMs:Date.now()-e}}}status(){return this.providers.map((e,t)=>{let r=this.state(t);return{position:t,provider:e.provider.id,model:e.provider.config.model,cooldown:r.cooldownUntil>Date.now(),cooldownUntil:r.cooldownUntil||void 0,failures:r.failures,lastError:r.lastError}})}};function Q(){let n=F(),e=Y();return new C({label:`Primary (${n.llm.provider})`,config:{id:n.llm.provider,apiKey:n.llm.apiKey,baseUrl:n.llm.baseUrl,model:n.llm.model,accountId:n.llm.accountId}},e.map(t=>({label:`Fallback ${t.slot} (${t.config.provider})`,config:{id:t.config.provider,apiKey:t.config.apiKey,baseUrl:t.config.baseUrl,model:t.config.model,accountId:t.config.accountId}})),V())}var L=class{constructor(e=Q()){this.provider=e}provider;get activeProvider(){return this.provider}setProvider(e){this.provider=e}generate(e){return this.provider.generate(e)}};function X(){return new L(Q())}import{existsSync as Le,readFileSync as De}from"node:fs";import{join as je}from"node:path";import{existsSync as ge,mkdirSync as tn,readFileSync as ye,renameSync as nn,writeFileSync as rn}from"node:fs";import{dirname as sn,join as he}from"node:path";function ke(n){return he(n.rootPath,".toolnet","work","current.json")}function D(n){let e=ke(n);if(!ge(e))return null;try{let t=JSON.parse(ye(e,"utf8"));return t.version!==1||t.projectId!==n.id?null:t}catch{return null}}import{existsSync as be,mkdirSync as ln,readFileSync as ve,renameSync as un,writeFileSync as dn}from"node:fs";import{dirname as pn,join as Ae}from"node:path";function Me(n){return Ae(n.rootPath,".toolnet","context","session-origin.json")}function j(n){let e=Me(n);if(!be(e))return null;try{let t=JSON.parse(ve(e,"utf8"));return t.version!==1||t.projectId!==n.id?null:t}catch{return null}}function Se(n){return n.normalize("NFKC").toLowerCase().replace(/\s+/g," ").trim()}function Z(n){let e=Se(n),t=/(?:agent trước|previous agent|ai trước|phiên trước)/u.test(e),r=/(?:đang làm gì|làm tới đâu|dừng ở đâu|dở ở đâu|stopped where|working on|current task|file nào|last touched|làm gì tiếp|next action|next step)/u.test(e);return t&&r?"summary":/(?:agent trước là ai|previous agent|ai trước là ai|phiên trước là ai)/u.test(e)?"previous_agent":/(?:task hiện tại|current task|đang làm gì|đang làm task nào|dở ở đâu|dừng ở đâu|stopped where)/u.test(e)?"current_task":/(?:tiếp theo|next action|next step|làm gì tiếp|phải làm gì tiếp)/u.test(e)?"next_action":/(?:file cuối|last file|file nào|đang sửa file|last touched)/u.test(e)?"last_file":/(?:blocker|vướng|kẹt|lỗi gì|blocked)/u.test(e)?"blocker":/(?:decision|quyết định|đã chốt|important decision)/u.test(e)?"decision":/(?:đã xong|completed|hoàn thành|done|todo nào xong)/u.test(e)?"completed":/(?:status|trạng thái|tiến độ|progress)/u.test(e)?"status":"summary"}import{existsSync as Re,mkdirSync as nr,readFileSync as Oe}from"node:fs";import{join as ee}from"node:path";var xe=[{type:"openai_key",regex:/\bsk-[A-Za-z0-9_\-]{20,}\b/g},{type:"huggingface_token",regex:/\bhf_[A-Za-z0-9]{20,}\b/g},{type:"hf_s3_access_key",regex:/\bHFAK[A-Za-z0-9]{8,}\b/g},{type:"bearer_token",regex:/\bBearer\s+[A-Za-z0-9._\-]{16,}\b/gi},{type:"jwt",regex:/\beyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\b/g},{type:"private_key",regex:/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g},{type:"password_assignment",regex:/\b(password|passwd|pwd)\s*[:=]\s*["']?[^"'\s]{6,}["']?/gi},{type:"secret_assignment",regex:/\b(secret|api[_-]?key|access[_-]?token|refresh[_-]?token)\s*[:=]\s*["']?[^"'\s]{8,}["']?/gi},{type:"cookie",regex:/\b(cookie|set-cookie)\s*[:=]\s*[^;\n]{8,}/gi}],T=class{scan(e){let t=[];for(let r of xe){let o=new RegExp(r.regex.source,r.regex.flags);for(let i of e.matchAll(o))t.push({type:r.type,value:i[0]})}return t}hasSecrets(e){return this.scan(e).length>0}};var M=class{scanner=new T;sanitize(e){let t=e,r=this.scanner.scan(e),o=new Set;for(let i of r)o.add(i.type),t=t.split(i.value).join(`[REDACTED:${i.type}]`);return{text:t,redacted:r.length,secretTypes:[...o]}}sanitizeValue(e){if(typeof e=="string")return this.sanitize(e).text;if(Array.isArray(e))return e.map(t=>this.sanitizeValue(t));if(e&&typeof e=="object"){let t={};for(let[r,o]of Object.entries(e)){let i=r.normalize("NFKC").toLowerCase().replace(/[^a-z0-9]+/g,"");i.includes("password")||i.includes("passwd")||i==="pwd"||i.includes("secret")||i.includes("token")||i.includes("cookie")||i.includes("authorization")||i.includes("apikey")||i.includes("accesskey")||i.includes("privatekey")||i.includes("clientsecret")||i.includes("credential")?t[r]="[REDACTED]":t[r]=this.sanitizeValue(o)}return t}return e}};import{existsSync as we,readFileSync as Ee}from"node:fs";import{homedir as Ie}from"node:os";import{join as _e}from"node:path";function Ce(n){let e=n.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
4
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Te(){let n=process.env.TOOLNET_GLOBAL_ENV??_e(Ie(),".config","toolnet-memory",".env");if(!we(n))return;let e=Ee(n,"utf8");for(let t of e.split(/\r?\n/)){let r=t.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let o=r.indexOf("=");if(o<=0)continue;let i=r.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&process.env[i]===void 0&&(process.env[i]=Ce(r.slice(o+1)))}}Te();var Gn=new M;function Pe(n){return ee(n.rootPath,".toolnet","memory","checkpoints")}function Ne(n){return ee(Pe(n),"latest.json")}function Fe(n){let e=Ne(n);if(!Re(e))return null;try{let t=JSON.parse(Oe(e,"utf8"));return t.schema!=="toolnet.memory.checkpoint.v1"||t.project.id!==n.id?null:t}catch{return null}}function te(n){return Fe(n)}var Ke={previous_agent:{previous_agent:100,task:35,phase:25},current_task:{task:100,request:95,activity:90,phase:75,file:65,next_action:55,blocker:45},next_action:{next_action:100,todo:80,task:65,blocker:60,file:40},last_file:{file:100,task:65,next_action:40},blocker:{blocker:100,task:60,next_action:50,file:35},decision:{decision:100,rule:85,architecture:80,task:45,file:30},completed:{completed:100,progress:70,task:30},status:{progress:100,task:85,phase:75,blocker:65,next_action:60},summary:{task:100,request:98,activity:96,next_action:95,file:90,blocker:85,previous_agent:80,phase:75,completed:60,todo:60,decision:55,rule:54,architecture:53,fix:52,progress:50,test:45,goal:35}},$e={checkpoint:20,handoff:18,"session-origin":14,"work-state":8};function Ue(n){return n.normalize("NFKC").toLowerCase().replace(/\s+/g," ").trim()}function a(n,e){let t=e.value.replace(/\s+/g," ").trim();t&&n.push({...e,value:t,score:0})}function Be(n){let e=je(n.rootPath,".toolnet","work","handoff-latest.json");if(!Le(e))return null;try{let r=JSON.parse(De(e,"utf8")).continuity;return r?.schema!=="toolnet.handoff.v2"||r.project.id!==n.id?null:r}catch{return null}}function ze(n){let e=te(n);if(!e)return[];let t=[];e.request&&a(t,{kind:"request",value:e.request,source:"checkpoint"}),e.activity&&a(t,{kind:"activity",value:e.activity,source:"checkpoint"}),e.current.phase&&a(t,{kind:"phase",value:e.current.phase.title,source:"checkpoint"}),e.current.task&&a(t,{kind:"task",value:e.current.task.title,source:"checkpoint"});let r=e.files.active.at(-1);r&&a(t,{kind:"file",value:r,source:"checkpoint"});for(let o of e.nextActions)a(t,{kind:"next_action",value:o,source:"checkpoint"});for(let o of e.blockers)a(t,{kind:"blocker",value:o,source:"checkpoint"});for(let o of e.completed.tasks)a(t,{kind:"completed",value:o,source:"checkpoint"});for(let o of e.remaining.tasks)a(t,{kind:"todo",value:o,source:"checkpoint"});for(let o of e.durableFacts)a(t,{kind:o.kind,value:o.content,source:"checkpoint"});return t}function Ge(n){let e=Be(n);if(!e)return[];let t=[];a(t,{kind:"previous_agent",value:e.source.agent,source:"handoff"}),e.request&&a(t,{kind:"request",value:e.request,source:"handoff"}),e.activity&&a(t,{kind:"activity",value:e.activity,source:"handoff"}),e.goal&&a(t,{kind:"goal",value:e.goal,source:"handoff"}),e.current.phase&&a(t,{kind:"phase",value:e.current.phase.title,source:"handoff"}),e.current.task&&a(t,{kind:"task",value:e.current.task.title,source:"handoff"}),e.files.current&&a(t,{kind:"file",value:e.files.current,source:"handoff"}),e.nextAction&&a(t,{kind:"next_action",value:e.nextAction,source:"handoff"});for(let r of e.blockers)a(t,{kind:"blocker",value:r,source:"handoff"});for(let r of e.decisions)a(t,{kind:"decision",value:r,source:"handoff"});for(let r of e.completed.tasks)a(t,{kind:"completed",value:r,source:"handoff"});for(let r of e.remaining.todos)a(t,{kind:"todo",value:r,source:"handoff"});return a(t,{kind:"test",value:`Test status: ${e.tests.status}`,source:"handoff"}),a(t,{kind:"progress",value:`${e.progress.tasksCompleted}/${e.progress.tasksTotal} tasks completed; ${e.progress.blocked} blocked`,source:"handoff"}),t}function He(n){let e=j(n);if(!e)return[];let t=[];return a(t,{kind:"previous_agent",value:e.agent,source:"session-origin"}),e.currentPhase&&a(t,{kind:"phase",value:e.currentPhase,source:"session-origin"}),e.currentTask&&a(t,{kind:"task",value:e.currentTask,source:"session-origin"}),e.lastTouchedFile&&a(t,{kind:"file",value:e.lastTouchedFile,source:"session-origin"}),e.latestNextAction&&a(t,{kind:"next_action",value:e.latestNextAction,source:"session-origin"}),e.latestBlocker&&a(t,{kind:"blocker",value:e.latestBlocker,source:"session-origin"}),e.latestDecision&&a(t,{kind:"decision",value:e.latestDecision,source:"session-origin"}),t}function qe(n){let e=D(n);if(!e)return[];let t=[];e.currentRequest&&a(t,{kind:"request",value:e.currentRequest,source:"work-state"}),e.currentActivity&&a(t,{kind:"activity",value:e.currentActivity,source:"work-state"}),e.goal&&a(t,{kind:"goal",value:e.goal,source:"work-state"}),e.currentPhase&&a(t,{kind:"phase",value:e.currentPhase.title,source:"work-state"}),e.currentTask&&a(t,{kind:"task",value:e.currentTask.title,source:"work-state"});let r=e.activeFiles?.at(-1)??e.filesTouched.at(-1);r&&a(t,{kind:"file",value:r,source:"work-state"});for(let o of e.nextActions.slice(0,5))a(t,{kind:"next_action",value:o,source:"work-state"});for(let o of e.blockers.slice(-5))a(t,{kind:"blocker",value:o,source:"work-state"});for(let o of e.decisions.slice(-5))a(t,{kind:"decision",value:o,source:"work-state"});for(let o of e.tasks.filter(i=>i.status==="completed").slice(-8))a(t,{kind:"completed",value:o.title,source:"work-state"});for(let o of e.tasks.filter(i=>i.status!=="completed"&&i.status!=="cancelled").slice(0,8))a(t,{kind:"todo",value:o.title,source:"work-state"});for(let o of e.tests.slice(-5))a(t,{kind:"test",value:o,source:"work-state"});return a(t,{kind:"progress",value:`${e.progress.tasksCompleted}/${e.progress.tasksTotal} tasks completed; ${e.progress.blocked} blocked`,source:"work-state"}),t}function We(n,e){let t=Ke[n];return e.map((r,o)=>({...r,score:(t[r.kind]??10)+$e[r.source]+Math.max(0,5-o*.01)}))}function Ye(n){let e=new Map;for(let t of n){let r=`${t.kind}:${Ue(t.value)}`,o=e.get(r);(!o||t.score>o.score)&&e.set(r,t)}return[...e.values()]}function R(n,e,t={}){let r=Z(e),o=[...ze(n),...Ge(n),...He(n),...qe(n)],i=Ye(We(r,o)).sort((d,p)=>p.score-d.score),c=Math.max(1,t.maxFacts??12),m=Math.max(300,t.maxChars??3200),u=[],f=0;for(let d of i){if(u.length>=c)break;let p=d.kind.length+d.value.length+d.source.length+16;u.length>0&&f+p>m||(u.push(d),f+=p)}return{intent:r,facts:u,context:{project:{id:n.id,name:n.name},intent:r,selectedFacts:u.map(d=>({kind:d.kind,value:d.value,source:d.source}))},stats:{candidates:o.length,selected:u.length,chars:f}}}var ne={previous_agent:"Agent tr\u01B0\u1EDBc",request:"Y\xEAu c\u1EA7u hi\u1EC7n t\u1EA1i",activity:"\u0110ang l\xE0m",goal:"M\u1EE5c ti\xEAu",phase:"Phase",task:"Task hi\u1EC7n t\u1EA1i",file:"File hi\u1EC7n t\u1EA1i",todo:"TODO",next_action:"Ti\u1EBFp theo",blocker:"Blocker",decision:"Quy\u1EBFt \u0111\u1ECBnh",rule:"Quy t\u1EAFc",architecture:"Ki\u1EBFn tr\xFAc",fix:"\u0110\xE3 s\u1EEDa",completed:"\u0110\xE3 ho\xE0n th\xE0nh",test:"Test",progress:"Ti\u1EBFn \u0111\u1ED9"},re=["previous_agent","request","activity","phase","task","file","todo","next_action","blocker","decision","rule","architecture","fix","completed","test","progress","goal"],Ve={previous_agent:["previous_agent"],current_task:["request","activity","task","file","next_action"],next_action:["next_action","task","file","todo"],last_file:["file","task","next_action"],blocker:["blocker"],decision:["decision"],completed:["completed"],status:["progress","task","next_action","blocker"],summary:re};function Je(n){let e=n.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim();return/(?:^|\s)(?:todo|to-do)(?:\s|$)/u.test(e)||/(?:việc|task)\s+(?:còn lại|chưa làm|chưa xong|chưa hoàn thành)/u.test(e)}function Qe(n){let e=n.filter(r=>r.source==="handoff");if(e.length===0)return n;let t=n.filter(r=>r.source==="checkpoint"&&["rule","architecture","fix"].includes(r.kind));return[...e,...t]}function Xe(n){let e=new Set,t=[];for(let r of n){let o=`${r.kind}:${r.value.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim()}`;e.has(o)||(e.add(o),t.push(r))}return t}function oe(n){switch(n){case"todo":case"blocker":case"decision":case"completed":return 3;default:return 1}}function Ze(n,e){let t=[];for(let r of e){let o=n.filter(i=>i.kind===r).slice(0,oe(r));for(let i of o)t.push(`${ne[r]}: ${i.value}`)}return t.join(`
5
- `)}function et(n,e){let t=Ve[e]??re,r=[];for(let o of t){let i=n.filter(c=>c.kind===o).slice(0,oe(o));for(let c of i)r.push(`${ne[o]}: ${c.value}`)}return r.join(`
6
- `)}function K(n,e){let t=R(n,e,{maxFacts:32,maxChars:6400}),r=Xe(Qe(t.facts)),o=Je(e)?Ze(r,["todo","task","next_action","file"]):et(r,String(t.intent));return o?{intent:String(t.intent),answer:o,source:r[0]?.source??"none"}:{intent:String(t.intent),answer:"ToolNet ch\u01B0a c\xF3 \u0111\u1EE7 memory cho c\xE2u h\u1ECFi n\xE0y.",source:"none"}}function tt(n,e){let t=JSON.stringify(n,null,2);return t.length<=e?t:t.slice(0,e)+`
7
- [truncated]`}function nt(n,e){let t=K(n,e),r=R(n,e,{maxFacts:12,maxChars:3200});return tt({project:r.context.project,intent:r.intent,directAnswer:t.answer,selectedMemory:r.context.selectedFacts,retrievalStats:r.stats},4200)}async function se(n,e,t={}){let r=K(n,e),o=nt(n,e),i=t.router??X();try{let c=await i.generate({temperature:.1,maxTokens:450,messages:[{role:"system",content:`
8
- You are ToolNet Memory Agent.
9
-
10
- You are NOT the coding agent.
11
-
12
- Your job is to help another coding AI continue work from previous sessions.
13
-
14
- Rules:
15
-
16
- 1. Answer only from the supplied ToolNet project memory.
17
- 2. Never invent completed work, files, blockers, decisions, or test results.
18
- 3. Prefer the latest session/work state over old information.
19
- 4. Be concise.
20
- 5. Do not dump raw JSON or full memory files.
21
- 6. Tell the coding agent exactly:
22
- - what the previous agent was doing,
23
- - what is already completed,
24
- - what remains unfinished,
25
- - what file was last touched,
26
- - what should be done next,
27
- when those facts are available.
28
- 7. If information is missing, say it is not recorded.
29
- 8. Do not explain ToolNet internals unless asked.
30
- 9. Do not repeat irrelevant history.
31
- 10. Respond in the same language as the question.
32
-
33
- Think of yourself as the project's memory coworker talking directly to another AI.
34
- `.trim()},{role:"user",content:`
35
- Question from coding agent:
36
-
37
- ${e}
38
-
39
- ToolNet selected memory:
40
-
41
- ${o}
42
-
43
- Answer the coding agent directly.
44
- `.trim()}]}),m=c.text.trim();return m?{answer:m,provider:c.provider,model:c.model,usedAi:!0}:{answer:r.answer,usedAi:!1}}catch{return{answer:r.answer,usedAi:!1}}}import{createHash as rt}from"node:crypto";import{existsSync as $,mkdirSync as ot,readFileSync as st,renameSync as it,writeFileSync as at}from"node:fs";import{basename as ct,dirname as O,join as N,parse as ce,resolve as S}from"node:path";var le=".toolnet",lt="project.json";function ut(n){return rt("sha256").update(n).digest("hex").slice(0,16)}function U(n){return N(n,le,lt)}function dt(n){return $(U(n))}function mt(n,e){let t=S(n),r=ce(t).root;for(;;){if(dt(t))return t;if(t===r||e&&t===S(e))break;let o=O(t);if(o===t)break;t=o}return null}function pt(n){let e=S(n),t=ce(e).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(i=>$(N(e,i))))return e;if(e===t)break;let o=O(e);if(o===e)break;e=o}return S(n)}function ft(n){let e;try{e=JSON.parse(st(n,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${n}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${n}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${n}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${n}`);let r=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:O(O(n)),createdAt:typeof t.createdAt=="string"?t.createdAt:r,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:r,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function ie(n,e){let t=N(n,le);ot(t,{recursive:!0});let r=U(n),o=`${r}.tmp-${process.pid}`;at(o,JSON.stringify(e,null,2)+`
45
- `,{encoding:"utf8",mode:384}),it(o,r)}function ae(n,e){return{id:n.id,name:n.name,remote:n.remote,rootPath:e,createdAt:n.createdAt,updatedAt:n.updatedAt,graphVersion:n.graphVersion,memoryVersion:n.memoryVersion,metadata:n.metadata}}var P=class{detect(e=process.cwd()){let t=S(e),r=pt(t),i=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(d=>$(N(r,d))),c=mt(t,i?r:void 0);if(c){let d=U(c),p=ft(d);return p.rootPath!==c&&(p.rootPath=c,p.updatedAt=new Date().toISOString(),ie(c,p)),ae(p,c)}let m=new Date().toISOString(),u=ct(r),f={version:1,id:ut(r),name:u,remote:u,rootPath:r,createdAt:m,updatedAt:m,graphVersion:0,memoryVersion:0};return ie(r,f),ae(f,r)}};async function gt(){let n=process.argv.slice(2).join(" ").trim();if(!n){console.error('Usage: toolnet-memory ask-ai "<question>"'),process.exitCode=1;return}let e=z(process.cwd());if(!e){console.error("Not in a ToolNet project."),process.exitCode=1;return}let t=new P().detect(e);if(!t){console.error("Unable to detect ToolNet project."),process.exitCode=1;return}let r=await se(t,n);process.stdout.write(`${r.answer}
46
- `),process.env.TOOLNET_MEMORY_AGENT_DEBUG==="1"&&process.stderr.write(["",`[ToolNet Memory Agent] AI: ${r.usedAi?"yes":"fallback"}`,r.provider?`Provider: ${r.provider}`:"",r.model?`Model: ${r.model}`:""].filter(Boolean).join(`
1
+ import*as f from"fs";import*as d from"path";function A(n){let e=d.resolve(n),t=d.parse(e).root;for(;e!==t;){let r=d.join(e,".toolnet");if(f.existsSync(r)&&f.statSync(r).isDirectory())return e;e=d.dirname(e)}return null}import{existsSync as ce,readFileSync as ae}from"node:fs";import{join as ue}from"node:path";import{existsSync as K,mkdirSync as nt,readFileSync as z,renameSync as rt,writeFileSync as ot}from"node:fs";import{dirname as it,join as W}from"node:path";function q(n){return W(n.rootPath,".toolnet","work","current.json")}function x(n){let e=q(n);if(!K(e))return null;try{let t=JSON.parse(z(e,"utf8"));return t.version!==1||t.projectId!==n.id?null:t}catch{return null}}import{existsSync as H,mkdirSync as ut,readFileSync as B,renameSync as lt,writeFileSync as dt}from"node:fs";import{dirname as ft,join as V}from"node:path";function G(n){return V(n.rootPath,".toolnet","context","session-origin.json")}function v(n){let e=G(n);if(!H(e))return null;try{let t=JSON.parse(B(e,"utf8"));return t.version!==1||t.projectId!==n.id?null:t}catch{return null}}function Y(n){return n.normalize("NFKC").toLowerCase().replace(/\s+/g," ").trim()}function T(n){let e=Y(n),t=/(?:agent trước|previous agent|ai trước|phiên trước)/u.test(e),r=/(?:đang làm gì|làm tới đâu|dừng ở đâu|dở ở đâu|stopped where|working on|current task|file nào|last touched|làm gì tiếp|next action|next step)/u.test(e);return t&&r?"summary":/(?:agent trước là ai|previous agent|ai trước là ai|phiên trước là ai)/u.test(e)?"previous_agent":/(?:task hiện tại|current task|đang làm gì|đang làm task nào|dở ở đâu|dừng ở đâu|stopped where)/u.test(e)?"current_task":/(?:tiếp theo|next action|next step|làm gì tiếp|phải làm gì tiếp)/u.test(e)?"next_action":/(?:file cuối|last file|file nào|đang sửa file|last touched)/u.test(e)?"last_file":/(?:blocker|vướng|kẹt|lỗi gì|blocked)/u.test(e)?"blocker":/(?:decision|quyết định|đã chốt|important decision)/u.test(e)?"decision":/(?:đã xong|completed|hoàn thành|done|todo nào xong)/u.test(e)?"completed":/(?:status|trạng thái|tiến độ|progress)/u.test(e)?"status":"summary"}import{existsSync as ne,mkdirSync as nn,readFileSync as re}from"node:fs";import{join as E}from"node:path";var J=[{type:"openai_key",regex:/\bsk-[A-Za-z0-9_\-]{20,}\b/g},{type:"huggingface_token",regex:/\bhf_[A-Za-z0-9]{20,}\b/g},{type:"hf_s3_access_key",regex:/\bHFAK[A-Za-z0-9]{8,}\b/g},{type:"bearer_token",regex:/\bBearer\s+[A-Za-z0-9._\-]{16,}\b/gi},{type:"jwt",regex:/\beyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\b/g},{type:"private_key",regex:/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g},{type:"password_assignment",regex:/\b(password|passwd|pwd)\s*[:=]\s*["']?[^"'\s]{6,}["']?/gi},{type:"secret_assignment",regex:/\b(secret|api[_-]?key|access[_-]?token|refresh[_-]?token)\s*[:=]\s*["']?[^"'\s]{8,}["']?/gi},{type:"cookie",regex:/\b(cookie|set-cookie)\s*[:=]\s*[^;\n]{8,}/gi}],h=class{scan(e){let t=[];for(let r of J){let o=new RegExp(r.regex.source,r.regex.flags);for(let i of e.matchAll(o))t.push({type:r.type,value:i[0]})}return t}hasSecrets(e){return this.scan(e).length>0}};var p=class{scanner=new h;sanitize(e){let t=e,r=this.scanner.scan(e),o=new Set;for(let i of r)o.add(i.type),t=t.split(i.value).join(`[REDACTED:${i.type}]`);return{text:t,redacted:r.length,secretTypes:[...o]}}sanitizeValue(e){if(typeof e=="string")return this.sanitize(e).text;if(Array.isArray(e))return e.map(t=>this.sanitizeValue(t));if(e&&typeof e=="object"){let t={};for(let[r,o]of Object.entries(e)){let i=r.normalize("NFKC").toLowerCase().replace(/[^a-z0-9]+/g,"");i.includes("password")||i.includes("passwd")||i==="pwd"||i.includes("secret")||i.includes("token")||i.includes("cookie")||i.includes("authorization")||i.includes("apikey")||i.includes("accesskey")||i.includes("privatekey")||i.includes("clientsecret")||i.includes("credential")?t[r]="[REDACTED]":t[r]=this.sanitizeValue(o)}return t}return e}};import{existsSync as U,readFileSync as Q}from"node:fs";import{homedir as X}from"node:os";import{join as Z}from"node:path";function ee(n){let e=n.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function te(){let n=process.env.TOOLNET_GLOBAL_ENV??Z(X(),".config","toolnet-memory",".env");if(!U(n))return;let e=Q(n,"utf8");for(let t of e.split(/\r?\n/)){let r=t.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let o=r.indexOf("=");if(o<=0)continue;let i=r.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&process.env[i]===void 0&&(process.env[i]=ee(r.slice(o+1)))}}te();var Ht=new p;function oe(n){return E(n.rootPath,".toolnet","memory","checkpoints")}function se(n){return E(oe(n),"latest.json")}function ie(n){let e=se(n);if(!ne(e))return null;try{let t=JSON.parse(re(e,"utf8"));return t.schema!=="toolnet.memory.checkpoint.v1"||t.project.id!==n.id?null:t}catch{return null}}function I(n){return ie(n)}var le={previous_agent:{previous_agent:100,task:35,phase:25},current_task:{task:100,request:95,activity:90,phase:75,file:65,next_action:55,blocker:45},next_action:{next_action:100,todo:80,task:65,blocker:60,file:40},last_file:{file:100,task:65,next_action:40},blocker:{blocker:100,task:60,next_action:50,file:35},decision:{decision:100,rule:85,architecture:80,task:45,file:30},completed:{completed:100,progress:70,task:30},status:{progress:100,task:85,phase:75,blocker:65,next_action:60},summary:{task:100,request:98,activity:96,next_action:95,file:90,blocker:85,previous_agent:80,phase:75,completed:60,todo:60,decision:55,rule:54,architecture:53,fix:52,progress:50,test:45,goal:35}},de={checkpoint:20,handoff:18,"session-origin":14,"work-state":8};function me(n){return n.normalize("NFKC").toLowerCase().replace(/\s+/g," ").trim()}function s(n,e){let t=e.value.replace(/\s+/g," ").trim();t&&n.push({...e,value:t,score:0})}function fe(n){let e=ue(n.rootPath,".toolnet","work","handoff-latest.json");if(!ce(e))return null;try{let r=JSON.parse(ae(e,"utf8")).continuity;return r?.schema!=="toolnet.handoff.v2"||r.project.id!==n.id?null:r}catch{return null}}function pe(n){let e=I(n);if(!e)return[];let t=[];e.request&&s(t,{kind:"request",value:e.request,source:"checkpoint"}),e.activity&&s(t,{kind:"activity",value:e.activity,source:"checkpoint"}),e.current.phase&&s(t,{kind:"phase",value:e.current.phase.title,source:"checkpoint"}),e.current.task&&s(t,{kind:"task",value:e.current.task.title,source:"checkpoint"});let r=e.files.active.at(-1);r&&s(t,{kind:"file",value:r,source:"checkpoint"});for(let o of e.nextActions)s(t,{kind:"next_action",value:o,source:"checkpoint"});for(let o of e.blockers)s(t,{kind:"blocker",value:o,source:"checkpoint"});for(let o of e.completed.tasks)s(t,{kind:"completed",value:o,source:"checkpoint"});for(let o of e.remaining.tasks)s(t,{kind:"todo",value:o,source:"checkpoint"});for(let o of e.durableFacts)s(t,{kind:o.kind,value:o.content,source:"checkpoint"});return t}function ge(n){let e=fe(n);if(!e)return[];let t=[];s(t,{kind:"previous_agent",value:e.source.agent,source:"handoff"}),e.request&&s(t,{kind:"request",value:e.request,source:"handoff"}),e.activity&&s(t,{kind:"activity",value:e.activity,source:"handoff"}),e.goal&&s(t,{kind:"goal",value:e.goal,source:"handoff"}),e.current.phase&&s(t,{kind:"phase",value:e.current.phase.title,source:"handoff"}),e.current.task&&s(t,{kind:"task",value:e.current.task.title,source:"handoff"}),e.files.current&&s(t,{kind:"file",value:e.files.current,source:"handoff"}),e.nextAction&&s(t,{kind:"next_action",value:e.nextAction,source:"handoff"});for(let r of e.blockers)s(t,{kind:"blocker",value:r,source:"handoff"});for(let r of e.decisions)s(t,{kind:"decision",value:r,source:"handoff"});for(let r of e.completed.tasks)s(t,{kind:"completed",value:r,source:"handoff"});for(let r of e.remaining.todos)s(t,{kind:"todo",value:r,source:"handoff"});return s(t,{kind:"test",value:`Test status: ${e.tests.status}`,source:"handoff"}),s(t,{kind:"progress",value:`${e.progress.tasksCompleted}/${e.progress.tasksTotal} tasks completed; ${e.progress.blocked} blocked`,source:"handoff"}),t}function ye(n){let e=v(n);if(!e)return[];let t=[];return s(t,{kind:"previous_agent",value:e.agent,source:"session-origin"}),e.currentPhase&&s(t,{kind:"phase",value:e.currentPhase,source:"session-origin"}),e.currentTask&&s(t,{kind:"task",value:e.currentTask,source:"session-origin"}),e.lastTouchedFile&&s(t,{kind:"file",value:e.lastTouchedFile,source:"session-origin"}),e.latestNextAction&&s(t,{kind:"next_action",value:e.latestNextAction,source:"session-origin"}),e.latestBlocker&&s(t,{kind:"blocker",value:e.latestBlocker,source:"session-origin"}),e.latestDecision&&s(t,{kind:"decision",value:e.latestDecision,source:"session-origin"}),t}function he(n){let e=x(n);if(!e)return[];let t=[];e.currentRequest&&s(t,{kind:"request",value:e.currentRequest,source:"work-state"}),e.currentActivity&&s(t,{kind:"activity",value:e.currentActivity,source:"work-state"}),e.goal&&s(t,{kind:"goal",value:e.goal,source:"work-state"}),e.currentPhase&&s(t,{kind:"phase",value:e.currentPhase.title,source:"work-state"}),e.currentTask&&s(t,{kind:"task",value:e.currentTask.title,source:"work-state"});let r=e.activeFiles?.at(-1)??e.filesTouched.at(-1);r&&s(t,{kind:"file",value:r,source:"work-state"});for(let o of e.nextActions.slice(0,5))s(t,{kind:"next_action",value:o,source:"work-state"});for(let o of e.blockers.slice(-5))s(t,{kind:"blocker",value:o,source:"work-state"});for(let o of e.decisions.slice(-5))s(t,{kind:"decision",value:o,source:"work-state"});for(let o of e.tasks.filter(i=>i.status==="completed").slice(-8))s(t,{kind:"completed",value:o.title,source:"work-state"});for(let o of e.tasks.filter(i=>i.status!=="completed"&&i.status!=="cancelled").slice(0,8))s(t,{kind:"todo",value:o.title,source:"work-state"});for(let o of e.tests.slice(-5))s(t,{kind:"test",value:o,source:"work-state"});return s(t,{kind:"progress",value:`${e.progress.tasksCompleted}/${e.progress.tasksTotal} tasks completed; ${e.progress.blocked} blocked`,source:"work-state"}),t}function ke(n,e){let t=le[n];return e.map((r,o)=>({...r,score:(t[r.kind]??10)+de[r.source]+Math.max(0,5-o*.01)}))}function be(n){let e=new Map;for(let t of n){let r=`${t.kind}:${me(t.value)}`,o=e.get(r);(!o||t.score>o.score)&&e.set(r,t)}return[...e.values()]}function R(n,e,t={}){let r=T(e),o=[...pe(n),...ge(n),...ye(n),...he(n)],i=be(ke(r,o)).sort((c,u)=>u.score-c.score),a=Math.max(1,t.maxFacts??12),y=Math.max(300,t.maxChars??3200),l=[],m=0;for(let c of i){if(l.length>=a)break;let u=c.kind.length+c.value.length+c.source.length+16;l.length>0&&m+u>y||(l.push(c),m+=u)}return{intent:r,facts:l,context:{project:{id:n.id,name:n.name},intent:r,selectedFacts:l.map(c=>({kind:c.kind,value:c.value,source:c.source}))},stats:{candidates:o.length,selected:l.length,chars:m}}}var F={previous_agent:"Agent tr\u01B0\u1EDBc",request:"Y\xEAu c\u1EA7u hi\u1EC7n t\u1EA1i",activity:"\u0110ang l\xE0m",goal:"M\u1EE5c ti\xEAu",phase:"Phase",task:"Task hi\u1EC7n t\u1EA1i",file:"File hi\u1EC7n t\u1EA1i",todo:"TODO",next_action:"Ti\u1EBFp theo",blocker:"Blocker",decision:"Quy\u1EBFt \u0111\u1ECBnh",rule:"Quy t\u1EAFc",architecture:"Ki\u1EBFn tr\xFAc",fix:"\u0110\xE3 s\u1EEDa",completed:"\u0110\xE3 ho\xE0n th\xE0nh",test:"Test",progress:"Ti\u1EBFn \u0111\u1ED9"},_=["previous_agent","request","activity","phase","task","file","todo","next_action","blocker","decision","rule","architecture","fix","completed","test","progress","goal"],Se={previous_agent:["previous_agent"],current_task:["request","activity","task","file","next_action"],next_action:["next_action","task","file","todo"],last_file:["file","task","next_action"],blocker:["blocker"],decision:["decision"],completed:["completed"],status:["progress","task","next_action","blocker"],summary:_};function xe(n){let e=n.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim();return/(?:^|\s)(?:todo|to-do)(?:\s|$)/u.test(e)||/(?:việc|task)\s+(?:còn lại|chưa làm|chưa xong|chưa hoàn thành)/u.test(e)}function ve(n){let e=n.filter(r=>r.source==="handoff");if(e.length===0)return n;let t=n.filter(r=>r.source==="checkpoint"&&["rule","architecture","fix"].includes(r.kind));return[...e,...t]}function Me(n){let e=new Set,t=[];for(let r of n){let o=`${r.kind}:${r.value.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim()}`;e.has(o)||(e.add(o),t.push(r))}return t}function O(n){switch(n){case"todo":case"blocker":case"decision":case"completed":return 3;default:return 1}}function we(n,e){let t=[];for(let r of e){let o=n.filter(i=>i.kind===r).slice(0,O(r));for(let i of o)t.push(`${F[r]}: ${i.value}`)}return t.join(`
3
+ `)}function Ce(n,e){let t=Se[e]??_,r=[];for(let o of t){let i=n.filter(a=>a.kind===o).slice(0,O(o));for(let a of i)r.push(`${F[o]}: ${a.value}`)}return r.join(`
4
+ `)}function P(n,e){let t=R(n,e,{maxFacts:32,maxChars:6400}),r=Me(ve(t.facts)),o=xe(e)?we(r,["todo","task","next_action","file"]):Ce(r,String(t.intent));return o?{intent:String(t.intent),answer:o,source:r[0]?.source??"none"}:{intent:String(t.intent),answer:"ToolNet ch\u01B0a c\xF3 \u0111\u1EE7 memory cho c\xE2u h\u1ECFi n\xE0y.",source:"none"}}async function N(n,e){let t=P(n,e);return t.answer?{answer:t.answer,usedAi:!1}:{answer:"ToolNet ch\u01B0a c\xF3 \u0111\u1EE7 memory cho c\xE2u h\u1ECFi n\xE0y.",usedAi:!1}}import{createHash as Ae}from"node:crypto";import{existsSync as M,mkdirSync as Te,readFileSync as Ee,renameSync as Ie,writeFileSync as Re}from"node:fs";import{basename as Fe,dirname as k,join as S,parse as L,resolve as g}from"node:path";var D=".toolnet",_e="project.json";function Oe(n){return Ae("sha256").update(n).digest("hex").slice(0,16)}function w(n){return S(n,D,_e)}function Pe(n){return M(w(n))}function Ne(n,e){let t=g(n),r=L(t).root;for(;;){if(Pe(t))return t;if(t===r||e&&t===g(e))break;let o=k(t);if(o===t)break;t=o}return null}function je(n){let e=g(n),t=L(e).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(i=>M(S(e,i))))return e;if(e===t)break;let o=k(e);if(o===e)break;e=o}return g(n)}function $e(n){let e;try{e=JSON.parse(Ee(n,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${n}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${n}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${n}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${n}`);let r=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:k(k(n)),createdAt:typeof t.createdAt=="string"?t.createdAt:r,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:r,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function j(n,e){let t=S(n,D);Te(t,{recursive:!0});let r=w(n),o=`${r}.tmp-${process.pid}`;Re(o,JSON.stringify(e,null,2)+`
5
+ `,{encoding:"utf8",mode:384}),Ie(o,r)}function $(n,e){return{id:n.id,name:n.name,remote:n.remote,rootPath:e,createdAt:n.createdAt,updatedAt:n.updatedAt,graphVersion:n.graphVersion,memoryVersion:n.memoryVersion,metadata:n.metadata}}var b=class{detect(e=process.cwd()){let t=g(e),r=je(t),i=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(c=>M(S(r,c))),a=Ne(t,i?r:void 0);if(a){let c=w(a),u=$e(c);return u.rootPath!==a&&(u.rootPath=a,u.updatedAt=new Date().toISOString(),j(a,u)),$(u,a)}let y=new Date().toISOString(),l=Fe(r),m={version:1,id:Oe(r),name:l,remote:l,rootPath:r,createdAt:y,updatedAt:y,graphVersion:0,memoryVersion:0};return j(r,m),$(m,r)}};async function Le(){let n=process.argv.slice(2).join(" ").trim();if(!n){console.error('Usage: toolnet-memory ask "<question>"'),process.exitCode=1;return}let e=A(process.cwd());if(!e){console.error("Not in a ToolNet project."),process.exitCode=1;return}let t=new b().detect(e);if(!t){console.error("Unable to detect ToolNet project."),process.exitCode=1;return}let r=await N(t,n);process.stdout.write(`${r.answer}
6
+ `),process.env.TOOLNET_MEMORY_AGENT_DEBUG==="1"&&process.stderr.write(["",`[ToolNet Memory Agent] AI: ${r.usedAi?"yes":"fallback"}`].filter(Boolean).join(`
47
7
  `)+`
48
- `)}gt().catch(n=>{console.error(n instanceof Error?n.message:String(n)),process.exitCode=1});
8
+ `)}Le().catch(n=>{console.error(n instanceof Error?n.message:String(n)),process.exitCode=1});
@@ -1,6 +1,6 @@
1
1
  import{readFileSync as Ue}from"node:fs";import{existsSync as Y,readFileSync as H}from"node:fs";import{homedir as G}from"node:os";import{join as W}from"node:path";function Q(o){let t=o.trim();return t.length>=2&&t.startsWith('"')&&t.endsWith('"')?(t=t.slice(1,-1),t.replace(/\\n/g,`
2
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):t.length>=2&&t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Z(){let o=process.env.TOOLNET_GLOBAL_ENV??W(G(),".config","toolnet-memory",".env");if(!Y(o))return;let t=H(o,"utf8");for(let e of t.split(/\r?\n/)){let r=e.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let n=r.indexOf("=");if(n<=0)continue;let s=r.slice(0,n).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)&&process.env[s]===void 0&&(process.env[s]=Q(r.slice(n+1)))}}Z();function l(o,t){return o===void 0?t:["1","true","yes","on"].includes(o.toLowerCase())}function m(o,t){if(!o)return t;let e=Number(o);return Number.isFinite(e)?e:t}function M(){return{memory:{autoCapture:l(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:l(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:l(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:l(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:m(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:m(process.env.MEMORY_RERANK_TOP,10),finalContext:m(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:m(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:l(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:m(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as X}from"node:crypto";import{existsSync as x,mkdirSync as q,readFileSync as ee,renameSync as te,writeFileSync as re}from"node:fs";import{basename as oe,dirname as h,join as w,parse as I,resolve as g}from"node:path";var E=".toolnet",ne="project.json";function se(o){return X("sha256").update(o).digest("hex").slice(0,16)}function b(o){return w(o,E,ne)}function ie(o){return x(b(o))}function ae(o,t){let e=g(o),r=I(e).root;for(;;){if(ie(e))return e;if(e===r||t&&e===g(t))break;let n=h(e);if(n===e)break;e=n}return null}function ce(o){let t=g(o),e=I(t).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(s=>x(w(t,s))))return t;if(t===e)break;let n=h(t);if(n===t)break;t=n}return g(o)}function ue(o){let t;try{t=JSON.parse(ee(o,"utf8"))}catch(n){throw new Error(`Invalid ToolNet project manifest: ${o}: ${n instanceof Error?n.message:String(n)}`)}if(!t||typeof t!="object")throw new Error(`Invalid ToolNet project manifest: ${o}`);let e=t;if(typeof e.id!="string"||!e.id.trim())throw new Error(`ToolNet project manifest is missing id: ${o}`);if(typeof e.name!="string"||!e.name.trim())throw new Error(`ToolNet project manifest is missing name: ${o}`);let r=new Date().toISOString();return{version:1,id:e.id,name:e.name,remote:typeof e.remote=="string"&&e.remote.trim()?e.remote:e.name,rootPath:typeof e.rootPath=="string"?e.rootPath:h(h(o)),createdAt:typeof e.createdAt=="string"?e.createdAt:r,updatedAt:typeof e.updatedAt=="string"?e.updatedAt:r,graphVersion:typeof e.graphVersion=="number"?e.graphVersion:0,memoryVersion:typeof e.memoryVersion=="number"?e.memoryVersion:0,metadata:e.metadata&&typeof e.metadata=="object"?e.metadata:void 0}}function C(o,t){let e=w(o,E);q(e,{recursive:!0});let r=b(o),n=`${r}.tmp-${process.pid}`;re(n,JSON.stringify(t,null,2)+`
3
- `,{encoding:"utf8",mode:384}),te(n,r)}function T(o,t){return{id:o.id,name:o.name,remote:o.remote,rootPath:t,createdAt:o.createdAt,updatedAt:o.updatedAt,graphVersion:o.graphVersion,memoryVersion:o.memoryVersion,metadata:o.metadata}}var S=class{detect(t=process.cwd()){let e=g(t),r=ce(e),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(p=>x(w(r,p))),i=ae(e,s?r:void 0);if(i){let p=b(i),d=ue(p);return d.rootPath!==i&&(d.rootPath=i,d.updatedAt=new Date().toISOString(),C(i,d)),T(d,i)}let a=new Date().toISOString(),c=oe(r),u={version:1,id:se(r),name:c,remote:c,rootPath:r,createdAt:a,updatedAt:a,graphVersion:0,memoryVersion:0};return C(r,u),T(u,r)}};import{homedir as Ee}from"node:os";import{join as Oe}from"node:path";import{DeleteObjectCommand as pe,GetObjectCommand as de,HeadObjectCommand as le,ListObjectsV2Command as me,PutObjectCommand as ge,S3Client as fe}from"@aws-sdk/client-s3";import{getSignedUrl as ye}from"@aws-sdk/s3-request-presigner";var j=class{name="huggingface";client;bucket;constructor(t){this.bucket=t.bucket,this.client=new fe({region:"us-east-1",endpoint:`https://s3.hf.co/${t.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,r="application/octet-stream"){let n=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new ge({Bucket:this.bucket,Key:t,Body:n,ContentType:r}))}async get(t){let e=await ye(this.client,new de({Bucket:this.bucket,Key:t}),{expiresIn:60}),r=await fetch(e,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`HF download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new le({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new pe({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],r;do{let n=await this.client.send(new me({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:r}));for(let s of n.Contents??[])s.Key&&e.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});r=n.IsTruncated?n.NextContinuationToken:void 0}while(r);return e}};import{access as O,mkdir as he,readFile as Se,readdir as we,rm as je,stat as _,writeFile as ve}from"node:fs/promises";import{dirname as xe,join as be,relative as N,resolve as ke}from"node:path";var f=class{constructor(t){this.root=t}root;name="local";path(t){let e=t.replace(/^\/+/,"");return ke(this.root,e)}async put(t,e){let r=this.path(t);await he(xe(r),{recursive:!0}),await ve(r,e)}async get(t){try{return await Se(this.path(t))}catch(e){if(typeof e=="object"&&e!==null&&"code"in e&&e.code==="ENOENT")return null;throw e}}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await O(this.path(t)),!0}catch{return!1}}async delete(t){await je(this.path(t),{force:!0})}async list(t=""){let e=this.path(t),r=[];try{await O(e)}catch{return r}let n=async i=>{let a=await we(i,{withFileTypes:!0});for(let c of a){let u=be(i,c.name);if(c.isDirectory()){await n(u);continue}let p=await _(u);r.push({key:N(this.root,u),size:p.size,updatedAt:p.mtime.toISOString()})}},s=await _(e);return s.isDirectory()?await n(e):r.push({key:N(this.root,e),size:s.size,updatedAt:s.mtime.toISOString()}),r}};import{DeleteObjectCommand as Pe,GetObjectCommand as Re,HeadObjectCommand as Ae,ListObjectsV2Command as Me,PutObjectCommand as Ce,S3Client as Te}from"@aws-sdk/client-s3";import{getSignedUrl as Ie}from"@aws-sdk/s3-request-presigner";var y=class{name;client;bucket;constructor(t){this.name=t.name??"s3",this.bucket=t.bucket,this.client=new Te({region:t.region??"us-east-1",endpoint:t.endpoint||void 0,forcePathStyle:t.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,r="application/octet-stream"){let n=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new Ce({Bucket:this.bucket,Key:t,Body:n,ContentType:r}))}async get(t){let e=await Ie(this.client,new Re({Bucket:this.bucket,Key:t}),{expiresIn:60}),r=await fetch(e,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`${this.name} download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new Ae({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new Pe({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],r;do{let n=await this.client.send(new Me({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:r}));for(let s of n.Contents??[])s.Key&&e.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});r=n.IsTruncated?n.NextContinuationToken:void 0}while(r);return e}};function k(o,t){return console.warn(t),new f(o)}function $(o){let t=o.localRoot??Oe(Ee(),".toolnet-memory","storage");if(o.provider==="r2"){let e=o.r2;return e?.accountId&&e.bucket&&e.accessKeyId&&e.secretAccessKey?new y({name:"r2",endpoint:`https://${e.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:e.bucket,forcePathStyle:!0,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):k(t,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(o.provider==="s3"){let e=o.s3;return e?.bucket&&e.accessKeyId&&e.secretAccessKey?new y({name:"s3",endpoint:e.endpoint,region:e.region??"us-east-1",bucket:e.bucket,forcePathStyle:e.forcePathStyle??!1,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):k(t,"[storage] S3 credentials missing. Using local fallback.")}if(o.provider==="huggingface"){let e=o.huggingface;return e?.namespace&&e.bucket&&e.accessKeyId&&e.secretAccessKey?new j({namespace:e.namespace,bucket:e.bucket,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):k(t,"[storage] Hugging Face credentials missing. Using local fallback.")}return new f(t)}function _e(o){return new Promise(t=>setTimeout(t,o))}async function K(o,t={}){let e=Math.max(1,t.attempts??3),r=t.baseDelayMs??150,n=t.maxDelayMs??2e3,s;for(let i=1;i<=e;i++)try{return await o()}catch(a){if(s=a,i>=e)break;let c=Math.min(n,r*2**(i-1)),u=Math.floor(Math.random()*Math.max(1,c*.2));await _e(c+u)}throw s}var Ne=new Set(["put","get","getText","delete","list"]);function D(o,t={}){return new Proxy(o,{get(e,r){let n=Reflect.get(e,r,e);return typeof n!="function"?n:Ne.has(r)?(...s)=>K(()=>Promise.resolve(n.apply(e,s)),t):n.bind(e)}})}function z(o){let t=o.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!t||t==="."||t==="..")throw new Error("Invalid project storage folder");return t}function L(o){let t=o.replace(/^\/+/,"");if(t.startsWith("memories/"))return"memory/records/"+t.slice(9);if(t.startsWith("vectors/"))return"memory/vectors/"+t.slice(8);if(t.startsWith("graph/"))return"code/graph/"+t.slice(6);let e=t.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(e)return`${e[1]}memory/records/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),e)return`${e[1]}memory/vectors/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),e)return`${e[1]}code/graph/${e[2]}`;let r=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(r)return`${r[1]}memory/records/${r[2]}`;if(r=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),r)return`${r[1]}memory/vectors/${r[2]}`;if(r=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),r)return`${r[1]}code/graph/${r[2]}`;let n=t.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return n?`${n[1]}memory/records/${n[2]}`:(n=t.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),n?`${n[1]}memory/vectors/${n[2]}`:(n=t.match(/^(projects\/[^/]+\/)graph\/(.+)$/),n?`${n[1]}code/graph/${n[2]}`:t))}var v=class{constructor(t,e,r,n){this.provider=t;this.name=t.name,this.projectId=e,this.projectName=r,this.folder=z(n??r),this.sourcePrefix=`projects/${e}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let t=`${this.targetPrefix}/project.json`,e=new Date().toISOString(),r=e,n=await this.provider.getText(t);if(n){let i;try{i=JSON.parse(n)}catch(a){throw new Error(`Invalid remote ToolNet project manifest at ${t}: ${a instanceof Error?a.message:String(a)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(r=i.createdAt)}let s={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:r,updatedAt:e};await this.provider.put(t,JSON.stringify(s,null,2)+`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):t.length>=2&&t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Z(){let o=process.env.TOOLNET_GLOBAL_ENV??W(G(),".config","toolnet-memory",".env");if(!Y(o))return;let t=H(o,"utf8");for(let e of t.split(/\r?\n/)){let r=e.trim();if(!r||r.startsWith("#"))continue;r.startsWith("export ")&&(r=r.slice(7));let n=r.indexOf("=");if(n<=0)continue;let s=r.slice(0,n).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)&&process.env[s]===void 0&&(process.env[s]=Q(r.slice(n+1)))}}Z();function l(o,t){return o===void 0?t:["1","true","yes","on"].includes(o.toLowerCase())}function m(o,t){if(!o)return t;let e=Number(o);return Number.isFinite(e)?e:t}function M(){return{memory:{autoCapture:l(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:l(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:l(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:l(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:m(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:m(process.env.MEMORY_RERANK_TOP,10),finalContext:m(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:m(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:l(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:m(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as X}from"node:crypto";import{existsSync as b,mkdirSync as q,readFileSync as ee,renameSync as te,writeFileSync as re}from"node:fs";import{basename as oe,dirname as h,join as w,parse as I,resolve as g}from"node:path";var E=".toolnet",ne="project.json";function se(o){return X("sha256").update(o).digest("hex").slice(0,16)}function x(o){return w(o,E,ne)}function ie(o){return b(x(o))}function ae(o,t){let e=g(o),r=I(e).root;for(;;){if(ie(e))return e;if(e===r||t&&e===g(t))break;let n=h(e);if(n===e)break;e=n}return null}function ce(o){let t=g(o),e=I(t).root,r=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(r.some(s=>b(w(t,s))))return t;if(t===e)break;let n=h(t);if(n===t)break;t=n}return g(o)}function ue(o){let t;try{t=JSON.parse(ee(o,"utf8"))}catch(n){throw new Error(`Invalid ToolNet project manifest: ${o}: ${n instanceof Error?n.message:String(n)}`)}if(!t||typeof t!="object")throw new Error(`Invalid ToolNet project manifest: ${o}`);let e=t;if(typeof e.id!="string"||!e.id.trim())throw new Error(`ToolNet project manifest is missing id: ${o}`);if(typeof e.name!="string"||!e.name.trim())throw new Error(`ToolNet project manifest is missing name: ${o}`);let r=new Date().toISOString();return{version:1,id:e.id,name:e.name,remote:typeof e.remote=="string"&&e.remote.trim()?e.remote:e.name,rootPath:typeof e.rootPath=="string"?e.rootPath:h(h(o)),createdAt:typeof e.createdAt=="string"?e.createdAt:r,updatedAt:typeof e.updatedAt=="string"?e.updatedAt:r,graphVersion:typeof e.graphVersion=="number"?e.graphVersion:0,memoryVersion:typeof e.memoryVersion=="number"?e.memoryVersion:0,metadata:e.metadata&&typeof e.metadata=="object"?e.metadata:void 0}}function C(o,t){let e=w(o,E);q(e,{recursive:!0});let r=x(o),n=`${r}.tmp-${process.pid}`;re(n,JSON.stringify(t,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),te(n,r)}function T(o,t){return{id:o.id,name:o.name,remote:o.remote,rootPath:t,createdAt:o.createdAt,updatedAt:o.updatedAt,graphVersion:o.graphVersion,memoryVersion:o.memoryVersion,metadata:o.metadata}}var S=class{detect(t=process.cwd()){let e=g(t),r=ce(e),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(p=>b(w(r,p))),i=ae(e,s?r:void 0);if(i){let p=x(i),d=ue(p);return d.rootPath!==i&&(d.rootPath=i,d.updatedAt=new Date().toISOString(),C(i,d)),T(d,i)}let a=new Date().toISOString(),c=oe(r),u={version:1,id:se(r),name:c,remote:c,rootPath:r,createdAt:a,updatedAt:a,graphVersion:0,memoryVersion:0};return C(r,u),T(u,r)}};import{homedir as Ee}from"node:os";import{join as Oe}from"node:path";import{DeleteObjectCommand as pe,GetObjectCommand as de,HeadObjectCommand as le,ListObjectsV2Command as me,PutObjectCommand as ge,S3Client as fe}from"@aws-sdk/client-s3";import{getSignedUrl as ye}from"@aws-sdk/s3-request-presigner";var j=class{name="huggingface";client;bucket;constructor(t){this.bucket=t.bucket,this.client=new fe({region:"us-east-1",endpoint:`https://s3.hf.co/${t.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,r="application/octet-stream"){let n=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new ge({Bucket:this.bucket,Key:t,Body:n,ContentType:r}))}async get(t){let e=await ye(this.client,new de({Bucket:this.bucket,Key:t}),{expiresIn:60}),r=await fetch(e,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`HF download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new le({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new pe({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],r;do{let n=await this.client.send(new me({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:r}));for(let s of n.Contents??[])s.Key&&e.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});r=n.IsTruncated?n.NextContinuationToken:void 0}while(r);return e}};import{access as O,mkdir as he,readFile as Se,readdir as we,rm as je,stat as _,writeFile as ve}from"node:fs/promises";import{dirname as be,join as xe,relative as N,resolve as ke}from"node:path";var f=class{constructor(t){this.root=t}root;name="local";path(t){let e=t.replace(/^\/+/,"");return ke(this.root,e)}async put(t,e){let r=this.path(t);await he(be(r),{recursive:!0}),await ve(r,e)}async get(t){try{return await Se(this.path(t))}catch(e){if(typeof e=="object"&&e!==null&&"code"in e&&e.code==="ENOENT")return null;throw e}}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await O(this.path(t)),!0}catch{return!1}}async delete(t){await je(this.path(t),{force:!0})}async list(t=""){let e=this.path(t),r=[];try{await O(e)}catch{return r}let n=async i=>{let a=await we(i,{withFileTypes:!0});for(let c of a){let u=xe(i,c.name);if(c.isDirectory()){await n(u);continue}let p=await _(u);r.push({key:N(this.root,u),size:p.size,updatedAt:p.mtime.toISOString()})}},s=await _(e);return s.isDirectory()?await n(e):r.push({key:N(this.root,e),size:s.size,updatedAt:s.mtime.toISOString()}),r}};import{DeleteObjectCommand as Pe,GetObjectCommand as Re,HeadObjectCommand as Ae,ListObjectsV2Command as Me,PutObjectCommand as Ce,S3Client as Te}from"@aws-sdk/client-s3";import{getSignedUrl as Ie}from"@aws-sdk/s3-request-presigner";var y=class{name;client;bucket;constructor(t){this.name=t.name??"s3",this.bucket=t.bucket,this.client=new Te({region:t.region??"us-east-1",endpoint:t.endpoint||void 0,forcePathStyle:t.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,r="application/octet-stream"){let n=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new Ce({Bucket:this.bucket,Key:t,Body:n,ContentType:r}))}async get(t){let e=await Ie(this.client,new Re({Bucket:this.bucket,Key:t}),{expiresIn:60}),r=await fetch(e,{redirect:"follow"});if(r.status===404)return null;if(!r.ok)throw new Error(`${this.name} download failed: ${r.status} ${r.statusText}`);return new Uint8Array(await r.arrayBuffer())}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new Ae({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new Pe({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],r;do{let n=await this.client.send(new Me({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:r}));for(let s of n.Contents??[])s.Key&&e.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});r=n.IsTruncated?n.NextContinuationToken:void 0}while(r);return e}};function k(o,t){return console.warn(t),new f(o)}function $(o){let t=o.localRoot??Oe(Ee(),".toolnet-memory","storage");if(o.provider==="r2"){let e=o.r2;return e?.accountId&&e.bucket&&e.accessKeyId&&e.secretAccessKey?new y({name:"r2",endpoint:`https://${e.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:e.bucket,forcePathStyle:!0,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):k(t,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(o.provider==="s3"){let e=o.s3;return e?.bucket&&e.accessKeyId&&e.secretAccessKey?new y({name:"s3",endpoint:e.endpoint,region:e.region??"us-east-1",bucket:e.bucket,forcePathStyle:e.forcePathStyle??!1,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):k(t,"[storage] S3 credentials missing. Using local fallback.")}if(o.provider==="huggingface"){let e=o.huggingface;return e?.namespace&&e.bucket&&e.accessKeyId&&e.secretAccessKey?new j({namespace:e.namespace,bucket:e.bucket,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):k(t,"[storage] Hugging Face credentials missing. Using local fallback.")}return new f(t)}function _e(o){return new Promise(t=>setTimeout(t,o))}async function K(o,t={}){let e=Math.max(1,t.attempts??3),r=t.baseDelayMs??150,n=t.maxDelayMs??2e3,s;for(let i=1;i<=e;i++)try{return await o()}catch(a){if(s=a,i>=e)break;let c=Math.min(n,r*2**(i-1)),u=Math.floor(Math.random()*Math.max(1,c*.2));await _e(c+u)}throw s}var Ne=new Set(["put","get","getText","delete","list"]);function D(o,t={}){return new Proxy(o,{get(e,r){let n=Reflect.get(e,r,e);return typeof n!="function"?n:Ne.has(r)?(...s)=>K(()=>Promise.resolve(n.apply(e,s)),t):n.bind(e)}})}function z(o){let t=o.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!t||t==="."||t==="..")throw new Error("Invalid project storage folder");return t}function L(o){let t=o.replace(/^\/+/,"");if(t.startsWith("memories/"))return"memory/records/"+t.slice(9);if(t.startsWith("vectors/"))return"memory/vectors/"+t.slice(8);if(t.startsWith("graph/"))return"code/graph/"+t.slice(6);let e=t.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(e)return`${e[1]}memory/records/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),e)return`${e[1]}memory/vectors/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),e)return`${e[1]}code/graph/${e[2]}`;let r=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(r)return`${r[1]}memory/records/${r[2]}`;if(r=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),r)return`${r[1]}memory/vectors/${r[2]}`;if(r=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),r)return`${r[1]}code/graph/${r[2]}`;let n=t.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return n?`${n[1]}memory/records/${n[2]}`:(n=t.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),n?`${n[1]}memory/vectors/${n[2]}`:(n=t.match(/^(projects\/[^/]+\/)graph\/(.+)$/),n?`${n[1]}code/graph/${n[2]}`:t))}var v=class{constructor(t,e,r,n){this.provider=t;this.name=t.name,this.projectId=e,this.projectName=r,this.folder=z(n??r),this.sourcePrefix=`projects/${e}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let t=`${this.targetPrefix}/project.json`,e=new Date().toISOString(),r=e,n=await this.provider.getText(t);if(n){let i;try{i=JSON.parse(n)}catch(a){throw new Error(`Invalid remote ToolNet project manifest at ${t}: ${a instanceof Error?a.message:String(a)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(r=i.createdAt)}let s={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:r,updatedAt:e};await this.provider.put(t,JSON.stringify(s,null,2)+`
4
4
  `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(t){if(t=L(t),t===this.sourcePrefix)return this.targetPrefix;if(t.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+t.slice(this.sourcePrefix.length);if(t===this.targetPrefix||t.startsWith(`${this.targetPrefix}/`))return t;if(t.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${t}`].join(" "));return t}async put(t,e,r){return await this.ensureRegistered(),this.provider.put(this.key(t),e,r)}async get(t){return await this.ensureRegistered(),this.provider.get(this.key(t))}async getText(t){return await this.ensureRegistered(),this.provider.getText(this.key(t))}async delete(t){return await this.ensureRegistered(),this.provider.delete(this.key(t))}async exists(t){return await this.ensureRegistered(),this.provider.exists(this.key(t))}async list(t){return await this.ensureRegistered(),this.provider.list(this.key(t))}};import{existsSync as F,mkdirSync as Ke,readFileSync as De,statSync as B,writeFileSync as ze}from"node:fs";import{dirname as Le,join as Be}from"node:path";import{createHash as $e}from"node:crypto";function P(o){return $e("sha256").update(o).digest("hex")}var V=64*1024,Ve=`# ToolNet Project Operating Manual
5
5
 
6
6
  This file contains persistent instructions for AI agents working on this project.