opencode-metis 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -174,7 +174,7 @@ The system has four components, each built as a separate bundle under `dist/`:
174
174
  ### Data Flow
175
175
 
176
176
  ```
177
- OpenCode Session (with X-Session-Id header)
177
+ OpenCode Session
178
178
 
179
179
  ├─ session.created ──────► Worker /api/context/inject ──► SQLite + ChromaDB query ──► context injected
180
180
 
@@ -194,7 +194,6 @@ OpenCode Session (with X-Session-Id header)
194
194
  ### Security
195
195
 
196
196
  - **Bearer token auth** — A cryptographically random token is generated per worker instance and stored in the PID file with `0o600` permissions (owner read/write only). All non-health endpoints require `Authorization: Bearer <token>`.
197
- - **Session isolation** — Each OpenCode session is identified by a unique `X-Session-Id` header. Observations and SSE events are scoped to the originating session, preventing cross-session data access.
198
197
  - **Automatic token refresh** — When the worker restarts, sessions automatically re-read the PID file and retry with the new token (single retry to prevent crash-loop storms).
199
198
  - **Privacy stripping** — `<private>` tags are removed at the hook layer before data leaves the plugin process. Secrets (AWS keys, GitHub tokens, API keys, PEM keys, JWTs) are detected via regex and redacted with `[REDACTED]`.
200
199
  - **Secure API key transmission** — Gemini API keys are sent via `x-goog-api-key` header rather than URL query parameters to prevent exposure in logs and proxy traces.
package/dist/plugin.cjs CHANGED
@@ -1,6 +1,6 @@
1
- "use strict";var w=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var Ee=Object.getOwnPropertyNames;var be=Object.prototype.hasOwnProperty;var xe=(e,t)=>{for(var n in t)w(e,n,{get:t[n],enumerable:!0})},Pe=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Ee(t))!be.call(e,r)&&r!==n&&w(e,r,{get:()=>t[r],enumerable:!(o=Te(t,r))||o.enumerable});return e};var Re=e=>Pe(w({},"__esModule",{value:!0}),e);var bt={};xe(bt,{default:()=>he});module.exports=Re(bt);var I=require("node:fs");var O=require("node:os"),S=require("node:path"),z=41777,G="127.0.0.1",ke=(0,S.join)((0,O.homedir)(),".config","opencode"),Pt=(0,S.join)(ke,"opencode.json"),R=(0,S.join)((0,O.homedir)(),".config","opencode","memory"),_e="memory.db",$e="settings.json",$=(0,S.join)(R,"worker.pid");var Rt=(0,S.join)(R,_e),A=(0,S.join)(R,$e);var F={workerPort:z,workerBind:G,chromaDbUrl:"http://localhost:8000",chromaDbEnabled:!0,recencyWindowDays:90,retentionDays:365,tddEnabled:!0,fileLengthWarn:300,fileLengthCritical:500,testFilePatterns:["*.test.ts","*.spec.ts","*_test.go","test_*.py"],toolRedirectRules:[],aiProvider:null,aiModel:null,aiCompressionTimeoutMs:15e3,aiCompressionMaxRetries:3,aiSkipTools:[],secretDetectionEnabled:!0,secretPatterns:[],contextTokenBudget:2e3,notificationsEnabled:!1,debugEnabled:!1},p=null;function T(){if(p!==null)return p;if(!(0,I.existsSync)(A))return p={...F},p;try{let e=(0,I.readFileSync)(A,"utf-8"),t=JSON.parse(e);return p={...F,...t},p}catch{return p={...F},p}}function H(){let e=T();return`http://${e.workerBind}:${e.workerPort}`}var b=require("node:fs"),Z=require("node:os"),M=require("node:path");var E=require("node:fs"),C=require("node:path");var K=(0,C.join)(R,"plugin-debug.log"),j=null;function Ie(){return j===null&&(j=T().debugEnabled),j}function c(e,t){if(!Ie())return;let o=`[${new Date().toISOString()}] [${e}] ${t}
2
- `;try{let r=(0,C.dirname)(K);(0,E.existsSync)(r)||(0,E.mkdirSync)(r,{recursive:!0}),(0,E.appendFileSync)(K,o,"utf-8")}catch{}}var Ce=5e3,m="compacted";function ve(){return process.env.OPENCODE_SESSIONS_DIR??(0,M.join)((0,Z.homedir)(),".config","opencode","sessions")}function we(e){return(0,M.join)(ve(),"sessions",e,"pre-compact-state.json")}function Oe(e){let t=["[Memory Context Restored After Compaction]"];return e.activePlan&&t.push(`Active plan: ${e.activePlan}`),e.currentTask&&t.push(`Current task: ${e.currentTask}`),e.status&&t.push(`Status: ${e.status}`),e.summary&&t.push(`Recent context: ${e.summary}`),t.join(`
3
- `)}async function Ae(e,t,n,o){let r=`${e}/api/context/inject?project=${encodeURIComponent(n)}`,a=await J(r,t);if(a.status===401&&o!==void 0){let s=o();a=await J(r,s)}return a.ok?(await a.json()).context??null:null}async function J(e,t){let n={};return t!==null&&(n.Authorization=`Bearer ${t}`),fetch(e,{headers:n,signal:AbortSignal.timeout(Ce)})}function Fe(e){let t=we(e);if(!(0,b.existsSync)(t))return null;try{let n=(0,b.readFileSync)(t,"utf-8"),o=JSON.parse(n);return(0,b.unlinkSync)(t),o}catch{return null}}function q(e,t){e.messages=e.messages??[],e.messages.push({role:"user",content:t})}async function X(e,t,n,o,r,a){c(m,`Session compacted: ${o.sessionId}`);try{let i=null;try{let l=`${t}/api/context/inject?project=${encodeURIComponent(e.project.name)}`;c(m,`GET ${l}`),i=await Ae(t,n,e.project.name,a),c(m,i?`got context (${i.length} chars)`:"no context from worker")}catch(l){if(l instanceof TypeError)throw l;c(m,`worker fetch failed: ${l instanceof Error?l.message:String(l)}`)}if(i){q(r,i),c(m,"injected worker context");return}let s=Fe(o.sessionId);if(s){let l=Oe(s);q(r,l),c(m,"injected fallback state from file")}else c(m,"no context available")}catch(i){c(m,`error: ${i instanceof Error?i.message:String(i)}`),e.client.app.log("warn","Compacted state restore failed",{error:i instanceof Error?i.message:String(i),sessionId:o.sessionId})}}var D=require("node:path");var je=5e3,Me=5,d="compacting";function De(e){if(e?.project?.name&&e.project.name!=="undefined")return e.project.name;if(e?.directory){let t=(0,D.basename)(e.directory);if(t&&t!=="."&&t!=="/")return t}if(e?.worktree){let t=(0,D.basename)(e.worktree);if(t&&t!=="."&&t!=="/")return t}}async function Ne(e,t,n,o){try{let r="decisions context observations",a=new URLSearchParams({query:r,limit:String(Me)});n!==void 0&&n!=="undefined"&&n.length>0&&a.set("project",n);let i=`${e}/api/search?${a.toString()}`;c(d,`GET ${i}`);let s=await Y(i,t);if(s.status===401&&o!==void 0){c(d,"401 received, refreshing token and retrying");let u=o();s=await Y(i,u)}if(c(d,`response.ok=${s.ok}, status=${s.status}`),!s.ok)return[];let l=await s.json();return c(d,`found ${l.results.length} memories`),l.results}catch(r){return c(d,`fetch error: ${r instanceof Error?r.message:String(r)}`),[]}}async function Y(e,t){let n={};return t!==null&&(n.Authorization=`Bearer ${t}`),fetch(e,{method:"GET",headers:n,signal:AbortSignal.timeout(je)})}function Le(e){if(e.length===0)return"";let t=["## Relevant Context from Memory"];t.push(""),t.push("The following information was retrieved from long-term memory and may be relevant:"),t.push("");for(let n of e){let o=n.title||"Observation",r=n.relevanceScore!==null?` (relevance: ${Math.abs(n.relevanceScore*100).toFixed(0)}%)`:"";t.push(`### ${o}${r}`),t.push(n.summary),t.push("")}return t.join(`
4
- `)}async function V(e,t,n,o,r,a){c(d,"Session compacting - fetching memories..."),c(d,`workerUrl=${t}, token=${n?"present":"null"}`),c(d,`input=${JSON.stringify(o).slice(0,300)}`);let i=De(e);c(d,`project=${i??"none"} (dir=${e?.directory??"unknown"})`);let s=await Ne(t,n,i,a);if(s.length>0){let l=Le(s);r.context.push(l),c(d,`injected ${s.length} memories into compaction context`)}else c(d,"no memories found to inject")}var Q=require("node:fs"),f=require("node:path");var Ue=300,We=500,Be=["*.test.ts","*.spec.ts","*_test.go","test_*.py"],N="file-edited";function ze(e,t){let n=t.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${n}$`).test(e)}function Ge(e,t){let n=(0,f.basename)(e);return t.some(o=>ze(n,o))}function He(e,t){let n=(0,f.dirname)(e),o=(0,f.extname)(e),r=(0,f.basename)(e,o),a=[];for(let i of t)if(i.startsWith("*.")){let s=i.slice(1);a.push((0,f.join)(n,`${r}${s}`))}else if(i.endsWith("*")){let s=i.slice(0,-1);a.push((0,f.join)(n,`${s}${r}${o}`))}return a}function L(e,t){e.messages=e.messages??[],e.messages.push({role:"system",content:t})}function Ke(e,t,n,o){if(Ge(t,n))return;let r=He(t,n),a=e.project.root;r.some(s=>{let l=s.startsWith("/")?s:(0,f.join)(a,s);return(0,Q.existsSync)(l)})||L(o,`\u26A0\uFE0F TDD Warning: No test file found for ${t}. Consider writing tests first (Red-Green-Refactor).`)}function Je(e,t,n,o,r){if(t>o){L(r,`\u{1F6A8} File Length Critical: ${e} has ${t} lines. This file should be refactored.`);return}t>n&&L(r,`\u26A0\uFE0F File Length Warning: ${e} has ${t} lines. Consider splitting this file.`)}async function ee(e,t,n,o){try{c(N,`File edited: ${n.filePath}`);let r=T(),a=n.testFilePatterns??r.testFilePatterns??Be,i=n.fileLengthWarn??r.fileLengthWarn??Ue,s=n.fileLengthCritical??r.fileLengthCritical??We;Ke(e,n.filePath,a,o);let l=n.lineCount??0;Je(n.filePath,l,i,s,o),c(N,`checks complete for ${n.filePath} (${l} lines)`)}catch(r){c(N,`error: ${r instanceof Error?r.message:String(r)}`),e.client.app.log("warn","File edited check failed",{error:r instanceof Error?r.message:String(r),filePath:n.filePath})}}var qe=15e3,x="session-idle";function Ze(e){let{context:t}=e;return t?!!(t.requested||t.investigated||t.learned||t.completed):!1}function Xe(e){let t=e.context??{};return["Session Summary:",`Requested: ${t.requested??"Not specified"}`,`Investigated: ${t.investigated??"Not specified"}`,`Learned: ${t.learned??"Not specified"}`,`Completed: ${t.completed??"Not specified"}`].join(`
5
- `)}async function ne(e,t,n,o,r,a,i){if(c(x,`Session idle: ${o.sessionId}`),!Ze(o)){c(x,"no meaningful context to save");return}let s=i??o.sessionId,l={type:"session-summary",title:`Session Summary: ${o.sessionId}`,text:Xe(o),project:e.project.name};try{let u=`${t}/api/memory/save`;c(x,`POST ${u}`);let g=await te(u,l,n,s);if(g.status===401&&a!==void 0){c(x,"401 received, refreshing token and retrying");let h=a();g=await te(u,l,h,s)}c(x,`response.status=${g.status}`),g.ok||e.client.app.log("warn","Failed to save session summary",{status:g.status})}catch(u){c(x,`error: ${u instanceof Error?u.message:String(u)}`),e.client.app.log("warn","Session summary save failed",{error:u instanceof Error?u.message:String(u)})}}async function te(e,t,n,o){let r={"Content-Type":"application/json"};return n!==null&&(r.Authorization=`Bearer ${n}`),o!=null&&(r["X-Session-Id"]=o),fetch(e,{method:"POST",headers:r,body:JSON.stringify(t),signal:AbortSignal.timeout(qe)})}var Ye=3e3,y="session-start";function Ve(e){let t={};return e!==null&&(t.Authorization=`Bearer ${e}`),t}async function re(e,t){return fetch(e,{headers:Ve(t),signal:AbortSignal.timeout(Ye)})}async function oe(e,t,n,o,r,a){c(y,"Session started");let i=`${t}/api/context/inject?project=${encodeURIComponent(e.project.name)}`;c(y,`GET ${i}`);try{let s=await re(i,n);if(c(y,`response.status=${s.status}`),s.status===401&&a!==void 0){c(y,"Token expired, refreshing...");let u=a();s=await re(i,u),c(y,`retry response.status=${s.status}`)}if(!s.ok){e.client.app.log("warn","Failed to load memory context",{status:s.status});return}let l=await s.json();l.context?(r.messages=r.messages??[],r.messages.push({role:"system",content:l.context}),c(y,`injected context (${l.context.length} chars)`)):c(y,"no context returned from worker")}catch(s){c(y,`error: ${s instanceof Error?s.message:String(s)}`),e.client.app.log("warn","Memory context injection failed",{error:s instanceof Error?s.message:String(s)})}}var Qe=15e3,et=200,se="tool-after";async function ae(e,t,n,o,r,a,i,s){c(se,`Tool completed: ${r.tool}`),tt(e,t,n,o,r,a,i,s).catch(l=>{c(se,`error: ${l instanceof Error?l.message:String(l)}`),e.client.app.log("warn","Observation capture failed",{error:l instanceof Error?l.message:String(l)})})}async function tt(e,t,n,o,r,a,i,s){let l=`Tool ${r.tool}: ${rt(r,a)}`,u=`${r.tool} execution`,g=nt(r.input),h=a.output??a.error??"",k={text:o.sanitize(l),title:o.sanitize(u),project:e.project.name,toolName:r.tool,toolInput:o.sanitize(g),toolOutput:o.sanitize(h)},B=`${t}/api/memory/save`,_=await ie(B,k,n,s);if(_.status===401&&i!==void 0){let Se=i();_=await ie(B,k,Se,s)}_.ok||e.client.app.log("warn","Observation save failed",{status:_.status})}async function ie(e,t,n,o){let r={"Content-Type":"application/json"};return n!==null&&(r.Authorization=`Bearer ${n}`),o!=null&&(r["X-Session-Id"]=o),fetch(e,{method:"POST",headers:r,body:JSON.stringify(t),signal:AbortSignal.timeout(Qe)})}function nt(e){try{return JSON.stringify(e)}catch{return String(e)}}function rt(e,t){let n=t.output??t.error??"",o=ot(e.input),r=n.slice(0,et).trim();return o&&r?`${o} -> ${r}`:o||r||"(no details)"}function ot(e){let t=Object.entries(e);return t.length===0?"":t.map(([n,o])=>`${n}=${String(o).slice(0,80)}`).join(", ")}var v="tool-before";function st(){let e=process.env.MEMORY_TOOL_REDIRECT_RULES;if(e!==void 0)try{return JSON.parse(e)}catch{return[]}return T().toolRedirectRules}function it(e){if(typeof e!="object"||e===null)return!1;let t=e;return typeof t.tool=="string"&&(t.action==="deny"||t.action==="redirect")}function at(e){return e.message??`Tool '${e.tool}' is blocked by project configuration.`}function ct(e){let t=e.message??`Tool '${e.tool}' has been redirected.`;return e.alternative?`${t} Suggested alternative: ${e.alternative}.`:t}function lt(e,t){t.blocked=!0,t.messages=[{role:"user",content:at(e)}]}function ut(e,t){t.messages=[{role:"user",content:ct(e)}]}async function ce(e,t,n,o){c(v,`Tool executing: ${n.tool}`);let r=st();for(let a of r)try{if(!it(a))throw new Error("Invalid rule: tool must be a string and action must be 'deny' or 'redirect'");if(a.tool!==n.tool)continue;a.action==="deny"?(lt(a,o),c(v,`blocked tool ${n.tool} (deny rule)`)):(ut(a,o),c(v,`redirected tool ${n.tool}`));return}catch(i){c(v,`rule error: ${i instanceof Error?i.message:String(i)}`),e.client.app.log("warn","Tool redirect rule evaluation failed",{error:i instanceof Error?i.message:String(i),rule:JSON.stringify(a)})}}var P=require("node:fs");function gt(e){try{return process.kill(e,0),!0}catch{return!1}}function U(e){if(!(0,P.existsSync)(e))return{success:!1,errorType:"not_found",message:`PID file not found at ${e}`};let t=(0,P.statSync)(e);if((t.mode&63)!==0){let s=`0o${(t.mode&511).toString(8)}`;return{success:!1,errorType:"bad_permissions",message:`PID file at ${e} has permissions ${s} \u2014 expected 0o600`}}let n;try{n=JSON.parse((0,P.readFileSync)(e,"utf-8"))}catch{return{success:!1,errorType:"invalid_format",message:`PID file at ${e} contains invalid JSON`}}if(typeof n!="object"||n===null||typeof n.token!="string"||typeof n.pid!="number")return{success:!1,errorType:"invalid_format",message:`PID file at ${e} is missing required fields (token, pid)`};let o=n;if(o.port!==void 0&&typeof o.port!="number")return{success:!1,errorType:"invalid_format",message:`PID file at ${e} has invalid port field`};let{token:r,pid:a,port:i}=o;return gt(a)?{success:!0,info:i!==void 0?{token:r,pid:a,port:i}:{token:r,pid:a}}:{success:!1,errorType:"dead_pid",message:`Worker process ${a} is not running`}}function le(e){let t=U(e);return t.success?t.info.token:null}function ue(e){switch(e.errorType){case"not_found":return"Worker not running \u2014 PID file not found. Start it with `opencode worker start`.";case"bad_permissions":return"PID file has unsafe permissions (expected 0o600). Fix with `chmod 600 <pid-file>` then restart the worker.";case"dead_pid":return"Worker process is no longer running. Restart it with `opencode worker start`.";case"invalid_format":return"PID file is corrupted or has invalid format. Delete it and restart the worker."}}var ge=[{name:"aws_access_key",pattern:/AKIA[0-9A-Z]{16}/g},{name:"github_pat",pattern:/ghp_[a-zA-Z0-9]{36}/g},{name:"github_oauth",pattern:/gho_[a-zA-Z0-9]{36}/g},{name:"anthropic_key",pattern:/sk-ant-[a-zA-Z0-9\-_]{20,}/g},{name:"openai_key",pattern:/sk-[a-zA-Z0-9]{20,}/g},{name:"jwt_token",pattern:/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g},{name:"pem_private_key",pattern:/-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/g},{name:"generic_api_key",pattern:/(?:api[_-]?key|token|secret|password)\s*[:=]\s*['"]?([a-zA-Z0-9\-_]{20,})['"]?/gi}];function dt(e,t=ge){if(!e)return e;let n=e;for(let{pattern:o}of t)o.lastIndex=0,n=n.replace(o,"[REDACTED]");return n}function de(e=[]){let t=[...ge,...e];return n=>dt(n,t)}var ft=/<private>[\s\S]*?<\/private>/gi;function fe(e){return e.replace(ft,"")}function pe(e={}){let{additionalSecretPatterns:t=[],secretDetectionEnabled:n=!0}=e,o=de(t);return{sanitize(r){let a=fe(r);return n?o(a):a}}}function ye(e,t,n,o,r){let a={ctx:e,workerUrl:t,getToken:n,refreshToken:o,getSessionId:r};return[pt(a),mt(a)]}function pt(e){return{name:"memory_search",description:"Search memory observations by topic or keyword",parameters:{query:{type:"string",description:"The search query or topic to look up in memory",required:!0},limit:{type:"number",description:"Maximum number of results to return (default 20)"},project:{type:"string",description:"Filter results to a specific project name"}},execute:t=>yt(e,t)}}function mt(e){return{name:"memory_save",description:"Save an observation to memory for future reference",parameters:{text:{type:"string",description:"The observation text to save to memory",required:!0},title:{type:"string",description:"Optional short title summarising the observation"}},execute:t=>ht(e,t)}}function W(e,t){let n={};return e!==null&&(n.Authorization=`Bearer ${e}`),t!==null&&(n["X-Session-Id"]=t),n}async function yt(e,t){let{ctx:n,workerUrl:o,getToken:r,refreshToken:a,getSessionId:i}=e;try{let s=St(o,t),l=i?.()??null,u=await fetch(s,{headers:W(r(),l),signal:AbortSignal.timeout(1e4)});if(u.status===401){let h=a();u=await fetch(s,{headers:W(h,l),signal:AbortSignal.timeout(1e4)})}if(!u.ok)return{result:`Error: Worker returned status ${u.status}`};let g=await u.json();return{result:Tt(g.results??[])}}catch(s){return n.client.app.log("warn","memory_search failed",{error:s instanceof Error?s.message:String(s)}),{result:`Error: ${s instanceof Error?s.message:String(s)}`}}}async function ht(e,t){let{ctx:n,workerUrl:o,getToken:r,refreshToken:a,getSessionId:i}=e;try{let s={text:t.text};t.title!==void 0&&(s.title=t.title);let l=`${o}/api/memory/save`,u=i?.()??null,g=await me(l,s,r(),u);if(g.status===401){let k=a();g=await me(l,s,k,u)}if(!g.ok)return{result:`Error: Worker returned status ${g.status}`};let h=await g.json();return{result:Et(h)}}catch(s){return n.client.app.log("warn","memory_save failed",{error:s instanceof Error?s.message:String(s)}),{result:`Error: ${s instanceof Error?s.message:String(s)}`}}}async function me(e,t,n,o){return fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...W(n,o)},body:JSON.stringify(t),signal:AbortSignal.timeout(1e4)})}function St(e,t){let n=new URLSearchParams;return n.set("query",String(t.query??"")),t.limit!==void 0&&n.set("limit",String(t.limit)),t.project!==void 0&&n.set("project",String(t.project)),`${e}/api/search?${n.toString()}`}function Tt(e){return e.length===0?"No results found for the given query.":e.map(t=>`[${t.id}] ${t.title} (${t.type}, ${t.project}) \u2014 ${t.summary}`).join(`
6
- `)}function Et(e){let t=e.id??"unknown",n=e.title??"(untitled)";return`Saved observation #${t}: "${n}"`}async function he(e){let t=H(),n=pe(),o=U($),r,a=null;o.success?r=o.info.token:(r=null,e.client.app.log("warn",ue(o),{errorType:o.errorType,pidFilePath:$}));function i(){return r=le($),r}let s=()=>r,l=()=>a;return ye(e,t,s,i,l),{"session.created":(u,g)=>(a=u.sessionId,oe(e,t,s(),u,g,i)),"tool.execute.after":(u,g)=>ae(e,t,s(),n,u,g,i,l()),"tool.execute.before":(u,g)=>ce(e,t,u,g),"session.idle":(u,g)=>ne(e,t,s(),u,g,i,l()),"experimental.session.compacting":(u,g)=>V(e,t,s(),u,g,i),"session.compacted":(u,g)=>X(e,t,s(),u,g,i),"file.edited":(u,g)=>ee(e,t,u,g)}}
1
+ "use strict";var v=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var Se=Object.getOwnPropertyNames;var Ee=Object.prototype.hasOwnProperty;var be=(e,t)=>{for(var r in t)v(e,r,{get:t[r],enumerable:!0})},xe=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Se(t))!Ee.call(e,o)&&o!==r&&v(e,o,{get:()=>t[o],enumerable:!(s=Te(t,o))||s.enumerable});return e};var Pe=e=>xe(v({},"__esModule",{value:!0}),e);var Et={};be(Et,{default:()=>ye});module.exports=Pe(Et);var k=require("node:fs");var I=require("node:os"),y=require("node:path"),B=41777,z="127.0.0.1",Re=(0,y.join)((0,I.homedir)(),".config","opencode"),xt=(0,y.join)(Re,"opencode.json"),x=(0,y.join)((0,I.homedir)(),".config","opencode","memory"),ke="memory.db",_e="settings.json",R=(0,y.join)(x,"worker.pid");var Pt=(0,y.join)(x,ke),w=(0,y.join)(x,_e);var O={workerPort:B,workerBind:z,chromaDbUrl:"http://localhost:8000",chromaDbEnabled:!0,recencyWindowDays:90,retentionDays:365,tddEnabled:!0,fileLengthWarn:300,fileLengthCritical:500,testFilePatterns:["*.test.ts","*.spec.ts","*_test.go","test_*.py"],toolRedirectRules:[],aiProvider:null,aiModel:null,aiCompressionTimeoutMs:15e3,aiCompressionMaxRetries:3,aiSkipTools:[],secretDetectionEnabled:!0,secretPatterns:[],contextTokenBudget:2e3,notificationsEnabled:!1,debugEnabled:!1},p=null;function h(){if(p!==null)return p;if(!(0,k.existsSync)(w))return p={...O},p;try{let e=(0,k.readFileSync)(w,"utf-8"),t=JSON.parse(e);return p={...O,...t},p}catch{return p={...O},p}}function G(){let e=h();return`http://${e.workerBind}:${e.workerPort}`}var S=require("node:fs"),q=require("node:os"),F=require("node:path");var T=require("node:fs"),_=require("node:path");var H=(0,_.join)(x,"plugin-debug.log"),A=null;function $e(){return A===null&&(A=h().debugEnabled),A}function l(e,t){if(!$e())return;let s=`[${new Date().toISOString()}] [${e}] ${t}
2
+ `;try{let o=(0,_.dirname)(H);(0,T.existsSync)(o)||(0,T.mkdirSync)(o,{recursive:!0}),(0,T.appendFileSync)(H,s,"utf-8")}catch{}}var Ce=5e3,f="compacted";function ve(){return process.env.OPENCODE_SESSIONS_DIR??(0,F.join)((0,q.homedir)(),".config","opencode","sessions")}function Ie(e){return(0,F.join)(ve(),"sessions",e,"pre-compact-state.json")}function we(e){let t=["[Memory Context Restored After Compaction]"];return e.activePlan&&t.push(`Active plan: ${e.activePlan}`),e.currentTask&&t.push(`Current task: ${e.currentTask}`),e.status&&t.push(`Status: ${e.status}`),e.summary&&t.push(`Recent context: ${e.summary}`),t.join(`
3
+ `)}async function Oe(e,t,r,s){let o=`${e}/api/context/inject?project=${encodeURIComponent(r)}`,a=await K(o,t);if(a.status===401&&s!==void 0){let n=s();a=await K(o,n)}return a.ok?(await a.json()).context??null:null}async function K(e,t){let r={};return t!==null&&(r.Authorization=`Bearer ${t}`),fetch(e,{headers:r,signal:AbortSignal.timeout(Ce)})}function Ae(e){let t=Ie(e);if(!(0,S.existsSync)(t))return null;try{let r=(0,S.readFileSync)(t,"utf-8"),s=JSON.parse(r);return(0,S.unlinkSync)(t),s}catch{return null}}function J(e,t){e.messages=e.messages??[],e.messages.push({role:"user",content:t})}async function Z(e,t,r,s,o,a){l(f,`Session compacted: ${s.sessionId}`);try{let i=null;try{let c=`${t}/api/context/inject?project=${encodeURIComponent(e.project.name)}`;l(f,`GET ${c}`),i=await Oe(t,r,e.project.name,a),l(f,i?`got context (${i.length} chars)`:"no context from worker")}catch(c){if(c instanceof TypeError)throw c;l(f,`worker fetch failed: ${c instanceof Error?c.message:String(c)}`)}if(i){J(o,i),l(f,"injected worker context");return}let n=Ae(s.sessionId);if(n){let c=we(n);J(o,c),l(f,"injected fallback state from file")}else l(f,"no context available")}catch(i){l(f,`error: ${i instanceof Error?i.message:String(i)}`),e.client.app.log("warn","Compacted state restore failed",{error:i instanceof Error?i.message:String(i),sessionId:s.sessionId})}}var j=require("node:path");var Fe=5e3,je=5,g="compacting";function Me(e){if(e?.project?.name&&e.project.name!=="undefined")return e.project.name;if(e?.directory){let t=(0,j.basename)(e.directory);if(t&&t!=="."&&t!=="/")return t}if(e?.worktree){let t=(0,j.basename)(e.worktree);if(t&&t!=="."&&t!=="/")return t}}async function De(e,t,r,s){try{let o="decisions context observations",a=new URLSearchParams({query:o,limit:String(je)});r!==void 0&&r!=="undefined"&&r.length>0&&a.set("project",r);let i=`${e}/api/search?${a.toString()}`;l(g,`GET ${i}`);let n=await Y(i,t);if(n.status===401&&s!==void 0){l(g,"401 received, refreshing token and retrying");let u=s();n=await Y(i,u)}if(l(g,`response.ok=${n.ok}, status=${n.status}`),!n.ok)return[];let c=await n.json();return l(g,`found ${c.results.length} memories`),c.results}catch(o){return l(g,`fetch error: ${o instanceof Error?o.message:String(o)}`),[]}}async function Y(e,t){let r={};return t!==null&&(r.Authorization=`Bearer ${t}`),fetch(e,{method:"GET",headers:r,signal:AbortSignal.timeout(Fe)})}function Ne(e){if(e.length===0)return"";let t=["## Relevant Context from Memory"];t.push(""),t.push("The following information was retrieved from long-term memory and may be relevant:"),t.push("");for(let r of e){let s=r.title||"Observation",o=r.relevanceScore!==null?` (relevance: ${Math.abs(r.relevanceScore*100).toFixed(0)}%)`:"";t.push(`### ${s}${o}`),t.push(r.summary),t.push("")}return t.join(`
4
+ `)}async function V(e,t,r,s,o,a){l(g,"Session compacting - fetching memories..."),l(g,`workerUrl=${t}, token=${r?"present":"null"}`),l(g,`input=${JSON.stringify(s).slice(0,300)}`);let i=Me(e);l(g,`project=${i??"none"} (dir=${e?.directory??"unknown"})`);let n=await De(t,r,i,a);if(n.length>0){let c=Ne(n);o.context.push(c),l(g,`injected ${n.length} memories into compaction context`)}else l(g,"no memories found to inject")}var X=require("node:fs"),d=require("node:path");var Le=300,Ue=500,We=["*.test.ts","*.spec.ts","*_test.go","test_*.py"],M="file-edited";function Be(e,t){let r=t.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}function ze(e,t){let r=(0,d.basename)(e);return t.some(s=>Be(r,s))}function Ge(e,t){let r=(0,d.dirname)(e),s=(0,d.extname)(e),o=(0,d.basename)(e,s),a=[];for(let i of t)if(i.startsWith("*.")){let n=i.slice(1);a.push((0,d.join)(r,`${o}${n}`))}else if(i.endsWith("*")){let n=i.slice(0,-1);a.push((0,d.join)(r,`${n}${o}${s}`))}return a}function D(e,t){e.messages=e.messages??[],e.messages.push({role:"system",content:t})}function He(e,t,r,s){if(ze(t,r))return;let o=Ge(t,r),a=e.project.root;o.some(n=>{let c=n.startsWith("/")?n:(0,d.join)(a,n);return(0,X.existsSync)(c)})||D(s,`\u26A0\uFE0F TDD Warning: No test file found for ${t}. Consider writing tests first (Red-Green-Refactor).`)}function Ke(e,t,r,s,o){if(t>s){D(o,`\u{1F6A8} File Length Critical: ${e} has ${t} lines. This file should be refactored.`);return}t>r&&D(o,`\u26A0\uFE0F File Length Warning: ${e} has ${t} lines. Consider splitting this file.`)}async function Q(e,t,r,s){try{l(M,`File edited: ${r.filePath}`);let o=h(),a=r.testFilePatterns??o.testFilePatterns??We,i=r.fileLengthWarn??o.fileLengthWarn??Le,n=r.fileLengthCritical??o.fileLengthCritical??Ue;He(e,r.filePath,a,s);let c=r.lineCount??0;Ke(r.filePath,c,i,n,s),l(M,`checks complete for ${r.filePath} (${c} lines)`)}catch(o){l(M,`error: ${o instanceof Error?o.message:String(o)}`),e.client.app.log("warn","File edited check failed",{error:o instanceof Error?o.message:String(o),filePath:r.filePath})}}var Je=15e3,E="session-idle";function qe(e){let{context:t}=e;return t?!!(t.requested||t.investigated||t.learned||t.completed):!1}function Ze(e){let t=e.context??{};return["Session Summary:",`Requested: ${t.requested??"Not specified"}`,`Investigated: ${t.investigated??"Not specified"}`,`Learned: ${t.learned??"Not specified"}`,`Completed: ${t.completed??"Not specified"}`].join(`
5
+ `)}async function te(e,t,r,s,o,a){if(l(E,`Session idle: ${s.sessionId}`),!qe(s)){l(E,"no meaningful context to save");return}let i={type:"session-summary",title:`Session Summary: ${s.sessionId}`,text:Ze(s),project:e.project.name};try{let n=`${t}/api/memory/save`;l(E,`POST ${n}`);let c=await ee(n,i,r);if(c.status===401&&a!==void 0){l(E,"401 received, refreshing token and retrying");let u=a();c=await ee(n,i,u)}l(E,`response.status=${c.status}`),c.ok||e.client.app.log("warn","Failed to save session summary",{status:c.status})}catch(n){l(E,`error: ${n instanceof Error?n.message:String(n)}`),e.client.app.log("warn","Session summary save failed",{error:n instanceof Error?n.message:String(n)})}}async function ee(e,t,r){let s={"Content-Type":"application/json"};return r!==null&&(s.Authorization=`Bearer ${r}`),fetch(e,{method:"POST",headers:s,body:JSON.stringify(t),signal:AbortSignal.timeout(Je)})}var Ye=3e3,m="session-start";function Ve(e){let t={};return e!==null&&(t.Authorization=`Bearer ${e}`),t}async function re(e,t){return fetch(e,{headers:Ve(t),signal:AbortSignal.timeout(Ye)})}async function ne(e,t,r,s,o,a){l(m,"Session started");let i=`${t}/api/context/inject?project=${encodeURIComponent(e.project.name)}`;l(m,`GET ${i}`);try{let n=await re(i,r);if(l(m,`response.status=${n.status}`),n.status===401&&a!==void 0){l(m,"Token expired, refreshing...");let u=a();n=await re(i,u),l(m,`retry response.status=${n.status}`)}if(!n.ok){e.client.app.log("warn","Failed to load memory context",{status:n.status});return}let c=await n.json();c.context?(o.messages=o.messages??[],o.messages.push({role:"system",content:c.context}),l(m,`injected context (${c.context.length} chars)`)):l(m,"no context returned from worker")}catch(n){l(m,`error: ${n instanceof Error?n.message:String(n)}`),e.client.app.log("warn","Memory context injection failed",{error:n instanceof Error?n.message:String(n)})}}var Xe=15e3,Qe=200,oe="tool-after";async function ie(e,t,r,s,o,a,i){l(oe,`Tool completed: ${o.tool}`),et(e,t,r,s,o,a,i).catch(n=>{l(oe,`error: ${n instanceof Error?n.message:String(n)}`),e.client.app.log("warn","Observation capture failed",{error:n instanceof Error?n.message:String(n)})})}async function et(e,t,r,s,o,a,i){let n=`Tool ${o.tool}: ${rt(o,a)}`,c=`${o.tool} execution`,u=tt(o.input),C=a.output??a.error??"",U={text:s.sanitize(n),title:s.sanitize(c),project:e.project.name,toolName:o.tool,toolInput:s.sanitize(u),toolOutput:s.sanitize(C)},W=`${t}/api/memory/save`,P=await se(W,U,r);if(P.status===401&&i!==void 0){let he=i();P=await se(W,U,he)}P.ok||e.client.app.log("warn","Observation save failed",{status:P.status})}async function se(e,t,r){let s={"Content-Type":"application/json"};return r!==null&&(s.Authorization=`Bearer ${r}`),fetch(e,{method:"POST",headers:s,body:JSON.stringify(t),signal:AbortSignal.timeout(Xe)})}function tt(e){try{return JSON.stringify(e)}catch{return String(e)}}function rt(e,t){let r=t.output??t.error??"",s=nt(e.input),o=r.slice(0,Qe).trim();return s&&o?`${s} -> ${o}`:s||o||"(no details)"}function nt(e){let t=Object.entries(e);return t.length===0?"":t.map(([r,s])=>`${r}=${String(s).slice(0,80)}`).join(", ")}var $="tool-before";function ot(){let e=process.env.MEMORY_TOOL_REDIRECT_RULES;if(e!==void 0)try{return JSON.parse(e)}catch{return[]}return h().toolRedirectRules}function st(e){if(typeof e!="object"||e===null)return!1;let t=e;return typeof t.tool=="string"&&(t.action==="deny"||t.action==="redirect")}function it(e){return e.message??`Tool '${e.tool}' is blocked by project configuration.`}function at(e){let t=e.message??`Tool '${e.tool}' has been redirected.`;return e.alternative?`${t} Suggested alternative: ${e.alternative}.`:t}function ct(e,t){t.blocked=!0,t.messages=[{role:"user",content:it(e)}]}function lt(e,t){t.messages=[{role:"user",content:at(e)}]}async function ae(e,t,r,s){l($,`Tool executing: ${r.tool}`);let o=ot();for(let a of o)try{if(!st(a))throw new Error("Invalid rule: tool must be a string and action must be 'deny' or 'redirect'");if(a.tool!==r.tool)continue;a.action==="deny"?(ct(a,s),l($,`blocked tool ${r.tool} (deny rule)`)):(lt(a,s),l($,`redirected tool ${r.tool}`));return}catch(i){l($,`rule error: ${i instanceof Error?i.message:String(i)}`),e.client.app.log("warn","Tool redirect rule evaluation failed",{error:i instanceof Error?i.message:String(i),rule:JSON.stringify(a)})}}var b=require("node:fs");function ut(e){try{return process.kill(e,0),!0}catch{return!1}}function N(e){if(!(0,b.existsSync)(e))return{success:!1,errorType:"not_found",message:`PID file not found at ${e}`};let t=(0,b.statSync)(e);if((t.mode&63)!==0){let n=`0o${(t.mode&511).toString(8)}`;return{success:!1,errorType:"bad_permissions",message:`PID file at ${e} has permissions ${n} \u2014 expected 0o600`}}let r;try{r=JSON.parse((0,b.readFileSync)(e,"utf-8"))}catch{return{success:!1,errorType:"invalid_format",message:`PID file at ${e} contains invalid JSON`}}if(typeof r!="object"||r===null||typeof r.token!="string"||typeof r.pid!="number")return{success:!1,errorType:"invalid_format",message:`PID file at ${e} is missing required fields (token, pid)`};let s=r;if(s.port!==void 0&&typeof s.port!="number")return{success:!1,errorType:"invalid_format",message:`PID file at ${e} has invalid port field`};let{token:o,pid:a,port:i}=s;return ut(a)?{success:!0,info:i!==void 0?{token:o,pid:a,port:i}:{token:o,pid:a}}:{success:!1,errorType:"dead_pid",message:`Worker process ${a} is not running`}}function ce(e){let t=N(e);return t.success?t.info.token:null}function le(e){switch(e.errorType){case"not_found":return"Worker not running \u2014 PID file not found. Start it with `opencode worker start`.";case"bad_permissions":return"PID file has unsafe permissions (expected 0o600). Fix with `chmod 600 <pid-file>` then restart the worker.";case"dead_pid":return"Worker process is no longer running. Restart it with `opencode worker start`.";case"invalid_format":return"PID file is corrupted or has invalid format. Delete it and restart the worker."}}var ue=[{name:"aws_access_key",pattern:/AKIA[0-9A-Z]{16}/g},{name:"github_pat",pattern:/ghp_[a-zA-Z0-9]{36}/g},{name:"github_oauth",pattern:/gho_[a-zA-Z0-9]{36}/g},{name:"anthropic_key",pattern:/sk-ant-[a-zA-Z0-9\-_]{20,}/g},{name:"openai_key",pattern:/sk-[a-zA-Z0-9]{20,}/g},{name:"jwt_token",pattern:/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g},{name:"pem_private_key",pattern:/-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/g},{name:"generic_api_key",pattern:/(?:api[_-]?key|token|secret|password)\s*[:=]\s*['"]?([a-zA-Z0-9\-_]{20,})['"]?/gi}];function gt(e,t=ue){if(!e)return e;let r=e;for(let{pattern:s}of t)s.lastIndex=0,r=r.replace(s,"[REDACTED]");return r}function ge(e=[]){let t=[...ue,...e];return r=>gt(r,t)}var dt=/<private>[\s\S]*?<\/private>/gi;function de(e){return e.replace(dt,"")}function pe(e={}){let{additionalSecretPatterns:t=[],secretDetectionEnabled:r=!0}=e,s=ge(t);return{sanitize(o){let a=de(o);return r?s(a):a}}}function me(e,t,r,s){let o={ctx:e,workerUrl:t,getToken:r,refreshToken:s};return[pt(o),ft(o)]}function pt(e){return{name:"memory_search",description:"Search memory observations by topic or keyword",parameters:{query:{type:"string",description:"The search query or topic to look up in memory",required:!0},limit:{type:"number",description:"Maximum number of results to return (default 20)"},project:{type:"string",description:"Filter results to a specific project name"}},execute:t=>mt(e,t)}}function ft(e){return{name:"memory_save",description:"Save an observation to memory for future reference",parameters:{text:{type:"string",description:"The observation text to save to memory",required:!0},title:{type:"string",description:"Optional short title summarising the observation"}},execute:t=>yt(e,t)}}function L(e){let t={};return e!==null&&(t.Authorization=`Bearer ${e}`),t}async function mt(e,t){let{ctx:r,workerUrl:s,getToken:o,refreshToken:a}=e;try{let i=ht(s,t),n=await fetch(i,{headers:L(o()),signal:AbortSignal.timeout(1e4)});if(n.status===401){let u=a();n=await fetch(i,{headers:L(u),signal:AbortSignal.timeout(1e4)})}if(!n.ok)return{result:`Error: Worker returned status ${n.status}`};let c=await n.json();return{result:Tt(c.results??[])}}catch(i){return r.client.app.log("warn","memory_search failed",{error:i instanceof Error?i.message:String(i)}),{result:`Error: ${i instanceof Error?i.message:String(i)}`}}}async function yt(e,t){let{ctx:r,workerUrl:s,getToken:o,refreshToken:a}=e;try{let i={text:t.text};t.title!==void 0&&(i.title=t.title);let n=`${s}/api/memory/save`,c=await fe(n,i,o());if(c.status===401){let C=a();c=await fe(n,i,C)}if(!c.ok)return{result:`Error: Worker returned status ${c.status}`};let u=await c.json();return{result:St(u)}}catch(i){return r.client.app.log("warn","memory_save failed",{error:i instanceof Error?i.message:String(i)}),{result:`Error: ${i instanceof Error?i.message:String(i)}`}}}async function fe(e,t,r){return fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...L(r)},body:JSON.stringify(t),signal:AbortSignal.timeout(1e4)})}function ht(e,t){let r=new URLSearchParams;return r.set("query",String(t.query??"")),t.limit!==void 0&&r.set("limit",String(t.limit)),t.project!==void 0&&r.set("project",String(t.project)),`${e}/api/search?${r.toString()}`}function Tt(e){return e.length===0?"No results found for the given query.":e.map(t=>`[${t.id}] ${t.title} (${t.type}, ${t.project}) \u2014 ${t.summary}`).join(`
6
+ `)}function St(e){let t=e.id??"unknown",r=e.title??"(untitled)";return`Saved observation #${t}: "${r}"`}async function ye(e){let t=G(),r=pe(),s=N(R),o;s.success?o=s.info.token:(o=null,e.client.app.log("warn",le(s),{errorType:s.errorType,pidFilePath:R}));function a(){return o=ce(R),o}let i=()=>o;return me(e,t,i,a),{"session.created":(n,c)=>ne(e,t,i(),n,c,a),"tool.execute.after":(n,c)=>ie(e,t,i(),r,n,c,a),"tool.execute.before":(n,c)=>ae(e,t,n,c),"session.idle":(n,c)=>te(e,t,i(),n,c,a),"experimental.session.compacting":(n,c)=>V(e,t,i(),n,c,a),"session.compacted":(n,c)=>Z(e,t,i(),n,c,a),"file.edited":(n,c)=>Q(e,t,n,c)}}