grix-connector 3.8.3 → 3.9.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/dist/adapter/acp/acp-adapter.js +13 -13
- package/dist/adapter/acp/session-scanner.js +5 -2
- package/dist/adapter/claude/claude-adapter.js +1 -1
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/adapter/claude/session-scanner.js +1 -1
- package/dist/adapter/codewhale/session-scanner.js +1 -0
- package/dist/adapter/codex/session-scanner.js +1 -1
- package/dist/adapter/cursor/cursor-adapter.js +2 -2
- package/dist/adapter/cursor/session-scanner.js +1 -0
- package/dist/adapter/opencode/session-scanner.js +6 -0
- package/dist/adapter/pi/session-scanner.js +1 -0
- package/dist/bridge/adapter-pool.js +1 -1
- package/dist/bridge/bridge.js +10 -10
- package/dist/bridge/event-queue.js +1 -1
- package/dist/bridge/send-controller.js +3 -3
- package/dist/bridge/session-controller.js +1 -1
- package/dist/bridge/session-scan-cache.js +1 -1
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/protocol/interaction-parser.js +1 -1
- package/dist/core/provider-quota/index.js +1 -1
- package/dist/core/provider-quota/providers.js +2 -1
- package/dist/core/runtime/cursor-resolve.js +1 -0
- package/dist/core/util/session-title.js +1 -0
- package/dist/core/util/sqlite-reader.js +1 -0
- package/dist/log.js +2 -2
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/dist/protocol/acp-client.js +1 -1
- package/openclaw-plugin/index.js +44 -35
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as
|
|
2
|
-
`),"utf8"),{expectPath:a,pidPath:i}}function h(
|
|
1
|
+
import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
|
|
2
|
+
`),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(
|
|
1
|
+
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdirSync as u,statSync as
|
|
1
|
+
import{readdirSync as u,statSync as d}from"node:fs";import l from"node:path";import{homedir as p}from"node:os";import{log as h}from"../../core/log/index.js";import{readFirstLines as y}from"../../core/util/read-first-lines.js";import{makeTitle as f}from"../../core/util/session-title.js";function T(s){const t=s.trim();return t?t.startsWith("<local-command-caveat>")||t.startsWith("<local-command-stdout>")||t.startsWith("<command-name>"):!0}function m(s){if(T(s))return;const t=s.match(/<channel[^>]*>([\s\S]+?)<\/channel>/);if(t){const o=t[1].trim();if(o)return f(o)}return f(s)}function S(s){for(const t of s)try{const o=JSON.parse(t);if(o.type!=="user")continue;const i=o.message?.content;if(typeof i=="string"){const e=m(i);if(e)return e;continue}if(Array.isArray(i)){for(const e of i)if(e.type==="text"&&e.text){const r=m(e.text);if(r)return r}}}catch{}}function j(s){try{const t=d(s),o=y(s,131072);let i="",e="",r="";for(const a of o.slice(0,80))try{const n=JSON.parse(a);if(n.sessionId&&!i&&(i=n.sessionId),n.type==="custom-title"&&n.customTitle&&!n.customTitle.startsWith("grix-")&&(e=n.customTitle),n.type==="ai-title"&&n.aiTitle&&!e&&(e=n.aiTitle),n.cwd&&!r&&(r=n.cwd),i&&e&&r)break}catch{}if(!i)return null;const c=e||S(o);return{sessionId:i,cwd:r||void 0,title:c,mtimeMs:Math.round(t.mtimeMs)}}catch{return null}}function C(s=l.join(p(),".claude","projects")){const t=[];let o;try{o=u(s,{withFileTypes:!0})}catch{return t}for(const i of o){if(!i.isDirectory())continue;const e=l.join(s,i.name);let r;try{r=u(e,{withFileTypes:!0})}catch{continue}for(const c of r){if(!c.isFile()||!c.name.endsWith(".jsonl"))continue;const a=l.join(e,c.name),n=j(a);n&&t.push({sessionId:n.sessionId,cwd:n.cwd||"",title:n.title,updatedAt:n.mtimeMs,filePath:a})}}return t.sort((i,e)=>e.updatedAt-i.updatedAt),h.info("claude-session-scanner",`Scanned ${t.length} Claude sessions from ${s}`),t}export{C as scanClaudeSessions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as d,readFileSync as c,readdirSync as u,statSync as m}from"node:fs";import r from"node:path";import{homedir as a}from"node:os";import{log as l}from"../../core/log/index.js";import{makeTitle as p}from"../../core/util/session-title.js";function f(n=a()){const t=r.join(n,".deepseek");return d(t)?t:r.join(n,".codewhale")}function h(n){try{const t=Math.round(m(n).mtimeMs),e=JSON.parse(c(n,"utf8"))?.metadata;if(!e?.id)return null;const o=e.created_at?new Date(e.created_at).getTime():void 0,s=e.updated_at?new Date(e.updated_at).getTime():t;return{sessionId:e.id,cwd:typeof e.workspace=="string"?e.workspace:"",title:typeof e.title=="string"?p(e.title):void 0,createdAt:Number.isFinite(o)?o:void 0,updatedAt:Number.isFinite(s)?s:t,filePath:n}}catch{return null}}function F(n=a()){const t=r.join(f(n),"sessions"),e=[];let o;try{o=u(t,{withFileTypes:!0})}catch{return e}for(const s of o){if(!s.isFile()||!s.name.endsWith(".json"))continue;const i=h(r.join(t,s.name));i&&e.push(i)}return e.sort((s,i)=>i.updatedAt-s.updatedAt),l.info("codewhale-session-scanner",`Scanned ${e.length} CodeWhale sessions from ${t}`),e}export{f as resolveCodeWhaleHome,F as scanCodeWhaleSessions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdirSync as p,statSync as
|
|
1
|
+
import{readdirSync as p,statSync as u}from"node:fs";import l from"node:path";import{resolveCodexHome as f}from"./codex-trust.js";import{log as y}from"../../core/log/index.js";import{readFirstLines as h}from"../../core/util/read-first-lines.js";import{makeTitle as g}from"../../core/util/session-title.js";function d(r,n="rollout-"){const i=[];let e;try{e=p(r,{withFileTypes:!0})}catch{return i}for(const o of e){const a=l.join(r,o.name);o.isDirectory()?i.push(...d(a,n)):o.isFile()&&o.name.startsWith(n)&&o.name.endsWith(".jsonl")&&i.push(a)}return i}function v(r){for(const n of r.slice(1))try{const i=JSON.parse(n);if(i.type==="event_msg"&&i.payload?.type==="user_message"){const e=i.payload.message;if(typeof e=="string"&&e.trim())return g(e)}}catch{}}function m(r){try{const n=u(r),i=h(r,131072);if(i.length===0)return null;const e=JSON.parse(i[0]);if(e.type!=="session_meta"||!e.payload?.id)return null;const o=v(i);let a=e.payload.cwd||"";if(!a)for(const c of i.slice(1,80))try{const s=JSON.parse(c);if(s.type==="turn_context"&&s.payload?.cwd){a=s.payload.cwd;break}}catch{}return{meta:{id:e.payload.id,timestamp:e.payload.timestamp,cwd:a,originator:e.payload.originator,cli_version:e.payload.cli_version},title:o,mtimeMs:Math.round(n.mtimeMs)}}catch{return null}}function M(r){const n=f(r),i=l.join(n,"sessions"),e=l.join(n,"archived_sessions"),o=[],a=d(i);for(const s of a){const t=m(s);t&&o.push({threadId:t.meta.id,cwd:t.meta.cwd||"",title:t.title,originator:t.meta.originator,cliVersion:t.meta.cli_version,createdAt:new Date(t.meta.timestamp).getTime(),updatedAt:t.mtimeMs,archived:!1,filePath:s})}const c=d(e);for(const s of c){const t=m(s);t&&o.push({threadId:t.meta.id,cwd:t.meta.cwd||"",title:t.title,originator:t.meta.originator,cliVersion:t.meta.cli_version,createdAt:new Date(t.meta.timestamp).getTime(),updatedAt:t.mtimeMs,archived:!0,filePath:s})}return o.sort((s,t)=>t.updatedAt-s.updatedAt),y.info("codex-session-scanner",`Scanned ${o.length} Codex sessions from ${n}`),o}export{M as scanCodexSessions};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{createInterface as q}from"node:readline";import{EventEmitter as M}from"node:events";import{mkdirSync as x,readFileSync as T,writeFileSync as C,readdirSync as j,statSync as F}from"node:fs";import{join as f,resolve as N}from"node:path";import{fileURLToPath as U}from"node:url";import{homedir as $}from"node:os";import{GRIX_PATHS as H,log as c}from"../../core/log/index.js";import{IdentityInjector as O}from"../shared/identity-injector.js";import{buildSimpleProbeReport as W}from"../shared/probe-util.js";import{InternalApiServer as J}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as G}from"../../default-skills/index.js";import{resolveCommandPath as B,spawnCommand as E,killProcessGroup as S}from"../../core/runtime/spawn.js";import{SessionBindingStore as X}from"../../core/persistence/session-binding-store.js";const b=12e4,z=new Set(["plan","ask"]);function K(h){try{return process.kill(h,0),!0}catch(e){return e.code==="EPERM"}}function L(h,e,t){if(t<0)return null;let i;try{i=j(h,{withFileTypes:!0})}catch{return null}for(const s of i){const o=f(h,s.name);if(s.isDirectory()){const n=L(o,e,t-1);if(n)return n}else if(s.isFile()&&s.name.endsWith(e))return o}return null}function Q(h,e){if(!h)return null;const t=e||f($(),".cursor","projects");return L(t,`${h}.jsonl`,6)}class P extends M{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){c.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class I extends M{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};this.bindingStore=i.bindingStore instanceof X?i.bindingStore:null,this.identity=new O("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new J,this.internalApi.setInvokeHandler(async(t,i,s,o)=>this.callbacks.agentInvoke(t,i,o)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=B(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),i=E(t,["
|
|
2
|
-
`)){const r=n.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&o.push({id:d[1],displayName:d[2].trim()})}o.length>0&&(this._availableModels=o,c.info("cursor-adapter",`Loaded ${o.length} available models`))}catch(e){c.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())S(e.child,"SIGTERM");this.activeBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const i=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...i,cwd:typeof t?.cwd=="string"?t.cwd:i.cwd,modelId:typeof t?.modelId=="string"?t.modelId:i.modelId,modeId:typeof t?.modeId=="string"?t.modeId:i.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const t=new P(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const i=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,o=this.pendingBySession.get(s)??[];return o.push({event:i,request:e,handle:t}),this.pendingBySession.set(s,o),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const i=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),i&&this.startJob(i,!1,0)}startJob(e,t,i){const{event:s,request:o,handle:n}=e,r=o.adapterSessionId,d=this.sessionRuntime.get(o.adapterSessionId)??{},m=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),m.trust!==!1&&u.push("--trust");const v=d.cwd||m.workspace;this.internalApi&&v&&(this.ensureWorkspaceMcpAndSkills(v),u.push("--approve-mcps")),v&&u.push("--workspace",v);const A=d.modelId||m.model;A&&u.push("--model",A);const y=d.modeId||m.mode;y&&z.has(y)&&u.push("--mode",y);const w=d.cursorSessionId??this.bindingStore?.getAcpSessionId(o.adapterSessionId),R=!!(w&&m.use_continue!==!1&&!t);R&&u.push("--resume",w),u.push(this.buildPromptText(o));const _={...process.env,...this.config.env??{}},D=B(this.config.command,typeof _.PATH=="string"?_.PATH:void 0);c.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${i}`);let p;try{p=E(D,u,{cwd:v||process.cwd(),env:_,stdio:["ignore","pipe","pipe"]}).process}catch(a){this.finishActive(r,"failed",`spawn failed: ${a instanceof Error?a.message:String(a)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id);const l={event:s,request:o,handle:n,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,retryCount:i,usedContinue:R,workspace:v||process.cwd(),args:u};this.activeBySession.set(r,l),this.armActiveIdleTimer(r);const k=o.timeoutMs&&o.timeoutMs>0?o.timeoutMs:0;k>0&&(l.timer=setTimeout(()=>{l.done||(S(p,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${k}ms`))},k)),q({input:p.stdout}).on("line",a=>this.handleStdoutLineForActive(l,a)),p.stderr?.on("data",a=>{const g=l.stderr+String(a??"");l.stderr=g.length>I.STDERR_MAX_CHARS?g.slice(-I.STDERR_MAX_CHARS):g}),p.once("error",a=>{this.finishActive(r,"failed",`spawn failed: ${String(a?.message??a)}`)}),p.once("close",a=>{if(!l.done)if((a??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(l)){l.timer&&clearTimeout(l.timer),l.idleTimer&&clearTimeout(l.idleTimer),this.activeBySession.delete(r),this.startJob(e,!0,l.retryCount+1);return}const g=l.stderr.trim()||`cursor agent exited with code ${a??-1}`;this.finishActive(r,"failed",g)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(S(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){c.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){c.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const i={adapterSessionId:t,text:String(e.content??"")},s=new P(t),o=this.pendingBySession.get(t)??[];o.push({event:e,request:i,handle:s}),this.pendingBySession.set(t,o),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${o.length}`),this.tryStartNext(t)}killChildWithFallback(e){S(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{S(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const i=this.activeBySession.get(t);if(i?.event.event_id===e){this.killChildWithFallback(i.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],o=s.findIndex(n=>n.event.event_id===e);if(o>=0){const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[i,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(i,"canceled","stopped");return}for(const[i,s]of this.pendingBySession.entries()){const o=s.findIndex(r=>r.event.event_id===e);if(o<0)continue;const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(i):this.pendingBySession.set(i,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),i=e.params??{},s=String(i.session_id??"").trim(),o=e.action_id;switch(t){case"set_model":{const n=String(i.model_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modelId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"model_set",session_context:{model_id:n,mode_id:r.modeId??null,modelId:n,modeId:r.modeId??null},model_id:n,mode_id:r.modeId??null,available_models:this.availableModels}),{handled:!0,kind:"set_model"}}case"set_mode":{const n=String(i.mode_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modeId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"mode_set",session_context:{model_id:r.modelId??null,mode_id:n,modelId:r.modelId??null,modeId:n},model_id:r.modelId??null,mode_id:n}),{handled:!0,kind:"set_mode"}}case"get_context":{const n=s?this.sessionRuntime.get(s):void 0;return this.callbacks.sendLocalActionResult(o,"ok",{session_context:{model_id:n?.modelId??null,mode_id:n?.modeId??null,cwd:n?.cwd??null},model_id:n?.modelId??null,mode_id:n?.modeId??null,modelId:n?.modelId??null,modeId:n?.modeId??null,cwd:n?.cwd??null,available_models:this.availableModels}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{const n=this.getRateLimitsSnapshot();return this.callbacks.sendLocalActionResult(o,"ok",n),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const n=s?this.getUsageSnapshot(s):null;return n?this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!0,sampledAt:n.sampledAt,turns:n.turns,tokenUsage:n.total}):this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const n=String(i.verb??"").trim().toLowerCase();if(n==="restart"&&s)this.deliverStopEvent("__all__",s),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"restarted"});else if(n==="status"){const r=this.sessionRuntime.get(s),d=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(o,"ok",{verb:"status",status:d?"running":"idle",session_context:{model_id:r?.modelId??null,mode_id:r?.modeId??null,cwd:r?.cwd??null},model_id:r?.modelId??null,mode_id:r?.modeId??null,modelId:r?.modelId??null,modeId:r?.modeId??null,cwd:r?.cwd??null})}else this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_verb",`Unsupported verb: ${n}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const i of this.activeBySession.values()){const s=i.child?.pid;if(s&&!K(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await W(this.config.command||"agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?Q(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const i=F(t);return{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(){return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}}buildPromptText(e){let t;return!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(s=>`[${s.senderId}] ${s.content}`).join(`
|
|
1
|
+
import{createInterface as q}from"node:readline";import{EventEmitter as M}from"node:events";import{mkdirSync as x,readFileSync as T,writeFileSync as C,readdirSync as j,statSync as F}from"node:fs";import{join as f,resolve as N}from"node:path";import{fileURLToPath as U}from"node:url";import{homedir as $}from"node:os";import{GRIX_PATHS as H,log as c}from"../../core/log/index.js";import{IdentityInjector as O}from"../shared/identity-injector.js";import{buildSimpleProbeReport as W}from"../shared/probe-util.js";import{InternalApiServer as J}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as G}from"../../default-skills/index.js";import{resolveCommandPath as B,spawnCommand as E,killProcessGroup as S}from"../../core/runtime/spawn.js";import{SessionBindingStore as X}from"../../core/persistence/session-binding-store.js";const b=12e4,z=new Set(["plan","ask"]);function K(h){try{return process.kill(h,0),!0}catch(e){return e.code==="EPERM"}}function L(h,e,t){if(t<0)return null;let i;try{i=j(h,{withFileTypes:!0})}catch{return null}for(const s of i){const o=f(h,s.name);if(s.isDirectory()){const n=L(o,e,t-1);if(n)return n}else if(s.isFile()&&s.name.endsWith(e))return o}return null}function Q(h,e){if(!h)return null;const t=e||f($(),".cursor","projects");return L(t,`${h}.jsonl`,6)}class P extends M{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){c.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class I extends M{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};this.bindingStore=i.bindingStore instanceof X?i.bindingStore:null,this.identity=new O("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new J,this.internalApi.setInvokeHandler(async(t,i,s,o)=>this.callbacks.agentInvoke(t,i,o)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=B(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),i=E(t,["models"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}),s=await new Promise(n=>{let r="";i.process.stdout?.on("data",d=>{r+=String(d)}),i.process.once("close",()=>n(r))}),o=[];for(const n of s.split(`
|
|
2
|
+
`)){const r=n.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&o.push({id:d[1],displayName:d[2].trim()})}o.length>0&&(this._availableModels=o,c.info("cursor-adapter",`Loaded ${o.length} available models`))}catch(e){c.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())S(e.child,"SIGTERM");this.activeBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const i=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...i,cwd:typeof t?.cwd=="string"?t.cwd:i.cwd,modelId:typeof t?.modelId=="string"?t.modelId:i.modelId,modeId:typeof t?.modeId=="string"?t.modeId:i.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const t=new P(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const i=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,o=this.pendingBySession.get(s)??[];return o.push({event:i,request:e,handle:t}),this.pendingBySession.set(s,o),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const i=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),i&&this.startJob(i,!1,0)}startJob(e,t,i){const{event:s,request:o,handle:n}=e,r=o.adapterSessionId,d=this.sessionRuntime.get(o.adapterSessionId)??{},m=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),m.trust!==!1&&u.push("--trust");const v=d.cwd||m.workspace;this.internalApi&&v&&(this.ensureWorkspaceMcpAndSkills(v),u.push("--approve-mcps")),v&&u.push("--workspace",v);const A=d.modelId||m.model;A&&u.push("--model",A);const y=d.modeId||m.mode;y&&z.has(y)&&u.push("--mode",y);const w=d.cursorSessionId??this.bindingStore?.getAcpSessionId(o.adapterSessionId),R=!!(w&&m.use_continue!==!1&&!t);R&&u.push("--resume",w),u.push(this.buildPromptText(o));const _={...process.env,...this.config.env??{}},D=B(this.config.command,typeof _.PATH=="string"?_.PATH:void 0);c.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${i}`);let p;try{p=E(D,u,{cwd:v||process.cwd(),env:_,stdio:["ignore","pipe","pipe"]}).process}catch(a){this.finishActive(r,"failed",`spawn failed: ${a instanceof Error?a.message:String(a)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id);const l={event:s,request:o,handle:n,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,retryCount:i,usedContinue:R,workspace:v||process.cwd(),args:u};this.activeBySession.set(r,l),this.armActiveIdleTimer(r);const k=o.timeoutMs&&o.timeoutMs>0?o.timeoutMs:0;k>0&&(l.timer=setTimeout(()=>{l.done||(S(p,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${k}ms`))},k)),q({input:p.stdout}).on("line",a=>this.handleStdoutLineForActive(l,a)),p.stderr?.on("data",a=>{const g=l.stderr+String(a??"");l.stderr=g.length>I.STDERR_MAX_CHARS?g.slice(-I.STDERR_MAX_CHARS):g}),p.once("error",a=>{this.finishActive(r,"failed",`spawn failed: ${String(a?.message??a)}`)}),p.once("close",a=>{if(!l.done)if((a??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(l)){l.timer&&clearTimeout(l.timer),l.idleTimer&&clearTimeout(l.idleTimer),this.activeBySession.delete(r),this.startJob(e,!0,l.retryCount+1);return}const g=l.stderr.trim()||`cursor agent exited with code ${a??-1}`;this.finishActive(r,"failed",g)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(S(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){c.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){c.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const i={adapterSessionId:t,text:String(e.content??"")},s=new P(t),o=this.pendingBySession.get(t)??[];o.push({event:e,request:i,handle:s}),this.pendingBySession.set(t,o),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${o.length}`),this.tryStartNext(t)}killChildWithFallback(e){S(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{S(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const i=this.activeBySession.get(t);if(i?.event.event_id===e){this.killChildWithFallback(i.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],o=s.findIndex(n=>n.event.event_id===e);if(o>=0){const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[i,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(i,"canceled","stopped");return}for(const[i,s]of this.pendingBySession.entries()){const o=s.findIndex(r=>r.event.event_id===e);if(o<0)continue;const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(i):this.pendingBySession.set(i,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),i=e.params??{},s=String(i.session_id??"").trim(),o=e.action_id;switch(t){case"set_model":{const n=String(i.model_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modelId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"model_set",session_context:{model_id:n,mode_id:r.modeId??null,modelId:n,modeId:r.modeId??null},model_id:n,mode_id:r.modeId??null,available_models:this.availableModels}),{handled:!0,kind:"set_model"}}case"set_mode":{const n=String(i.mode_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modeId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"mode_set",session_context:{model_id:r.modelId??null,mode_id:n,modelId:r.modelId??null,modeId:n},model_id:r.modelId??null,mode_id:n}),{handled:!0,kind:"set_mode"}}case"get_context":{const n=s?this.sessionRuntime.get(s):void 0;return this.callbacks.sendLocalActionResult(o,"ok",{session_context:{model_id:n?.modelId??null,mode_id:n?.modeId??null,cwd:n?.cwd??null},model_id:n?.modelId??null,mode_id:n?.modeId??null,modelId:n?.modelId??null,modeId:n?.modeId??null,cwd:n?.cwd??null,available_models:this.availableModels}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{const n=this.getRateLimitsSnapshot();return this.callbacks.sendLocalActionResult(o,"ok",n),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const n=s?this.getUsageSnapshot(s):null;return n?this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!0,sampledAt:n.sampledAt,turns:n.turns,tokenUsage:n.total}):this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const n=String(i.verb??"").trim().toLowerCase();if(n==="restart"&&s)this.deliverStopEvent("__all__",s),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"restarted"});else if(n==="status"){const r=this.sessionRuntime.get(s),d=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(o,"ok",{verb:"status",status:d?"running":"idle",session_context:{model_id:r?.modelId??null,mode_id:r?.modeId??null,cwd:r?.cwd??null},model_id:r?.modelId??null,mode_id:r?.modeId??null,modelId:r?.modelId??null,modeId:r?.modeId??null,cwd:r?.cwd??null})}else this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_verb",`Unsupported verb: ${n}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const i of this.activeBySession.values()){const s=i.child?.pid;if(s&&!K(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await W(this.config.command||"cursor-agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?Q(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const i=F(t);return{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(){return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}}buildPromptText(e){let t;return!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(s=>`[${s.senderId}] ${s.content}`).join(`
|
|
3
3
|
`)}
|
|
4
4
|
|
|
5
5
|
[Current user message]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as h,readdirSync as l}from"node:fs";import d from"node:path";import{homedir as p}from"node:os";import{log as m}from"../../core/log/index.js";import{makeTitle as y,stripInjectedPreamble as b}from"../../core/util/session-title.js";import{openReadOnlyDatabase as x}from"../../core/util/sqlite-reader.js";function S(r=p()){return d.join(r,".cursor","chats")}function g(r){try{return JSON.parse(h(r,"utf8"))}catch{return null}}function w(r){const e=[];for(let t=0;t+34<=r.length;t+=1)r[t]===10&&r[t+1]===32&&e.push(r.subarray(t+2,t+34).toString("hex"));return e}function A(r){let e;try{e=JSON.parse(r.toString("utf8"))}catch{return}if(e.role!=="user")return;const t=e.content;if(typeof t=="string")return t;if(Array.isArray(t)){for(const n of t)if(n&&typeof n=="object"&&typeof n.text=="string")return n.text}}function C(r){const e=x(r);if(e)try{const t=e.all("SELECT value FROM meta LIMIT 1");if(t.length===0)return;const n=JSON.parse(Buffer.from(t[0].value,"hex").toString("utf8"));if(!n.latestRootBlobId)return;const o=new Map;for(const i of e.all("SELECT id, data FROM blobs"))o.set(i.id,Buffer.from(i.data));const s=o.get(n.latestRootBlobId);if(!s)return;for(const i of w(s)){const a=o.get(i);if(!a)continue;const u=A(a);if(!u)continue;const c=/<user_query>([\s\S]*?)<\/user_query>/.exec(u);if(c)return y(b(c[1]))}return}catch{return}finally{e.close()}}function O(r=p()){const e=S(r),t=[];let n;try{n=l(e,{withFileTypes:!0})}catch{return t}for(const o of n){if(!o.isDirectory())continue;const s=d.join(e,o.name);let i;try{i=l(s,{withFileTypes:!0})}catch{continue}for(const a of i){if(!a.isDirectory())continue;const u=d.join(s,a.name),c=d.join(u,"meta.json"),f=g(c);f?.updatedAtMs&&t.push({sessionId:a.name,cwd:f.cwd??"",title:f.hasConversation?C(d.join(u,"store.db")):void 0,createdAt:f.createdAtMs,updatedAt:f.updatedAtMs,filePath:c})}}return t.sort((o,s)=>s.updatedAt-o.updatedAt),m.info("cursor-session-scanner",`Scanned ${t.length} Cursor sessions from ${e}`),t}export{S as resolveCursorChatsDir,O as scanCursorSessions};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import i from"node:path";import{homedir as s}from"node:os";import{log as d}from"../../core/log/index.js";import{makeTitle as c,stripInjectedPreamble as p}from"../../core/util/session-title.js";import{openReadOnlyDatabase as m}from"../../core/util/sqlite-reader.js";function l(t=s()){const o=process.env.XDG_DATA_HOME;return i.join(o||i.join(t,".local","share"),"opencode")}function f(t=s()){return i.join(l(t),"opencode.db")}function u(t,o){const r=t.all(`SELECT p.data AS data FROM part p
|
|
2
|
+
JOIN message m ON m.id = p.message_id
|
|
3
|
+
WHERE p.session_id = ? AND json_extract(m.data, '$.role') = 'user'
|
|
4
|
+
ORDER BY p.time_created, p.id
|
|
5
|
+
LIMIT 20`,o);for(const a of r)try{const n=JSON.parse(a.data);if(n.type!=="text"||typeof n.text!="string")continue;const e=c(p(n.text));if(e)return e}catch{}}function x(t=s()){const o=f(t),r=m(o);if(!r)return[];try{const n=r.all(`SELECT id, directory, time_created, time_updated, time_archived
|
|
6
|
+
FROM session ORDER BY time_updated DESC`).map(e=>({sessionId:e.id,cwd:e.directory??"",title:u(r,e.id),createdAt:e.time_created,updatedAt:e.time_updated,archived:e.time_archived!=null}));return d.info("opencode-session-scanner",`Scanned ${n.length} OpenCode sessions from ${o}`),n}catch{return[]}finally{r.close()}}export{l as resolveOpenCodeDataDir,f as resolveOpenCodeDbPath,x as scanOpenCodeSessions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readdirSync as l,statSync as h}from"node:fs";import m from"node:path";import{homedir as u}from"node:os";import{log as g}from"../../core/log/index.js";import{readFirstLines as w}from"../../core/util/read-first-lines.js";import{makeTitle as p,stripInjectedPreamble as y}from"../../core/util/session-title.js";function A(a=u(),r){return r||process.env.PI_CODING_AGENT_DIR||m.join(a,".pi","agent")}function P(a){const r=w(a,131072);if(r.length===0)return null;let o="",n,i;for(const c of r)try{const t=JSON.parse(c);if(t.type==="session"){typeof t.cwd=="string"&&(o=t.cwd);const e=t.timestamp?new Date(t.timestamp).getTime():void 0;Number.isFinite(e)&&(n=e);continue}if(i||t.type!=="message"||t.message?.role!=="user")continue;const s=t.message.content;if(typeof s=="string"){i=p(y(s));continue}if(!Array.isArray(s))continue;for(const e of s)if(e&&typeof e=="object"&&typeof e.text=="string"&&(i=p(y(e.text)),i))break}catch{}return o?{cwd:o,title:i,createdAt:n}:null}function x(a=u(),r){const o=m.join(A(a,r),"sessions"),n=[];let i;try{i=l(o,{withFileTypes:!0})}catch{return n}for(const c of i){if(!c.isDirectory())continue;const t=m.join(o,c.name);let s;try{s=l(t,{withFileTypes:!0})}catch{continue}for(const e of s){if(!e.isFile()||!e.name.endsWith(".jsonl"))continue;const d=m.join(t,e.name),f=P(d);f&&n.push({sessionPath:d,cwd:f.cwd,title:f.title,createdAt:f.createdAt,updatedAt:Math.round(h(d).mtimeMs)})}}return n.sort((c,t)=>t.updatedAt-c.updatedAt),g.info("pi-session-scanner",`Scanned ${n.length} Pi sessions from ${o}`),n}export{A as resolvePiAgentDir,x as scanPiSessions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as p}from"./event-queue.js";class c extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class g{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new c(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,r=i}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state==="ready"){if(s.adapter.getStatus().busy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(r=>{r?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})))}}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values())t.state==="ready"&&(t.adapter.getStatus().busy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t));return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots)r.state==="ready"&&(r.adapter.getStatus().busy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s));return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values()){if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0)return!0;if(e.state==="ready"){const t=e.adapter.getStatus();if(t.busy||t.backgroundBusy)return!0}}return!1}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const a=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(i){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),i}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventDone?.(t,e.sessionId)}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,a,i)=>{this.onEventState?.(r,n,a,i)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,a)=>{this.onQueueComposing?.(r,n,a)}};return new p(t,s)}}export{g as AdapterPool,c as PoolFullError};
|
|
1
|
+
import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as c}from"./event-queue.js";class p extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class S{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new p(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const a=this.createSlot(s);try{await a.startPromise,r=a}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(a=>a.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(a=>a.status==="fulfilled"&&a.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state!=="ready")continue;const r=s.adapter.getStatus();if(r.busy||r.backgroundBusy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(n=>{n?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(i=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${i}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})))}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values()){if(t.state!=="ready")continue;const s=t.adapter.getStatus();s.busy||s.backgroundBusy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t)}return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots){if(r.state!=="ready")continue;const n=r.adapter.getStatus();n.busy||n.backgroundBusy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s)}return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values())if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0||e.state==="ready"&&e.adapter.getStatus().busy)return!0;return!1}hasPendingOrBackgroundWork(){for(const e of this.slots.values()){if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0)return!0;if(e.state==="ready"){const t=e.adapter.getStatus();if(t.busy||t.backgroundBusy)return!0}}return!1}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const i=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(a){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),a}finally{n.startPromise=null}})();return n.startPromise=i,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&(this.onEventDone?.(t,e.sessionId),e.eventQueue.markAdapterDone(t))}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,i,a)=>{this.onEventState?.(r,n,i,a)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,i)=>{this.onQueueComposing?.(r,n,i)}};return new c(t,s)}}export{S as AdapterPool,p as PoolFullError};
|