grix-connector 1.0.2
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 +149 -0
- package/dist/adapter/acp/acp-adapter.js +13 -0
- package/dist/adapter/acp/index.js +1 -0
- package/dist/adapter/acp/usage-parser.js +1 -0
- package/dist/adapter/claude/activity-status-manager.js +1 -0
- package/dist/adapter/claude/channel-notification.js +1 -0
- package/dist/adapter/claude/claude-adapter.js +15 -0
- package/dist/adapter/claude/claude-bridge-server.js +1 -0
- package/dist/adapter/claude/claude-tools.js +1 -0
- package/dist/adapter/claude/claude-worker-client.js +1 -0
- package/dist/adapter/claude/index.js +1 -0
- package/dist/adapter/claude/interaction-protocol.js +1 -0
- package/dist/adapter/claude/mcp-http-launcher.js +2 -0
- package/dist/adapter/claude/model-list.js +1 -0
- package/dist/adapter/claude/protocol-contract.js +1 -0
- package/dist/adapter/claude/result-timeout.js +1 -0
- package/dist/adapter/claude/skill-scanner.js +2 -0
- package/dist/adapter/claude/usage-parser.js +3 -0
- package/dist/adapter/codewhale/codewhale-adapter.js +6 -0
- package/dist/adapter/codewhale/index.js +1 -0
- package/dist/adapter/codex/codex-bridge.js +10 -0
- package/dist/adapter/codex/codex-trust.js +8 -0
- package/dist/adapter/codex/index.js +1 -0
- package/dist/adapter/codex/usage-parser.js +1 -0
- package/dist/adapter/cursor/cursor-adapter.js +8 -0
- package/dist/adapter/cursor/index.js +1 -0
- package/dist/adapter/deepseek/deepseek-adapter.js +6 -0
- package/dist/adapter/deepseek/index.js +1 -0
- package/dist/adapter/index.js +1 -0
- package/dist/adapter/opencode/index.js +1 -0
- package/dist/adapter/opencode/opencode-adapter.js +8 -0
- package/dist/adapter/opencode/opencode-transport.js +5 -0
- package/dist/adapter/opencode/opencode-types.js +0 -0
- package/dist/adapter/openhuman/index.js +1 -0
- package/dist/adapter/openhuman/openhuman-adapter.js +7 -0
- package/dist/adapter/openhuman/openhuman-transport.js +1 -0
- package/dist/adapter/openhuman/openhuman-types.js +0 -0
- package/dist/adapter/pi/index.js +1 -0
- package/dist/adapter/pi/pi-adapter.js +10 -0
- package/dist/adapter/pi/pi-transport.js +4 -0
- package/dist/adapter/pi/pi-types.js +0 -0
- package/dist/adapter/pi/usage-parser.js +1 -0
- package/dist/adapter/qwen/index.js +1 -0
- package/dist/adapter/qwen/qwen-adapter.js +4 -0
- package/dist/adapter/types.js +1 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/process.js +2 -0
- package/dist/aibot/client.js +1 -0
- package/dist/aibot/index.js +1 -0
- package/dist/aibot/types.js +0 -0
- package/dist/bridge/adapter-pool.js +1 -0
- package/dist/bridge/bridge.js +10 -0
- package/dist/bridge/deferred-events.js +1 -0
- package/dist/bridge/event-queue.js +1 -0
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/respawn-manager.js +1 -0
- package/dist/bridge/revoke-handler.js +1 -0
- package/dist/bridge/runtime-config.js +1 -0
- package/dist/bridge/send-controller.js +1 -0
- package/dist/bridge/session-controller.js +9 -0
- package/dist/bridge/tool-card-utils.js +1 -0
- package/dist/core/access/allowlist-gate.js +1 -0
- package/dist/core/access/allowlist-store.js +1 -0
- package/dist/core/access/index.js +1 -0
- package/dist/core/aibot/client.js +1 -0
- package/dist/core/aibot/connection-handle.js +1 -0
- package/dist/core/aibot/connection-manager.js +1 -0
- package/dist/core/aibot/event-lifecycle-types.js +0 -0
- package/dist/core/aibot/index.js +1 -0
- package/dist/core/aibot/types.js +0 -0
- package/dist/core/config/index.js +1 -0
- package/dist/core/config/paths.js +1 -0
- package/dist/core/context/channel-context-resolution.js +1 -0
- package/dist/core/context/channel-context-store.js +1 -0
- package/dist/core/context/index.js +1 -0
- package/dist/core/context/transcript-channel-context.js +1 -0
- package/dist/core/file-ops/handler.js +1 -0
- package/dist/core/file-ops/list-files.js +1 -0
- package/dist/core/file-ops/types.js +0 -0
- package/dist/core/files/create-folder.js +1 -0
- package/dist/core/files/index.js +1 -0
- package/dist/core/files/list-files.js +1 -0
- package/dist/core/files/list-handler.js +1 -0
- package/dist/core/files/types.js +0 -0
- package/dist/core/files/utils.js +1 -0
- package/dist/core/hooks/hook-signal-store.js +2 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/log/bridge-event-log.js +2 -0
- package/dist/core/log/conversation-log.js +3 -0
- package/dist/core/log/index.js +1 -0
- package/dist/core/log/logger.js +6 -0
- package/dist/core/log/packet-log.js +2 -0
- package/dist/core/log/rotation.js +2 -0
- package/dist/core/mcp/event-tool-executor.js +1 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/internal-api-server.js +1 -0
- package/dist/core/mcp/tool-schemas.js +1 -0
- package/dist/core/mcp/tools.js +1 -0
- package/dist/core/persistence/active-event-store.js +1 -0
- package/dist/core/persistence/agent-global-config-store.js +1 -0
- package/dist/core/persistence/elicitation-store.js +1 -0
- package/dist/core/persistence/event-results-store.js +1 -0
- package/dist/core/persistence/permission-store.js +1 -0
- package/dist/core/persistence/question-store.js +1 -0
- package/dist/core/persistence/session-binding-store.js +1 -0
- package/dist/core/protocol/agent-api-media.js +1 -0
- package/dist/core/protocol/attachment-file.js +1 -0
- package/dist/core/protocol/index.js +1 -0
- package/dist/core/protocol/interaction-parser.js +1 -0
- package/dist/core/protocol/message-metadata.js +2 -0
- package/dist/core/protocol/message-reference.js +2 -0
- package/dist/core/protocol/payload-parser.js +11 -0
- package/dist/core/protocol/protocol-descriptor.js +1 -0
- package/dist/core/protocol/protocol-text.js +1 -0
- package/dist/core/provider-quota/index.js +1 -0
- package/dist/core/provider-quota/kiro.js +1 -0
- package/dist/core/provider-quota/providers.js +1 -0
- package/dist/core/provider-quota/types.js +0 -0
- package/dist/core/runtime/health.js +1 -0
- package/dist/core/runtime/index.js +1 -0
- package/dist/core/runtime/pidfile.js +2 -0
- package/dist/core/runtime/spawn.js +1 -0
- package/dist/core/text-segmentation/index.js +1 -0
- package/dist/core/text-segmentation/safe-markdown-stream-segmenter.js +6 -0
- package/dist/core/transport/index.js +1 -0
- package/dist/core/transport/json-rpc.js +3 -0
- package/dist/core/upgrade/npm-upgrader.js +2 -0
- package/dist/core/upgrade/upgrade-checker.js +1 -0
- package/dist/core/util/client-version.js +1 -0
- package/dist/core/util/codex-output-policy.js +1 -0
- package/dist/core/util/event-buffer.js +1 -0
- package/dist/core/util/index.js +1 -0
- package/dist/core/util/json-file.js +2 -0
- package/dist/core/util/normalize-string.js +1 -0
- package/dist/core/util/quoted-message-stream.js +3 -0
- package/dist/grix.js +28 -0
- package/dist/index.js +1 -0
- package/dist/log.js +3 -0
- package/dist/main.js +31 -0
- package/dist/manager.js +1 -0
- package/dist/mcp/acp-mcp-server.js +5 -0
- package/dist/mcp/stdio/server.js +10 -0
- package/dist/mcp/stream-http/config.js +1 -0
- package/dist/mcp/stream-http/connection-binding.js +1 -0
- package/dist/mcp/stream-http/event-tool-executor.js +1 -0
- package/dist/mcp/stream-http/gateway.js +1 -0
- package/dist/mcp/stream-http/index.js +1 -0
- package/dist/mcp/stream-http/security.js +1 -0
- package/dist/mcp/stream-http/session-manager.js +1 -0
- package/dist/mcp/stream-http/tool-executor.js +1 -0
- package/dist/mcp/stream-http/tool-registry.js +1 -0
- package/dist/mcp/stream-http/tool-schemas.js +1 -0
- package/dist/protocol/acp-client.js +1 -0
- package/dist/protocol/event-mapper.js +5 -0
- package/dist/protocol/index.js +1 -0
- package/dist/runtime/daemon-lock.js +2 -0
- package/dist/runtime/service-state.js +2 -0
- package/dist/scripts/approve-plan-hook.js +2 -0
- package/dist/scripts/elicitation-hook.js +6 -0
- package/dist/scripts/lib/read-stdin.js +1 -0
- package/dist/scripts/lifecycle-hook.js +2 -0
- package/dist/scripts/notification-hook.js +4 -0
- package/dist/scripts/permission-hook.js +5 -0
- package/dist/scripts/status-line-forwarder.js +2 -0
- package/dist/scripts/user-prompt-submit-hook.js +2 -0
- package/dist/service/platform-adapter.js +45 -0
- package/dist/service/process-control.js +1 -0
- package/dist/service/service-install-store.js +1 -0
- package/dist/service/service-manager.js +1 -0
- package/dist/service/service-paths.js +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/manager.js +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/json-rpc.js +3 -0
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/protocol.js +0 -0
- package/dist/types/session-state.js +0 -0
- package/dist/types/usage.js +0 -0
- package/openclaw-plugin/index.js +11271 -0
- package/openclaw-plugin/skills/grix-admin/SKILL.md +202 -0
- package/openclaw-plugin/skills/grix-admin/references/api-contract.md +210 -0
- package/openclaw-plugin/skills/grix-egg/SKILL.md +81 -0
- package/openclaw-plugin/skills/grix-egg/references/api-contract.md +40 -0
- package/openclaw-plugin/skills/grix-group/SKILL.md +164 -0
- package/openclaw-plugin/skills/grix-group/references/api-contract.md +97 -0
- package/openclaw-plugin/skills/grix-query/SKILL.md +247 -0
- package/openclaw-plugin/skills/grix-register/SKILL.md +86 -0
- package/openclaw-plugin/skills/grix-register/references/api-contract.md +76 -0
- package/openclaw-plugin/skills/grix-register/references/grix-concepts.md +26 -0
- package/openclaw-plugin/skills/grix-register/references/handoff-contract.md +24 -0
- package/openclaw-plugin/skills/grix-register/references/openclaw-setup.md +6 -0
- package/openclaw-plugin/skills/grix-register/references/user-replies.md +25 -0
- package/openclaw-plugin/skills/grix-register/scripts/grix_auth.ts +599 -0
- package/openclaw-plugin/skills/grix-update/SKILL.md +310 -0
- package/openclaw-plugin/skills/grix-update/references/cron-setup.md +56 -0
- package/openclaw-plugin/skills/grix-update/references/update-contract.md +149 -0
- package/openclaw-plugin/skills/message-send/SKILL.md +197 -0
- package/openclaw-plugin/skills/message-unsend/SKILL.md +186 -0
- package/openclaw-plugin/skills/message-unsend/flowchart.mermaid +27 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/SKILL.md +282 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/case-study-macpro.md +52 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/host-readiness.md +147 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/bench_ollama_embeddings.ts +326 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/set_openclaw_memory_model.ts +385 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/survey_host_readiness.ts +294 -0
- package/openclaw.plugin.json +24 -0
- package/package.json +114 -0
- package/scripts/install-guardian.mjs +30 -0
- package/scripts/install-guardian.sh +30 -0
- package/scripts/upgrade-guardian.sh +98 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CodeWhaleAdapter as r}from"./codewhale-adapter.js";export{r as CodeWhaleAdapter};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{createInterface as P}from"node:readline";import{EventEmitter as T}from"node:events";import{join as y}from"node:path";import{resolveCommandPath as L,spawnCommand as O,killProcessGroup as k}from"../../core/runtime/spawn.js";import{formatInboundMessageReferenceText as N}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";import{resolveClientVersion as j}from"../../core/util/client-version.js";import{isUserVisibleAgentMessagePhase as q}from"../../core/util/codex-output-policy.js";import{SessionBindingStore as F}from"../../core/persistence/session-binding-store.js";import{ensureCodexProjectTrusted as D,isCodexCommand as H}from"./codex-trust.js";import{scanSkills as z}from"../claude/skill-scanner.js";const U=60*1e3,W=600*1e3,J=20,B=8e3,G=[{id:"gpt-5.3-codex",displayName:"GPT-5.3 Codex",defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!0}],C=[{id:"default",displayName:"Default"},{id:"plan",displayName:"Plan"}];function X(a,e){const s=String(a??"").trim()||"codex",i=L(s,e);if(i!==s)return i;const t=process.platform==="win32"?[]:["/opt/homebrew/bin/codex","/usr/local/bin/codex","/usr/bin/codex"],n=process.platform!=="win32"?[]:[y(process.env.LOCALAPPDATA??"","npm","codex.cmd"),y(process.env.APPDATA??"","npm","codex.cmd"),y(process.env.LOCALAPPDATA??"","Programs","codex","codex.exe")],o=[...t,...n];for(const d of o)try{return require("node:fs").accessSync(d,require("node:fs").constants.X_OK),d}catch{continue}return r.warn("codex-adapter",`resolveCodexCommandPath: failed to resolve "${s}". envPath=${e?"provided":"missing (using process.env.PATH)"}. fallbacks tried: [${o.join(", ")}]`),s}class ge extends T{type="codex";config;callbacks;process=null;alive=!1;stopped=!1;bridgeStatus="starting";pendingRequests=new Map;requestId=0;threadId=null;initialized=!1;cwd;approvalPolicy;sandboxMode;model;collaborationMode;reasoningEffort;idleTimer=null;inFlightToolOps=0;pendingApprovals=new Map;composingTTL=12e4;composingRefreshInterval=3e4;composingTimer=null;composingTTLClear=null;needsHistoryInjection=!1;lastInjectedSessionId=null;bindingStore=null;aibotSessionId="";threadResumePending=!1;autoTrustProject="auto";codexHome;codexModelOptions=[];rateLimitSnapshot=null;currentThreadTokenUsage=null;currentModelContextWindow=null;constructor(e,s){super(),this.config=e,this.callbacks=s;const i=e.options??{};if(this.approvalPolicy=i.approvalPolicy??"never",this.sandboxMode=typeof i.sandboxMode=="string"&&i.sandboxMode.trim()&&i.sandboxMode.trim()!=="default"?i.sandboxMode.trim():void 0,this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof F?i.bindingStore:null,this.model=i.model,this.collaborationMode=i.collaborationMode,this.reasoningEffort=i.reasoningEffort,this.bindingStore&&this.aibotSessionId&&(this.model=this.model??this.bindingStore.getCodexModelId(this.aibotSessionId),this.collaborationMode=this.collaborationMode??this.bindingStore.getCodexModeId(this.aibotSessionId),this.reasoningEffort=this.reasoningEffort??this.bindingStore.getCodexReasoningEffort(this.aibotSessionId),this.sandboxMode=this.sandboxMode??this.bindingStore.getCodexSandboxMode(this.aibotSessionId)),this.autoTrustProject=typeof i.autoTrustProject=="boolean"?i.autoTrustProject:"auto",this.codexHome=typeof i.codexHome=="string"&&i.codexHome.trim()?i.codexHome.trim():void 0,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const t=this.bindingStore.getCodexThreadId(this.aibotSessionId);t&&(this.threadId=t,this.threadResumePending=!0)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){await this.ensureProjectTrusted(),await this.spawnCodex(),await this.initializeHandshake(),await this.notifyBindingReadyWithContext(),r.info("codex-adapter",`Ready (pid=${this.process?.pid})`)}async ensureProjectTrusted(){if(this.shouldAutoTrustProject())try{const e=await D(this.cwd,{codexHome:this.codexHome});e.changed&&r.info("codex-adapter",`Trusted Codex project ${this.cwd} in ${e.configPath}`)}catch(e){const s=e instanceof Error?e.message:String(e);throw r.error("codex-adapter",`Failed to trust Codex project ${this.cwd}: ${s}`),e}}shouldAutoTrustProject(){return this.autoTrustProject===!1?!1:this.autoTrustProject===!0?!0:H(this.config.command)}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}async notifyBindingReadyWithContext(){if(!(!this.aibotSessionId||!this.cwd))try{this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd,await this.buildToolbarContextResult("binding_ready"))}catch(e){r.warn("codex-adapter",`Failed to attach toolbar context to binding update: ${e instanceof Error?e.message:String(e)}`),this.notifyBindingReady()}}getEffortMeta(){const e=this.currentModelId(),s=this.getModelOption(e),i=s?.supportedReasoningEfforts??[];return i.length===0?{}:{available_efforts:i,reasoning_effort:this.reasoningEffort??s?.defaultReasoningEffort??null}}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.rejectAllPending("adapter stopped");const e=this.process;if(this.process=null,e?.pid&&typeof e.once=="function")k(e,"SIGTERM"),await Promise.race([new Promise(i=>{e.once("exit",()=>i(!0))}),new Promise(i=>{setTimeout(()=>i(!1),5e3)})])||k(e,"SIGKILL");else if(e)try{e.kill("SIGTERM")}catch{}}isAlive(){return this.alive}async createSession(e){if(this.initialized||await this.initializeHandshake(),this.threadId||await this.startNewThread(),!this.threadId)throw new Error("Failed to create session: thread ID is missing");return await this.notifyBindingReadyWithContext(),this.threadId}async resumeSession(e,s){await this.ensureThreadResumed()}async destroySession(e){this.threadId=null,this.threadResumePending=!1,this.persistThreadId(void 0)}sendPrompt(e){const s=new S(e.adapterSessionId);return this.runTurn(e,s).catch(i=>{s.emitError(i instanceof Error?i:new Error(String(i)))}),s}async cancel(e){if(this.threadId)try{await this.sendRequest("turn/interrupt",{threadId:this.threadId,turnId:this.currentTurnId??""},5e3)}catch{}}setPermissionHandler(e){this.permissionHandler=e}permissionHandler=null;async ping(e){if(!this.alive||!this.process)return!1;if(!this.process.pid){const s=!!(this.process.stdin&&!this.process.stdin.destroyed),i=!!(this.process.stdout&&!this.process.stdout.destroyed);return s&&i}if(!this.process.stdin||this.process.stdin.destroyed)return!!(this.process.stdout&&!this.process.stdout.destroyed);try{const s=++this.requestId,i={jsonrpc:"2.0",id:s,method:"ping",params:{}};return await new Promise(t=>{const n=setTimeout(()=>{this.pendingRequests.delete(String(s)),t(!1)},e);this.pendingRequests.set(String(s),{resolve:()=>t(!0),reject:()=>t(!1),timer:n}),this.process.stdin.write(`${JSON.stringify(i)}
|
|
2
|
+
`)})}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.currentTurnId!==null,sessions:this.threadId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.activeEventId=null}setModel(e){this.model=e,this.persistCodexContext(),r.info("codex-adapter",`Model set to: ${e}`)}setMode(e){const s=v(e);if(!s){r.info("codex-adapter",`Ignoring unsupported mode: ${e}`);return}this.collaborationMode=s,this.persistCodexContext(),r.info("codex-adapter",`Mode set to: ${s}`)}setReasoningEffort(e){this.reasoningEffort=e,this.persistCodexContext(),r.info("codex-adapter",`Reasoning effort set to: ${e}`)}setSandboxMode(e){this.sandboxMode=e,this.persistCodexContext(),r.info("codex-adapter",`Sandbox mode set to: ${e} (applied on next restart)`)}async threadCompact(){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/compact/start",{threadId:this.threadId},12e4)}async threadRollback(e){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/rollback",{threadId:this.threadId,numTurns:e},1e4)}getThreadId(){return this.threadId}getMcpConfig(){return null}getSupportedCommands(){return[{name:"compact",description:"Compact thread to reduce context size"},{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[default|plan]"},{name:"rollback",description:"Roll back thread turns",args:"[num_turns]"},{name:"interrupt",description:"Interrupt current turn"},{name:"rate_limits",description:"Show current rate limits"},{name:"status",description:"Show thread and session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,s,i){try{switch(e){case"compact":return this.threadId?(this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive()),{status:"ok",message:"Thread compacted",data:await this.threadCompact()}):{status:"failed",message:"No active thread"};case"model":{const t=s.trim();return t?this.getModelOptions().some(o=>o.id===t)?(this.setModel(t),{status:"ok",message:`Model set to ${t}`}):{status:"failed",message:`Unknown model: ${t}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const t=s.trim();if(t){const n=v(t);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${t}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:C}}}case"rollback":{if(!this.threadId)return{status:"failed",message:"No active thread"};const t=Math.max(1,parseInt(s.trim(),10)||1);return this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive()),await this.threadRollback(t),{status:"ok",message:`Rolled back ${t} turn(s)`}}case"interrupt":return this.threadId?(await this.cancel(this.threadId),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Turn interrupted"}):{status:"failed",message:"No active thread"};case"rate_limits":return{status:"ok",message:this.rateLimitSnapshot?`Primary: ${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}%, Secondary: ${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`:"Rate limits not available",data:{rateLimits:this.rateLimitSnapshot}};case"status":return{status:"ok",message:`Thread: ${this.threadId??"none"}, Model: ${this.currentModelId()}, Mode: ${this.collaborationMode??"default"}`,data:{threadId:this.threadId,model:this.currentModelId(),mode:this.collaborationMode??"default",cwd:this.cwd,alive:this.alive}};case"skills":{const t=z({mode:"codex",projectDir:this.cwd}),n=t.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""}: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
|
|
3
|
+
`):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}async handleLocalAction(e){const s=e.action_type??"",i=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${s} action_id=${e.action_id} session_id=${i.session_id??""}`),s){case"set_model":{const t=x(l(i.model_id),l(i.modelId),l(i.value));return t&&this.setModel(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const t=i.mode_id;return t&&this.setMode(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const t=x(l(i.reasoning_effort),l(i.reasoning_eff),l(i.effort));return t&&this.setReasoningEffort(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_sandbox_mode":{const t=x(l(i.sandbox_mode),l(i.sandboxMode),l(i.value));return t&&this.setSandboxMode(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("sandbox_mode_set",!1)),{handled:!0,kind:"set_sandbox_mode"}}case"thread_compact":{r.info("codex-adapter",`thread_compact start action_id=${e.action_id} threadId=${this.threadId}`);try{const t=await this.threadCompact();r.info("codex-adapter",`thread_compact done action_id=${e.action_id} result=${JSON.stringify(t)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(t){r.info("codex-adapter",`thread_compact failed action_id=${e.action_id} err=${t instanceof Error?t.message:String(t)}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,t instanceof Error?t.message:String(t))}return{handled:!0,kind:"thread_compact"}}case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{threadId:this.threadId,model:this.model,mode:this.collaborationMode,approvalPolicy:this.approvalPolicy,cwd:this.cwd,...await this.buildToolbarContextResult("context")}),{handled:!0,kind:"get_context"};case"exec_approve":case"file_approve":{const t=this.resolvePendingApproval(e);if(!t)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:s};const n=i.decision||"allow-once",o=V[n]??"accept";return this.sendApprovalDecision(t.requestId,o),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,approval_command_id:t.approvalCommandId,decision:n}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s}}case"exec_reject":case"file_reject":{const t=this.resolvePendingApproval(e);return t?(this.sendApprovalDecision(t.requestId,"deny"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,approval_command_id:t.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:s})}case"permission_approve":{const t=this.resolvePendingApproval(e);return!t||t.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:s}):(this.sendApprovalDecision(t.requestId,"accept"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,decision:"approve"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s})}case"permission_reject":{const t=this.resolvePendingApproval(e);return!t||t.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:s}):(this.sendApprovalDecision(t.requestId,"deny"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,decision:"deny"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(t){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,t instanceof Error?t.message:String(t))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const t=Number(i.numTurns??i.num_turns??1);if(!this.threadId)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"no_thread","No active thread"),{handled:!0,kind:"thread_rollback"};this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive());try{await this.threadRollback(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:t})}catch(n){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n instanceof Error?n.message:String(n))}return{handled:!0,kind:"thread_rollback"}}case"get_rate_limits":{const t=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return t?r.info("codex-adapter",`[rate-limits] responding: primary=${t.primary.usedPercent.toFixed(1)}% window=${t.primary.windowMinutes}min resetsAt=${t.primary.resetsAt} secondary=${t.secondary.usedPercent.toFixed(1)}% window=${t.secondary.windowMinutes}min resetsAt=${t.secondary.resetsAt}`):r.info("codex-adapter","[rate-limits] responding: no rateLimitSnapshot available"),r.debug("codex-adapter",`[cp-diagnose] get_rate_limits: threadId=${this.threadId??"-"} contextWindow=${JSON.stringify(n)} tokenUsage=${JSON.stringify(d)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok",{adapterType:"codex",available:t!==null,cached:!1,sampledAt:o,rateLimits:t,contextWindow:n,tokenUsage:d}),{handled:!0,kind:"get_rate_limits"}}default:return{handled:!1,kind:""}}}async buildToolbarContextResult(e,s=!0){s&&await this.refreshCodexModelOptions();const i=v(this.collaborationMode)??"default",t=this.currentModelId(),n=this.getModelOption(t),o=this.getModelOptions(),d=C,h=this.reasoningEffort??n?.defaultReasoningEffort??null,p=n?.supportedReasoningEfforts??[],u={outcome:e,session_context:{modelId:t,modeId:i,reasoningEffort:h,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:t,mode_id:i,currentModelId:t,currentModeId:i,available_models:o,available_modes:d,available_efforts:p,availableModels:o,availableModes:d,reasoning_effort:h,sandbox_mode:this.sandboxMode??null,models:o.map(f=>({modelId:f.id,name:f.displayName})),modes:d.map(f=>({id:f.id,name:f.displayName}))};return this.rateLimitSnapshot&&(u.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,u.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,u.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,u.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),u}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const s=this.getModelOptions();return s.find(i=>i.isDefault)?.id??s[0]?.id??""}getModelOption(e){const s=e.trim().toLowerCase();return this.getModelOptions().find(i=>i.id.trim().toLowerCase()===s)}getModelOptions(){const e=this.config.options??{},s=e.available_models??e.availableModels??e.models,i=ee(s),t=this.codexModelOptions,n=t.length>0?t:G,o=[],d=String(this.model??"").trim();if(d){const u=[...t,...i].find(f=>f.id.trim().toLowerCase()===d.toLowerCase());o.push({id:d,displayName:u?.displayName??_(d),defaultReasoningEffort:u?.defaultReasoningEffort??null,supportedReasoningEfforts:u?.supportedReasoningEfforts??[],isDefault:u?.isDefault??!1})}o.push(...i),o.push(...n);const h=[],p=new Set;for(const u of o){const f=u.id.trim(),b=f.toLowerCase();!f||p.has(b)||(p.add(b),h.push({id:f,displayName:u.displayName.trim()||f,defaultReasoningEffort:u.defaultReasoningEffort,supportedReasoningEfforts:[...u.supportedReasoningEfforts],isDefault:u.isDefault}))}return h}async refreshCodexModelOptions(){if(!(!this.initialized||!this.alive||!this.process?.stdin))try{const e=[];let s;do{const i=await this.sendRequest("model/list",{cursor:s??null,includeHidden:!1,limit:100},15e3),t=te(i);e.push(...t.models),s=t.nextCursor}while(s);e.length>0&&(this.codexModelOptions=se(e))}catch(e){r.warn("codex-adapter",`Failed to load Codex model list: ${e instanceof Error?e.message:String(e)}`)}}currentTurnId=null;activeEventId=null;activeSessionId=null;visibleAgentMessageIds=new Set;hiddenAgentMessageIds=new Set;agentMessagePhases=new Map;codexSequence=0;deliverInboundEvent(e){const s=N(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.currentTurnId&&s.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(s).catch(i=>{if(r.info("codex-adapter",`Steer failed, falling through: ${i}`),!this.currentTurnId){this.startNewTurn(e,s);return}const t=i instanceof Error?i.message:String(i);this.callbacks.sendEventResult(e.event_id,"failed",`failed to steer message into active turn: ${t}`)});return}if(this.currentTurnId){r.info("codex-adapter",`Event ${e.event_id} rejected: busy and empty content`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewTurn(e,s)}startNewTurn(e,s){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.codexSequence=0,this.startComposing();const i=this.threadId??"",t=new S(i),n={adapterSessionId:i,text:s,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(o=>({senderId:o.sender_id??"unknown",content:o.content})):void 0};this.runTurn(n,t,e.event_id,e.session_id).catch(o=>{r.error("codex-adapter",`Turn failed: ${o}`),this.callbacks.sendEventResult(e.event_id,"failed",o instanceof Error?o.message:String(o)),this.clearActive()}),this.resetIdleTimer(e.event_id)}deliverStopEvent(e,s){this.activeEventId===e&&(this.cancel(this.threadId??"").catch(()=>{}),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}async spawnCodex(){const e={...process.env,...this.config.env},s=typeof e.PATH=="string"?e.PATH:void 0;s||r.warn("codex-adapter",`spawnCodex: env.PATH is missing! process.env.PATH=${process.env.PATH?"set":"missing"}, config.env=${JSON.stringify(this.config.env)}`);const i=X(this.config.command||"codex",s),t=Y(this.config.args);this.sandboxMode&&t.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${i} ${t.join(" ")}`);try{this.process=O(i,t,{env:e,cwd:this.cwd}).process}catch(n){throw r.error("codex-adapter",`Codex spawn threw: ${n}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",n}this.process.on("error",n=>{r.error("codex-adapter",`Codex process spawn error: ${n}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("spawn failed: "+(n instanceof Error?n.message:String(n))),this.stopComposing(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed","Codex process spawn failed"),this.activeEventId=null),this.emit("exit",1)}),this.process.on("exit",n=>{r.info("codex-adapter",`Codex process exited (code=${n})`),this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("process exited"),this.stopComposing(),this.emit("exit",n)}),this.process.stderr?.on("data",n=>{const o=n.toString().trim();o&&r.info("codex-adapter",`[codex stderr] ${o}`)}),this.bindStdout(),this.alive=!0}bindStdout(){if(!this.process?.stdout)return;P({input:this.process.stdout}).on("line",s=>{if(!s.trim())return;let i;try{i=JSON.parse(s)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${s.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),i.id!=null){const t=String(i.id),n=this.pendingRequests.get(t);if(n){clearTimeout(n.timer),this.pendingRequests.delete(t),n.resolve(i);return}}this.handleNotification(i)})}handleNotification(e){const s=e.method,i=e.params??{};switch(this.captureContextWindowFromPayload(s,i),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&s&&s.startsWith("item/")&&!s.endsWith("/requestApproval")&&this.startComposing(),s){case"item/started":{const t=i.item,n=t?.id,o=t?.type,d=t?.phase;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps+=1),o==="agentMessage"&&n&&(typeof d=="string"&&d.trim()?this.agentMessagePhases.set(n,d.trim().toLowerCase()):this.agentMessagePhases.delete(n),q(d)?(this.visibleAgentMessageIds.add(n),this.hiddenAgentMessageIds.delete(n)):(this.hiddenAgentMessageIds.add(n),this.visibleAgentMessageIds.delete(n)));break}case"item/agentMessage/delta":{const t=i.delta,n=i.itemId;t&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const t=i.item,n=t?.id,o=t?.type;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps=Math.max(0,this.inFlightToolOps-1)),this.activeEventId&&o!=="agentMessage"&&this.emitCodexEvent("item/completed",e),o==="agentMessage"&&n&&(this.visibleAgentMessageIds.delete(n),this.hiddenAgentMessageIds.delete(n),this.agentMessagePhases.delete(n));break}case"turn/started":{const n=i.turn?.id;n&&(this.currentTurnId=n,r.info("codex-adapter",`Turn started: ${n}`),this.startComposing());break}case"item/fileChange/requestApproval":{this.handleApprovalRequest(e,"file");break}case"item/commandExecution/requestApproval":{this.handleApprovalRequest(e,"exec");break}case"item/permissions/requestApproval":{this.activeEventId&&this.emitCodexEvent("item/permissions/requestApproval",e),this.handleApprovalRequest(e,"permission");break}case"turn/completed":{this.handleTurnCompleted(i);break}case"error":{const t=i.message??JSON.stringify(i);r.error("codex-adapter",`Codex error: ${t}`);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(i),this.rateLimitSnapshot&&(r.info("codex-adapter",`Rate limits updated: primary=${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}% secondary=${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`),this.callbacks.onRateLimitsUpdated?.(this.rateLimitSnapshot));break}case"thread/tokenUsage/updated":{const t=i.threadId,n=i.tokenUsage,o=n?.last;(!t||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!t)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(i)}`),t&&n&&o&&(this.currentThreadTokenUsage={inputTokens:o.inputTokens??0,outputTokens:o.outputTokens??0,cacheReadInputTokens:o.cachedInputTokens??0,cacheCreationInputTokens:0},r.info("codex-adapter",`[cp-diagnose] token_usage raw thread=${t}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${t}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.callbacks.onContextWindowUpdated?.(this.buildContextWindowSnapshot()));break}default:break}}captureContextWindowFromPayload(e,s){const i=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],t=this.findNumericField(s,i);if(t==null||t<=0){const n=this.probeContextWindowFields(s,i);n.foundAnyCandidateField&&r.info("codex-adapter",`[cp-diagnose] context_window missing_or_invalid: method=${e??"-"} matchedFields=${n.matchedFields.join(",")||"-"} raw=${n.rawValues.join(",")||"-"} extracted=${String(t)}`);return}this.currentModelContextWindow!==t&&(this.currentModelContextWindow=t,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${t}`),this.callbacks.onContextWindowUpdated?.(this.buildContextWindowSnapshot()))}probeContextWindowFields(e,s){const i=[],t=[],n=o=>{if(!o||typeof o!="object")return;if(Array.isArray(o)){for(const h of o)n(h);return}const d=o;for(const h of s)Object.prototype.hasOwnProperty.call(d,h)&&(i.push(h),t.push(String(d[h])));for(const h of Object.values(d))n(h)};return n(e),{foundAnyCandidateField:i.length>0,matchedFields:i,rawValues:t}}findNumericField(e,s){if(!e||typeof e!="object")return null;const i=e;for(const t of s){const n=i[t];if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"){const o=Number(n);if(Number.isFinite(o))return o}}for(const t of Object.values(i)){if(Array.isArray(t)){for(const o of t){const d=this.findNumericField(o,s);if(d!=null)return d}continue}const n=this.findNumericField(t,s);if(n!=null)return n}return null}buildContextWindowSnapshot(){const e=this.currentModelContextWindow;if(!e||e<=0)return r.debug("codex-adapter",`[cp-diagnose] context_window snapshot skipped: invalid size=${String(e)}`),null;const s=this.currentThreadTokenUsage,i=s?s.inputTokens+s.outputTokens:null,t=i==null?null:Math.max(0,e-i),n=i==null?null:Math.min(100,i/e*100),o=i==null?null:Math.max(0,100-(n??0));return r.info("codex-adapter",`[cp-diagnose] context_window computed: threadId=${this.threadId??"-"} size=${e} usedTokens=${String(i)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:i,remainingTokens:t,usedPercentage:n,remainingPercentage:o,source:"codex:model_context_window"}}parseRateLimitSnapshot(e){try{const i=e.rateLimitsByLimitId?.codex??e.rateLimits??e,t=i.primary,n=i.secondary,o=i.credits,d=c=>{if(typeof c=="number"&&Number.isFinite(c))return c;if(typeof c=="string"){const m=Number(c);if(Number.isFinite(m))return m}return 0},h=c=>typeof c=="boolean"?c:typeof c=="string"?c.toLowerCase()==="true":!1,p=c=>{if(c==null)return null;if(typeof c=="string"){const m=c.trim();if(!m)return null;const I=Number(m);return Number.isFinite(I)&&I>1e9?new Date(I*1e3).toISOString():m}if(typeof c=="number"&&Number.isFinite(c)&&c>0){const m=c>1e12?c/1e3:c;return new Date(m*1e3).toISOString()}return null},u=c=>({usedPercent:d(c.used_percent??c.usedPercent),windowMinutes:d(c.window_minutes??c.windowDurationMins??c.windowMinutes),resetsAt:p(c.resets_at??c.resetsAt)}),f=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),b=()=>{const c=i.windows;if(!Array.isArray(c))return null;const m=c.map(g=>g&&typeof g=="object"?u(g):f()).filter(g=>g.windowMinutes>0);if(m.length===0)return null;const I=[...m].sort((g,$)=>g.windowMinutes-$.windowMinutes),A=I.find(g=>Math.abs(g.windowMinutes-300)<=30)??I[0],R=I.find(g=>Math.abs(g.windowMinutes-10080)<=120)??I[I.length-1];return{primary:A,secondary:R}},w=(t||n?{primary:t?u(t):f(),secondary:n?u(n):f()}:null)??b();return w?{primary:w.primary,secondary:w.secondary,credits:o?{hasCredits:h(o.has_credits??o.hasCredits),unlimited:h(o.unlimited),balance:o.balance==null?null:d(o.balance)}:{hasCredits:!1,unlimited:!1,balance:null}}:null}catch{return null}}handleApprovalRequest(e,s){const i=e.id;if(i==null)return;const t=e.method,n=`${i}`.trim(),o=`codex_${M(this.activeSessionId??"")}_${M(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(i,"accept");return}this.activeEventId&&this.emitCodexEvent(t,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:i,kind:s}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${s})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const s=e.params??{},i=x(s.approval_id,s.approvalId);if(i)return this.pendingApprovals.get(i)??null;const t=x(s.approval_command_id,s.approvalCommandId);if(t){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===t)return n}return null}sendApprovalDecision(e,s){if(e==null||!this.process?.stdin)return;const i={jsonrpc:"2.0",id:e,result:{decision:s}};this.process.stdin.write(`${JSON.stringify(i)}
|
|
4
|
+
`)}handleTurnCompleted(e){if(this.currentTurnId=null,this.stopComposing(),this.activeEventId){const s=this.activeEventId,i={event_id:s,session_id:this.activeSessionId??"",thread_id:this.threadId??void 0,codex_event_type:"codex",codex_method:"turn/completed",codex_sequence:++this.codexSequence,codex_payload:e,codex_at:new Date().toISOString()};this.callbacks.sendCodexEventReliable?(this.clearActive(),this.callbacks.sendCodexEventReliable(i).then(()=>{this.callbacks.sendEventResult(s,"responded")}).catch(t=>{r.error("codex-adapter",`sendCodexEventReliable failed event=${s}: ${t}`),this.callbacks.sendCodexEvent(i),this.callbacks.sendEventResult(s,"responded")})):(this.callbacks.sendCodexEvent(i),this.callbacks.sendEventResult(s,"responded"),this.clearActive())}}emitCodexEvent(e,s){this.activeEventId&&this.callbacks.sendCodexEvent({event_id:this.activeEventId,session_id:this.activeSessionId??"",thread_id:this.threadId??void 0,codex_event_type:"codex",codex_method:e,codex_sequence:++this.codexSequence,codex_payload:s,codex_at:new Date().toISOString()})}startComposing(){if(!this.activeSessionId||this.composingTimer)return;this.stopComposing();const e=this.activeSessionId,s={ttl_ms:this.composingTTL,...this.activeEventId?{ref_event_id:this.activeEventId}:{}};this.callbacks.sendSessionActivitySet(e,"composing",!0,s),this.composingTimer=setInterval(()=>{this.callbacks.sendSessionActivitySet(e,"composing",!0,s)},this.composingRefreshInterval),this.composingTTLClear=setTimeout(()=>{this.stopComposing()},this.composingTTL)}stopComposing(){this.composingTimer&&(clearInterval(this.composingTimer),this.composingTimer=null),this.composingTTLClear&&(clearTimeout(this.composingTTLClear),this.composingTTLClear=null),this.activeSessionId&&this.callbacks.sendSessionActivitySet(this.activeSessionId,"composing",!1)}async sendRequest(e,s,i=3e4){return new Promise((t,n)=>{if(!this.process?.stdin){n(new Error("Codex process not available"));return}const o=++this.requestId,d={jsonrpc:"2.0",id:o,method:e,params:s??{}},h=setTimeout(()=>{this.pendingRequests.delete(String(o)),n(new Error(`Request timeout: ${e}`))},i);this.pendingRequests.set(String(o),{resolve:u=>{u.error?n(new Error(`JSON-RPC error: ${u.error.message}`)):t(u.result)},reject:n,timer:h});const p=JSON.stringify(d);this.process.stdin.write(`${p}
|
|
5
|
+
`)})}sendNotification(e,s){if(!this.process?.stdin)return;const i={jsonrpc:"2.0",method:e,params:s??{}};this.process.stdin.write(`${JSON.stringify(i)}
|
|
6
|
+
`)}async initializeHandshake(){if(this.initialized)return;const e=j(),s=await this.sendRequest("initialize",{clientInfo:{name:"grix-connector",title:"Grix Connector",version:e},capabilities:{experimentalApi:!0}},15e3);r.info("codex-adapter",`Initialized: ${JSON.stringify(s)}`),this.sendNotification("initialized"),this.initialized=!0,await this.refreshCodexModelOptions(),this.fetchRateLimits()}fetchRateLimits(){Promise.resolve(this.sendRequest("account/rateLimits/read",void 0,1e4)).then(e=>{const s=this.parseRateLimitSnapshot(e);s&&(this.rateLimitSnapshot=s,r.info("codex-adapter",`Initial rate limits: primary=${s.primary.usedPercent.toFixed(1)}% secondary=${s.secondary.usedPercent.toFixed(1)}% credits=${s.credits.balance??"N/A"}`),this.callbacks.onRateLimitsUpdated?.(s))}).catch(e=>{r.warn("codex-adapter",`Failed to fetch rate limits: ${e instanceof Error?e.message:e}`)})}async runTurn(e,s,i,t){this.threadId||await this.createSession({cwd:this.cwd}),await this.ensureThreadResumed();const n=[];if(this.needsHistoryInjection&&t){this.needsHistoryInjection=!1;const h=await this.loadHistoryForInjection(t);for(const p of h)n.push(p);h.length>0&&r.info("codex-adapter",`Injected ${h.length} history items`)}if(e.contextMessages&&e.contextMessages.length>0){const h=e.contextMessages.map(p=>`[${p.senderId??"unknown"}]: ${p.content}`).join(`
|
|
7
|
+
`);n.push({type:"text",text:`Conversation context:
|
|
8
|
+
${h}
|
|
9
|
+
|
|
10
|
+
Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const o={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},d=Q(this.collaborationMode,this.model);d&&(o.collaborationMode=d),this.reasoningEffort&&(o.effort=this.reasoningEffort),await this.sendRequest("turn/start",o),s.emitDone({status:"completed"})}async ensureThreadResumed(){if(!(!this.threadId||!this.threadResumePending))try{const e=await this.sendRequest("thread/resume",{threadId:this.threadId,approvalPolicy:this.approvalPolicy,model:this.model,excludeTurns:!0,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}});this.updateModelFromThreadResult(e),this.threadResumePending=!1}catch{this.threadId=null,this.needsHistoryInjection=!0,await this.startNewThread(),this.threadResumePending=!1}}async startNewThread(){try{const e=await this.sendRequest("thread/start",{approvalPolicy:this.approvalPolicy,model:this.model,cwd:this.cwd,experimentalRawEvents:!1,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}}),s=K(e);if(!s)throw new Error("Codex thread id is missing in thread/start result");this.threadId=s,this.updateModelFromThreadResult(e),this.threadResumePending=!1,this.persistThreadId(s)}catch{throw new Error("Failed to start Codex thread")}}updateModelFromThreadResult(e){if(this.model)return;const s=Z(e);s&&(this.model=s,this.persistCodexContext())}persistThreadId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexThreadId(this.aibotSessionId,e)}persistCodexContext(){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexContext(this.aibotSessionId,{modelId:this.model,modeId:v(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const s=this.callbacks.getConversationLog();if(!s)return[];try{const i=await s.readHistory(e,J),t=[];for(const n of i){const o=(n.content??"").slice(0,B);n.direction==="inbound"?t.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&t.push({type:"assistant",text:o,text_elements:[]})}return t}catch(i){return r.error("codex-adapter",`Failed to load history: ${i}`),[]}}async steerTurn(e){if(!this.threadId||!this.currentTurnId)throw new Error("No active turn to steer");await this.sendRequest("turn/steer",{threadId:this.threadId,expectedTurnId:this.currentTurnId,input:[{type:"text",text:e,text_elements:[]}]})}resumeAfterApproval(e,s,i){if(!this.threadId)return;const t=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=x(s.session_id,s.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new S(o),h={adapterSessionId:o,text:""};this.activeEventId=t,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(h,d,t,n).catch(p=>{r.error("codex-adapter",`Post-approval turn failed: ${p}`),this.callbacks.sendEventResult(t,"failed",p instanceof Error?p.message:String(p)),this.clearActive()}),this.resetIdleTimer(t)}clearActive(){this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.currentTurnId=null,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.inFlightToolOps=0,this.clearIdleTimer(),this.pendingApprovals.clear()}resetIdleTimer(e){this.clearIdleTimer();const s=this.inFlightToolOps>0?W:U;this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(r.error("codex-adapter",`Agent idle for ${s/1e3}s, declaring stuck: ${e}`),this.callbacks.sendEventResult(e,"failed",`agent idle for ${s/1e3}s`),this.clearActive(),this.emit("stuck"))},s)}isLongRunningToolItemType(e){if(!e)return!1;const s=e.toLowerCase();return s==="commandexecution"||s==="command_execution"||s==="item/commandexecution"||s==="filechange"||s==="file_change"||s==="item/filechange"}shouldSuppressAgentMessageDeltaDuringToolRun(e){if(this.inFlightToolOps<=0)return!1;if(!e)return!0;const s=this.agentMessagePhases.get(e);return s==="final_answer"?!1:(r.info("codex-adapter",`Suppressing agentMessage delta during tool run item_id=${e} phase=${s??"unknown"}`),!0)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}rejectAllPending(e){for(const[,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error(`Request canceled: ${e}`));this.pendingRequests.clear(),this.pendingApprovals.clear()}}const V={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function Y(a){return a&&a.length>0?[...a]:["app-server"]}function l(a){if(typeof a!="string")return;const e=a.trim();return e||void 0}function K(a){if(!a||typeof a!="object")return null;const e=a,s=e.thread&&typeof e.thread=="object"?e.thread:void 0;return x(l(e.threadId),l(e.threadID),l(s?.id))??null}function Z(a){return!a||typeof a!="object"?null:l(a.model)??null}function v(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function Q(a,e){const s=v(a),i=l(e);if(!(!s||!i))return{mode:s,settings:{model:i}}}function ee(a){if(!Array.isArray(a))return[];const e=[];for(const s of a){if(typeof s=="string"){const o=s.trim();o&&e.push(ie(o,_(o)));continue}if(!s||typeof s!="object")continue;const i=s,t=x(l(i.id),l(i.model_id),l(i.modelId),l(i.value));if(!t)continue;const n=x(l(i.displayName),l(i.display_name),l(i.name),l(i.label))??_(t);e.push({id:t,displayName:n,defaultReasoningEffort:l(i.defaultReasoningEffort)??null,supportedReasoningEfforts:E(i.supportedReasoningEfforts),isDefault:i.isDefault===!0})}return e}function te(a){if(!a||typeof a!="object")return{models:[],nextCursor:null};const e=a,s=Array.isArray(e.data)?e.data:[],i=[];for(const t of s){if(!t||typeof t!="object")continue;const n=t;if(n.hidden===!0)continue;const o=x(l(n.id),l(n.model));if(!o)continue;const d=x(l(n.displayName),l(n.display_name),l(n.name))??_(o);i.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:E(n.supportedReasoningEfforts),isDefault:n.isDefault===!0})}return{models:i,nextCursor:l(e.nextCursor)??null}}function se(a){const e=[],s=new Set;for(const i of a){const t=i.id.trim(),n=t.toLowerCase();!t||s.has(n)||(s.add(n),e.push({id:t,displayName:i.displayName.trim()||t,defaultReasoningEffort:i.defaultReasoningEffort,supportedReasoningEfforts:[...i.supportedReasoningEfforts],isDefault:i.isDefault}))}return e}function ie(a,e){return{id:a,displayName:e,defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!1}}function E(a){if(!Array.isArray(a))return[];const e=[];for(const s of a){if(typeof s=="string"){const t=s.trim();t&&e.push(t);continue}if(!s||typeof s!="object")continue;const i=l(s.reasoningEffort);i&&e.push(i)}return e}function _(a){return a.split(/[-_\s]+/).filter(Boolean).map(e=>/^(gpt|codex|o\d+)$/i.test(e)?e.toUpperCase():e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function x(...a){for(const e of a)if(e!=null){if(typeof e=="string"&&!e.trim())continue;return e}}function M(a){return a.trim().replace(/[^a-zA-Z0-9._:-]+/g,"_")}class S extends T{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("codex-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{ge as CodexAdapter};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{mkdir as w,readFile as g,rename as v,writeFile as $}from"node:fs/promises";import{readFileSync as f}from"node:fs";import{dirname as _,isAbsolute as y,join as u}from"node:path";import{homedir as C}from"node:os";let m=Promise.resolve();function p(e){return e||process.env.CODEX_HOME||u(C(),".codex")}function H(e){return((e||"codex").split(/[\\/]/).pop()?.toLowerCase()||"codex").replace(/\.(exe|cmd|bat)$/i,"")==="codex"}async function I(e,o={}){if(!y(e))throw new Error(`Codex trusted project path must be absolute: ${e}`);const r=u(p(o.codexHome),"config.toml"),n=async()=>{const s=await b(r),t=E(s,e);return t===s?{configPath:r,changed:!1}:(await j(r,t),{configPath:r,changed:!0})},i=m.then(n,n);return m=i.catch(()=>{}),i}function E(e,o){const r=`[projects.${O(o)}]`,n=e.split(/\r?\n/),i=n.findIndex(t=>t.trim()===r);if(i===-1){const t=e.trimEnd(),c=`${r}
|
|
2
|
+
trust_level = "trusted"
|
|
3
|
+
`;return t?`${t}
|
|
4
|
+
|
|
5
|
+
${c}`:c}let s=n.length;for(let t=i+1;t<n.length;t+=1)if(/^\s*\[.*\]\s*$/.test(n[t])){s=t;break}for(let t=i+1;t<s;t+=1)if(/^\s*trust_level\s*=/.test(n[t]))return/^\s*trust_level\s*=\s*"trusted"\s*(?:#.*)?$/.test(n[t])?e:(n[t]='trust_level = "trusted"',h(n,e));return n.splice(i+1,0,'trust_level = "trusted"'),h(n,e)}async function b(e){try{return await g(e,"utf8")}catch(o){if(o.code==="ENOENT")return"";throw o}}async function j(e,o){await w(_(e),{recursive:!0});const r=`${e}.${process.pid}.${Date.now()}.tmp`;await $(r,o,{mode:384}),await v(r,e)}function O(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function h(e,o){const r=e.join(`
|
|
6
|
+
`);return o.endsWith(`
|
|
7
|
+
`)?r:r.replace(/\n$/,"")}function K(e){const o=p(e);let r;try{const i=u(o,"config.toml"),s=f(i,"utf8"),t=s.match(/^\s*model_provider\s*=\s*"([^"]+)"/m);if(t){const c=t[1],x=new RegExp('^\\s*base_url\\s*=\\s*"([^"]+)"',"m"),a=s.indexOf(`[model_providers.${c}]`);if(a>=0){const d=s.indexOf(`
|
|
8
|
+
[`,a+1),l=(d>=0?s.slice(a,d):s.slice(a)).match(x);l&&(r=l[1].trim())}}}catch{}let n;try{const i=u(o,"auth.json");n=(JSON.parse(f(i,"utf8")).OPENAI_API_KEY??"").trim()||void 0}catch{}return{baseUrl:r,apiKey:n}}export{I as ensureCodexProjectTrusted,H as isCodexCommand,K as readCodexProviderSettings,p as resolveCodexHome};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CodexAdapter as r}from"./codex-bridge.js";export{r as CodexAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createReadStream as T,readdirSync as g}from"node:fs";import{createInterface as _}from"node:readline";import p from"node:path";import x from"node:os";import{log as f}from"../../core/log/index.js";function k(){return{inputTokens:0,outputTokens:0,cacheReadInputTokens:0,cacheCreationInputTokens:0}}function m(n,e){n.inputTokens+=e.inputTokens,n.outputTokens+=e.outputTokens,n.cacheReadInputTokens+=e.cacheReadInputTokens,n.cacheCreationInputTokens+=e.cacheCreationInputTokens}function I(n){const e=p.join(x.homedir(),".codex"),s=[p.join(e,"sessions"),p.join(e,"archived_sessions")],t=`${n}.jsonl`;for(const r of s)try{const o=h(r,t);if(o)return o}catch{}return null}function h(n,e){let s;try{s=g(n,{withFileTypes:!0})}catch{return null}for(const t of s){const r=p.join(n,t.name);if(t.isDirectory()){const o=h(r,e);if(o)return o}else if(t.isFile()&&t.name.endsWith(e))return r}return null}async function S(n){const e=I(n);if(!e)return f.info("codex-usage-parser",`No rollout file found for thread ${n}`),null;f.info("codex-usage-parser",`Parsing session usage from ${e}`);const s=new Map,t=k();let r=0,o="unknown";try{const i=_({input:T(e,"utf8"),crlfDelay:1/0});for await(const a of i)if(a.trim())try{const u=JSON.parse(a);if(u.type==="turn_context"&&u.payload?.model&&(o=u.payload.model,r++),u.type==="event_msg"&&u.payload?.type==="token_count"){const c=u.payload?.info?.last_token_usage;if(!c)continue;const y=(c.input_tokens??0)-(c.cached_input_tokens??0),d={inputTokens:Math.max(0,y),outputTokens:c.output_tokens??0,cacheReadInputTokens:c.cached_input_tokens??0,cacheCreationInputTokens:0};m(t,d);let l=s.get(o);l||(l={turns:0,usage:k()},s.set(o,l)),l.turns++,m(l.usage,d)}}catch{}}catch(i){return f.error("codex-usage-parser",`Failed to parse rollout: ${i}`),null}return r===0?null:{models:[...s.entries()].map(([i,a])=>({model:i,turns:a.turns,total:a.usage})),total:t,turns:r}}export{S as parseCodexSessionUsage};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{createInterface as C}from"node:readline";import{EventEmitter as I}from"node:events";import{mkdirSync as A,readFileSync as R,writeFileSync as x}from"node:fs";import{join as v}from"node:path";import{GRIX_PATHS as T,log as c}from"../../core/log/index.js";import{InternalApiServer as L}from"../../core/mcp/internal-api-server.js";import{resolveCommandPath as B,spawnCommand as M,killProcessGroup as g}from"../../core/runtime/spawn.js";class $ extends I{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 y extends I{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,s){super(),this.config=e,this.callbacks=s}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new L,this.internalApi.setInvokeHandler(async(s,o)=>this.callbacks.agentInvoke(s,o)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},s=B(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),o=M(s,["agent","models"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}),t=await new Promise(n=>{let r="";o.process.stdout?.on("data",d=>{r+=String(d)}),o.process.once("close",()=>n(r))}),i=[];for(const n of t.split(`
|
|
2
|
+
`)){const r=n.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&i.push({id:d[1],displayName:d[2].trim()})}i.length>0&&(this._availableModels=i,c.info("cursor-adapter",`Loaded ${i.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())g(e.child,"SIGTERM");this.activeBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const s=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(s),this.sessionRuntime.set(s,{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}),s}async resumeSession(e,s){this.sessions.add(e);const o=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...o,cwd:typeof s?.cwd=="string"?s.cwd:o.cwd,modelId:typeof s?.modelId=="string"?s.modelId:o.modelId,modeId:typeof s?.modeId=="string"?s.modeId:o.modeId})}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e)}sendPrompt(e){const s=new $(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>s.emitError(new Error("adapter not running"))),s;const o=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},t=e.adapterSessionId,i=this.pendingBySession.get(t)??[];return i.push({event:o,request:e,handle:s}),this.pendingBySession.set(t,i),this.tryStartNext(t),s}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const s=this.pendingBySession.get(e);if(!s||s.length===0)return;const o=s.shift();s.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,s),o&&this.startJob(o,!1,0)}startJob(e,s,o){const{event:t,request:i,handle:n}=e,r=i.adapterSessionId,d=this.sessionRuntime.get(i.adapterSessionId)??{},h=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),h.trust!==!1&&u.push("--trust");const p=d.cwd||h.workspace;this.internalApi&&p&&(this.ensureCursorMcpConfig(p,this.internalApi.url),u.push("--approve-mcps")),p&&u.push("--workspace",p);const b=d.modelId||h.model;b&&u.push("--model",b);const k=d.modeId||h.mode;k&&u.push("--mode",k);const w=!!(d.cursorSessionId&&h.use_continue!==!1&&!s);w&&u.push("--continue"),u.push(this.buildPromptText(i));const S={...process.env,...this.config.env??{}},E=B(this.config.command,typeof S.PATH=="string"?S.PATH:void 0);c.info("cursor-adapter",`job start: event=${t.event_id} session=${t.session_id} retry=${o}`);let m;try{m=M(E,u,{cwd:p||process.cwd(),env:S,stdio:["ignore","pipe","pipe"]}).process}catch(a){this.finishActive(r,"failed",`spawn failed: ${a instanceof Error?a.message:String(a)}`);return}this.callbacks.sendEventAck(t.event_id,t.session_id),this.callbacks.sendSessionComposing(t.session_id,!0);const l={event:t,request:i,handle:n,child:m,seq:0,done:!1,stderr:"",timer:null,retryCount:o,usedContinue:w,workspace:p||process.cwd(),args:u};this.activeBySession.set(r,l);const _=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;_>0&&(l.timer=setTimeout(()=>{l.done||(g(m,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${_}ms`))},_)),C({input:m.stdout}).on("line",a=>this.handleStdoutLineForActive(l,a)),m.stderr?.on("data",a=>{const f=l.stderr+String(a??"");l.stderr=f.length>y.STDERR_MAX_CHARS?f.slice(-y.STDERR_MAX_CHARS):f}),m.once("error",a=>{this.finishActive(r,"failed",`spawn failed: ${String(a?.message??a)}`)}),m.once("close",a=>{if(!l.done)if((a??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(l)){l.timer&&clearTimeout(l.timer),this.activeBySession.delete(r),this.startJob(e,!0,l.retryCount+1);return}const f=l.stderr.trim()||`cursor agent exited with code ${a??-1}`;this.finishActive(r,"failed",f)}})}async cancel(e){const s=this.activeBySession.get(e);s&&(g(s.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const s=String(e.session_id??"").trim();if(!s){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,s),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const o={adapterSessionId:s,text:String(e.content??"")},t=new $(s),i=this.pendingBySession.get(s)??[];i.push({event:e,request:o,handle:t}),this.pendingBySession.set(s,i),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${s} depth=${i.length}`),this.tryStartNext(s)}deliverStopEvent(e,s){if(s){const o=this.activeBySession.get(s);if(o?.event.event_id===e){g(o.child,"SIGTERM"),this.finishActive(s,"canceled","stopped");return}const t=this.pendingBySession.get(s)??[],i=t.findIndex(n=>n.event.event_id===e);if(i>=0){const[n]=t.splice(i,1);t.length===0?this.pendingBySession.delete(s):this.pendingBySession.set(s,t),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[o,t]of this.activeBySession.entries())if(t.event.event_id===e){g(t.child,"SIGTERM"),this.finishActive(o,"canceled","stopped");return}for(const[o,t]of this.pendingBySession.entries()){const i=t.findIndex(r=>r.event.event_id===e);if(i<0)continue;const[n]=t.splice(i,1);t.length===0?this.pendingBySession.delete(o):this.pendingBySession.set(o,t),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 s=String(e.action_type??"").trim().toLowerCase(),o=e.params??{},t=String(o.session_id??"").trim(),i=e.action_id;switch(s){case"set_model":{const n=String(o.model_id??"").trim();if(!n||!t)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(t)??{};return this.sessionRuntime.set(t,{...r,modelId:n}),this.callbacks.sendLocalActionResult(i,"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(o.mode_id??"").trim();if(!n||!t)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(t)??{};return this.sessionRuntime.set(t,{...r,modeId:n}),this.callbacks.sendLocalActionResult(i,"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=t?this.sessionRuntime.get(t):void 0;return this.callbacks.sendLocalActionResult(i,"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(i,"ok",n),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const n=t?this.getUsageSnapshot(t):null;return n?this.callbacks.sendLocalActionResult(i,"ok",{adapterType:"cursor",available:!0,sampledAt:n.sampledAt,turns:n.turns,tokenUsage:n.total}):this.callbacks.sendLocalActionResult(i,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const n=String(o.verb??"").trim().toLowerCase();if(n==="restart"&&t)this.deliverStopEvent("__all__",t),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(n==="status"){const r=this.sessionRuntime.get(t),d=this.activeBySession.has(t);this.callbacks.sendLocalActionResult(i,"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(i,"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){return this.alive&&!this.stopped}getStatus(){let e=0;for(const s of this.pendingBySession.values())e+=s.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 s of this.activeBySession.values())s.event.event_id&&e.push(s.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){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){return!e.contextMessages||e.contextMessages.length===0?e.text:`${e.contextMessages.map(o=>`[${o.senderId}] ${o.content}`).join(`
|
|
3
|
+
`)}
|
|
4
|
+
|
|
5
|
+
[Current user message]
|
|
6
|
+
${e.text}`}handleStdoutLineForActive(e,s){if(e.done)return;const o=s.trim();if(!o)return;const{event:t}=e;let i;try{i=JSON.parse(o)}catch{this.callbacks.sendRawEventEnvelope?.(t.event_id,t.session_id,{type:"raw_text",text:o});return}this.callbacks.sendRawEventEnvelope?.(t.event_id,t.session_id,i);const n=this.extractAssistantText(i);if(n&&(e.seq+=1,this.callbacks.sendStreamChunk(t.event_id,t.session_id,n,e.seq,!1)),i?.type==="result"){const r=String(i?.session_id??"").trim();if(r){const h=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...h,cursorSessionId:r})}const d=i?.usage??{};this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(this.lastUsageBySession.get(e.request.adapterSessionId)?.turns??0)+1,total:{input:Number(d.inputTokens??0),output:Number(d.outputTokens??0),cacheRead:Number(d.cacheReadTokens??0),cacheWrite:Number(d.cacheWriteTokens??0)}})}}extractAssistantText(e){if(e?.type!=="assistant")return"";const s=e?.message?.content;return Array.isArray(s)?s.map(t=>t?.type==="text"?String(t?.text??""):"").filter(Boolean).join(""):""}finishActive(e,s,o){const t=this.activeBySession.get(e);t&&(t.done=!0,t.timer&&clearTimeout(t.timer),this.callbacks.sendSessionComposing(t.event.session_id,!1),c.info("cursor-adapter",`job finish: event=${t.event.event_id} session=${t.event.session_id} status=${s}${o?` msg=${o}`:""}`),this.callbacks.sendEventResult(t.event.event_id,s,o),s==="responded"?t.handle.emitDone({status:"completed"}):s==="canceled"?t.handle.emitDone({status:"canceled",error:o}):t.handle.emitDone({status:"failed",error:o}),this.emit("eventDone",t.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const s=e.stderr.toLowerCase();return s.includes("continue")&&(s.includes("not found")||s.includes("session not found")||s.includes("no previous session"))}ensureCursorMcpConfig(e,s){try{const o=v(e,".cursor"),t=v(o,"mcp.json");A(o,{recursive:!0});let i={};try{i=JSON.parse(R(t,"utf8"))}catch{i={}}const n={command:process.execPath,args:[v(process.cwd(),"dist","mcp","stdio","server.js"),"--handle-url",s]};(!i.mcpServers||typeof i.mcpServers!="object")&&(i.mcpServers={}),i.mcpServers.grix=n,x(t,`${JSON.stringify(i,null,2)}
|
|
7
|
+
`,"utf8"),this.recordCursorMcpRegistry(e,t,n),c.info("cursor-adapter",`MCP config synced: workspace=${e} file=${t}`)}catch(o){c.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(o)}`)}}recordCursorMcpRegistry(e,s,o){try{const t=v(T.data,"cursor"),i=v(t,"mcp-registry.json");A(t,{recursive:!0});let n={};try{n=JSON.parse(R(i,"utf8"))}catch{n={}}n[e]={mcp_path:s,server:o,updated_at:new Date().toISOString()},x(i,`${JSON.stringify(n,null,2)}
|
|
8
|
+
`,"utf8")}catch(t){c.warn("cursor-adapter",`Failed to record MCP registry: ${String(t)}`)}}}export{y as CursorAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CursorAdapter as e}from"./cursor-adapter.js";export{e as CursorAdapter};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{resolveCommandPath as f,spawnCommand as T}from"../../core/runtime/spawn.js";import{createInterface as w}from"node:readline";import{EventEmitter as I}from"node:events";import{formatInboundMessageReferenceText as _}from"../../core/protocol/message-reference.js";import{log as o}from"../../core/log/index.js";import{SessionBindingStore as E}from"../../core/persistence/session-binding-store.js";const S=120*1e3;class y extends I{type="deepseek";config;callbacks;alive=!1;stopped=!1;deepSeekSessionId=null;activeEventId=null;activeSessionId=null;chunkSeq=0;activeClientMsgId=null;idleTimer=null;activeProcess=null;composingTimer=null;composingTTLClear=null;composingTTL=12e4;composingRefreshInterval=3e4;bindingStore=null;aibotSessionId="";cwd;lastUsage=null;currentModel=null;constructor(e,s){super(),this.config=e,this.callbacks=s;const t=e.options??{};if(this.aibotSessionId=String(t.aibotSessionId??"").trim(),this.bindingStore=t.bindingStore instanceof E?t.bindingStore:null,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const i=this.bindingStore.getDeepSeekThreadId(this.aibotSessionId);i&&(this.deepSeekSessionId=i)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){this.alive=!0,this.notifyBindingReady(),o.info("deepseek-adapter","Ready (exec mode)")}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.killActiveProcess()}isAlive(){return this.alive}async createSession(e){const s=this.deepSeekSessionId??`ds-${Date.now()}`;return this.notifyBindingReady(),s}async resumeSession(e,s){}async destroySession(e){this.deepSeekSessionId=null,this.persistSessionId(void 0)}sendPrompt(e){const s=new k(e.adapterSessionId);return this.runMessage(e,s).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){this.killActiveProcess()}setPermissionHandler(e){}async ping(e){return this.alive}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.deepSeekSessionId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.killActiveProcess(),this.activeEventId=null}getMcpConfig(){return null}getUsageSnapshot(){return this.lastUsage}getSupportedCommands(){return[{name:"status",description:"Show session and working directory status"}]}async execCommand(e,s,t){return e==="status"?{status:"ok",message:`Session: ${this.deepSeekSessionId??"none"}, CWD: ${this.cwd}`,data:{sessionId:this.deepSeekSessionId,cwd:this.cwd,alive:this.alive}}:{status:"unsupported",message:`Unknown command: ${e}`}}async handleLocalAction(e){const s=e.action_type??"",t=e.params??{};switch(s){case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{sessionId:this.deepSeekSessionId,cwd:this.cwd,model:this.currentModel}),{handled:!0,kind:"get_context"};case"set_model":{const i=String(t.model_id??"").trim();return i?(this.currentModel=i,this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",modelId:i}),o.info("deepseek-adapter",`Model set to: ${i}`),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id is required"),{handled:!0,kind:"set_model"})}default:return{handled:!1,kind:""}}}deliverInboundEvent(e){const s=_(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.activeEventId){o.info("deepseek-adapter",`Event ${e.event_id}: rejected, busy with ${this.activeEventId}`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewMessage(e,s)}deliverStopEvent(e,s){this.activeEventId===e&&(this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}startNewMessage(e,s){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.chunkSeq=0,this.activeClientMsgId=`ds-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,this.startComposing();const t={adapterSessionId:this.deepSeekSessionId??"",text:s,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(n=>({senderId:n.sender_id??"unknown",content:n.content})):void 0},i=new k(this.deepSeekSessionId??"");this.runMessage(t,i,e.event_id,e.session_id).catch(n=>{o.error("deepseek-adapter",`Message failed: ${n}`),this.callbacks.sendEventResult(e.event_id,"failed",n instanceof Error?n.message:String(n)),this.clearActive()}),this.resetIdleTimer(e.event_id)}buildExecArgs(e){const s=["exec","--output-format","stream-json"];return this.currentModel&&s.push("--model",this.currentModel),this.deepSeekSessionId&&s.push("--resume",this.deepSeekSessionId),s.push("--",e),s}async runMessage(e,s,t,i){let n=e.text;e.contextMessages&&e.contextMessages.length>0&&(n=`Conversation context:
|
|
2
|
+
${e.contextMessages.map(c=>`[${c.senderId??"unknown"}]: ${c.content}`).join(`
|
|
3
|
+
`)}
|
|
4
|
+
|
|
5
|
+
Latest user message:
|
|
6
|
+
${n}`);const h=this.config.command||"codewhale",d=this.buildExecArgs(n),u={...process.env,...this.config.env},g=f(h,typeof u.PATH=="string"?u.PATH:void 0);o.info("deepseek-adapter",`Spawning: ${g} ${d.slice(0,5).join(" ")}...`);const r=T(g,d,{cwd:this.cwd,env:u}).process;return this.activeProcess=r,r.stderr?.on("data",c=>{const l=c.toString().trim();l&&o.info("deepseek-adapter",`[deepseek stderr] ${l}`)}),new Promise((c,l)=>{let p=!1,m="";const v=()=>{this.activeProcess=null};r.on("error",a=>{p||(p=!0,v(),l(a))}),r.on("exit",a=>{if(m.trim()&&this.handleOutputLine(m.trim(),t),m="",p){v();return}if(p=!0,v(),a!==0&&t&&this.activeEventId===t){l(new Error(`deepseek exec exited with code ${a}`));return}s.emitDone({status:"completed"}),c()}),w({input:r.stdout}).on("line",a=>{a.trim()&&this.handleOutputLine(a.trim(),t)}),r.stdin?.end()})}handleOutputLine(e,s){let t;try{t=JSON.parse(e)}catch{o.error("deepseek-adapter",`Invalid JSON: ${e.slice(0,200)}`);return}switch(t.type){case"content":{const i=t.content;i&&s&&this.activeEventId===s&&this.activeSessionId&&(this.chunkSeq++,this.callbacks.sendStreamChunk(s,this.activeSessionId,i,this.chunkSeq,!1,this.activeClientMsgId??void 0),this.startComposing(),this.resetIdleTimer(s));break}case"session_capture":{const i=t.content;i&&(this.deepSeekSessionId=i,this.persistSessionId(i),o.info("deepseek-adapter",`Session captured: ${i}`));break}case"metadata":{const i=t.meta;if(i){o.info("deepseek-adapter",`Metadata: model=${i.model}, tokens_in=${i.input_tokens}, tokens_out=${i.output_tokens}`);const n=Number(i.input_tokens??0),h=Number(i.output_tokens??0);if(n>0||h>0){const d=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(d?.turns??0)+1,total:{input:(d?.total.input??0)+n,output:(d?.total.output??0)+h}}}}break}case"tool_use":{const i=t.name,n=typeof t.input=="string"?t.input:JSON.stringify(t.input??{});i&&s&&this.activeEventId===s&&this.activeSessionId&&(o.info("deepseek-adapter",`Tool use: ${i}`),this.callbacks.sendToolUse(s,this.activeSessionId,i,n),this.resetIdleTimer(s));break}case"tool_result":{const i=t.name,n=t.output;s&&this.activeEventId===s&&this.activeSessionId&&(this.callbacks.sendToolResult(s,this.activeSessionId,i??"unknown",n??""),this.resetIdleTimer(s));break}case"done":{this.handleMessageCompleted(s);break}default:break}}handleMessageCompleted(e){if(this.stopComposing(),e&&this.activeEventId===e){const s=this.activeSessionId??"",t=this.activeClientMsgId??void 0;s&&(this.chunkSeq++,this.callbacks.sendStreamChunk(e,s,"",this.chunkSeq,!0,t)),this.callbacks.sendEventResult(e,"responded"),this.clearActive()}}killActiveProcess(){const e=this.activeProcess;if(this.activeProcess=null,e?.pid)try{e.kill("SIGTERM")}catch{}}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}persistSessionId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setDeepSeekThreadId(this.aibotSessionId,e)}startComposing(){if(!this.activeSessionId||this.composingTimer)return;this.stopComposing();const e=this.activeSessionId,s={ttl_ms:this.composingTTL};this.callbacks.sendSessionActivitySet(e,"composing",!0,s),this.composingTimer=setInterval(()=>{this.callbacks.sendSessionActivitySet(e,"composing",!0,s)},this.composingRefreshInterval),this.composingTTLClear=setTimeout(()=>{this.stopComposing()},this.composingTTL)}stopComposing(){this.composingTimer&&(clearInterval(this.composingTimer),this.composingTimer=null),this.composingTTLClear&&(clearTimeout(this.composingTTLClear),this.composingTTLClear=null),this.activeSessionId&&this.callbacks.sendSessionActivitySet(this.activeSessionId,"composing",!1)}resetIdleTimer(e){this.clearIdleTimer(),this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(o.error("deepseek-adapter",`Agent idle for ${S/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${S/1e3}s`),this.clearActive(),this.emit("stuck"))},S)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.chunkSeq=0,this.activeClientMsgId=null,this.clearIdleTimer(),e&&this.emit("eventDone",e)}}class k extends I{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("deepseek-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{y as DeepSeekAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DeepSeekAdapter as e}from"./deepseek-adapter.js";export{e as DeepSeekAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AgentEventType as n}from"./types.js";export{n as AgentEventType};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{OpenCodeAdapter as p}from"./opencode-adapter.js";export{p as OpenCodeAdapter};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{EventEmitter as g}from"node:events";import{resolveCommandPath as k,spawnCommand as w,killProcessGroup as I}from"../../core/runtime/spawn.js";import{OpenCodeTransport as b}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as T}from"../../core/protocol/index.js";class E extends g{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){n.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const y=200,_=2e3,$=12e4,C=6e5,S=3e4,P="127.0.0.1",A=0,O=1e3;class H extends g{type="opencode";config;callbacks;options;process=null;transport=new b;alive=!1;stopped=!1;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;composingSessionId=null;composingInterval=null;pendingPermissions=new Map;permissionHandler=null;constructor(e,t,s){super(),this.config=e,this.callbacks=t,this.options=s??{}}async start(){const e=this.options.hostname??P,t=this.options.port??A,s=await this.spawnAndWait(e,t),i=this.resolveCwd();await this.transport.connect(s,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${s})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.process){const e=this.process;try{I(e,"SIGTERM")}catch{}const t=setTimeout(()=>{try{I(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(t)),this.process=null}}isAlive(){return this.alive}async createSession(e){const t=e.cwd??this.resolveCwd(),s=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(s.id,{ocSessionId:s.id,cwd:t}),n.info("opencode-adapter",`Created OC session ${s.id} for cwd=${t}`),s.id}async resumeSession(e,t){const s=this.sessions.get(e);if(s?.ocSessionId)try{await this.transport.getSession(s.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, will create new on next prompt`),s.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e)}sendPrompt(e){const t=new E(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:t,session_id:s,content:i}=e;if(this.completedEvents.has(t)){n.info("opencode-adapter",`Dropping duplicate event ${t}`),this.callbacks.sendEventAck(t,s),this.callbacks.sendEventResult(t,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${t}: process not alive`),this.callbacks.sendEventAck(t,s),this.callbacks.sendEventResult(t,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==t&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${t}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${t} session=${s}`),this.callbacks.sendEventAck(t,s),this.startRun(t,s),this.startComposing(s),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(s,o).catch(a=>{n.error("opencode-adapter",`prompt_async failed: ${a}`),this.finishRun("failed",String(a))})}deliverStopEvent(e,t){this.activeRun&&this.activeRun.eventId===e&&(n.info("opencode-adapter",`stop: event=${e}`),this.transport.abortSession(this.activeRun.ocSessionId).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){return null}async handleLocalAction(e){const{action_type:t}=e;return t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,t){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${t}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:t});else{const s=this.sessions.get(e);s.cwd=t}}async spawnAndWait(e,t){return new Promise((s,i)=>{const o=this.config.command||"opencode",c=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${t}`],h={...process.env,...this.config.env},p={};if(this.options.model){const[r,...l]=this.options.model.split("/");r&&l.length>0&&(p.agents={coder:{model:l.join("/"),maxTokens:16e3}})}this.options.permissionPolicy==="fullAuto"&&(p.permission={edit:"allow",bash:"allow",webfetch:"allow",doom_loop:"allow",external_directory:"allow"}),Object.keys(p).length>0&&(h.OPENCODE_CONFIG_CONTENT=JSON.stringify(p));const f=this.resolveCwd(),m=k(o,typeof h.PATH=="string"?h.PATH:void 0);n.info("opencode-adapter",`Spawning: ${m} ${c.join(" ")} (cwd=${f})`),this.process=w(m,c,{env:h,cwd:f}).process;let v="",d=!1;const u=setTimeout(()=>{d||(d=!0,i(new Error(`opencode serve did not start after ${S/1e3}s`)))},S);this.process.stdout?.on("data",r=>{if(v+=r.toString(),!d)for(const l of v.split(`
|
|
2
|
+
`)){const R=l.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(u),d=!0,this.alive=!0,s(R[1]);return}}}),this.process.stderr?.on("data",r=>{const l=r.toString().trim();l&&n.info("opencode-adapter",`[stderr] ${l}`)}),this.process.on("error",r=>{n.error("opencode-adapter",`Spawn error: ${r.message}`),clearTimeout(u),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,i(r))}),this.process.on("exit",r=>{n.info("opencode-adapter",`Process exited (code=${r})`),clearTimeout(u),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),d?this.stopped||this.emit("exit",r??1):(d=!0,i(new Error(`opencode serve exited with code ${r}`)))})})}async ensureOcSession(e){const t=this.sessions.get(e);if(t?.ocSessionId)try{return await this.transport.getSession(t.ocSessionId),t.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, creating new`),t.ocSessionId=""}const s=t?.cwd??this.resolveCwd(),i=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:i.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${i.id} for aibot=${e}`),i.id}async sendOcPrompt(e,t){const s=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=s),await this.transport.sendPromptAsync(s,{parts:[{type:"text",text:t}]})}handleSseEvent(e){if(!this.stopped)switch(this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const t=e.part,s=e.delta;t.type==="text"?s?this.appendText(s):t.text&&this.appendText(t.text):t.type==="reasoning"?s&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,s):t.type==="tool"&&this.handleToolPartUpdate(t,s);break}case"message.updated":{if(!this.activeRun)break;const t=e.info;if(t.role==="assistant"){const s=t;s.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(s.error)}`)}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const t=e.error,s=t?typeof t=="object"&&"message"in t?t.message:JSON.stringify(t):"unknown session error";n.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{n.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:break}}handleToolPartUpdate(e,t){if(!this.activeRun)return;const s=e.state;switch(s.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(s.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=s.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=s.error??"unknown tool error";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,`Error: ${i}`);break}}}handlePermission(e){if(!this.activeRun)return;const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(a=>{n.warn("opencode-adapter",`Auto-approve failed: ${a}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(t,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(t,s,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const t=this.pendingPermissions.get(this.activeRun.eventId);if(!t)return{handled:!1,kind:""};const{permissionId:s,ocSessionId:i}=t,a=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,s,a).then(()=>{n.info("opencode-adapter",`Permission ${a}: ${s}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,t){this.activeRun={eventId:e,sessionId:t,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,t){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>O){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,o=s.clientMsgId;e==="failed"&&t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t);const a=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(s.eventId,e,t)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,o),this.callbacks.sendEventResult(s.eventId,e,t)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,o),this.callbacks.sendEventResult(s.eventId,e,t))};if(s.textBuffer){this.stopTextFlush();for(const c of T(s.textBuffer))s.chunkSeq++,this.callbacks.sendStreamChunk(s.eventId,s.sessionId,c,s.chunkSeq,!1,s.clientMsgId);s.textBuffer=""}a(),this.emit("eventDone",s.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer),this.activeRun=null}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=_){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},y))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of T(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){this.stopComposing(),this.composingSessionId=e,this.callbacks.sendSessionComposing(e,!0),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionComposing(this.composingSessionId,!0)},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionComposing(this.composingSessionId,!1),this.composingSessionId=null)}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)?C:$;this.idleTimer=setTimeout(()=>{n.error("opencode-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},e)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=(this.config.options??{}).cwd;return typeof e=="string"&&e?e:process.cwd()}buildPromptText(e){let t=e.text;return e.contextMessages&&e.contextMessages.length>0&&(t=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
3
|
+
`)+`
|
|
4
|
+
|
|
5
|
+
`+t),t}buildPromptTextFromEvent(e){let t=e.content||"";if(e.context_messages_json)try{const s=JSON.parse(e.context_messages_json);Array.isArray(s)&&s.length>0&&(t=s.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
|
|
6
|
+
`)+`
|
|
7
|
+
|
|
8
|
+
`+t)}catch{}return t}}export{H as OpenCodeAdapter};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{EventEmitter as u}from"node:events";import{log as h}from"../../core/log/index.js";const f=3e4,d=3e3,p=3e4;class w extends u{baseUrl="";directory="";abortController=null;closed=!1;sseConnected=!1;async connect(e,t){this.baseUrl=e,this.directory=t??"",await this.subscribeEvents(),h.info("opencode-transport",`connected to ${e}`)}async subscribeEvents(){if(this.closed)return;this.abortController=new AbortController;const e=new URL("/event",this.baseUrl);this.directory&&e.searchParams.set("directory",this.directory);try{const t=await fetch(e.toString(),{method:"GET",headers:{Accept:"text/event-stream"},signal:this.abortController.signal});if(!t.ok||!t.body)throw new Error(`SSE connect failed: ${t.status}`);this.sseConnected=!0;const s=t.body.getReader(),r=new TextDecoder;let n="";for(;!this.closed;){const{done:c,value:i}=await s.read();if(c)break;n+=r.decode(i,{stream:!0});const o=n.split(`
|
|
2
|
+
|
|
3
|
+
`);n=o.pop()??"";for(const a of o){const l=this.parseSseFrame(a);l&&this.emit("event",l)}}}catch(t){if(this.closed)return;const s=t instanceof Error?t.message:String(t);if(s.includes("abort"))return;h.warn("opencode-transport",`SSE error: ${s}, reconnecting...`),await this.reconnectSse()}finally{this.sseConnected=!1}}parseSseFrame(e){let t="",s=[];for(const r of e.split(`
|
|
4
|
+
`))r.startsWith("event:")?t=r.slice(6).trim():r.startsWith("data:")&&s.push(r.slice(5).trimStart());if(s.length===0)return null;try{const r=s.join(`
|
|
5
|
+
`),n=JSON.parse(r);return t&&n.type!==t&&(n.type=t),n}catch{return null}}async reconnectSse(){if(this.closed)return;const e=d+Math.random()*d;await new Promise(t=>setTimeout(t,Math.min(e,p))),this.closed||await this.subscribeEvents()}async request(e,t,s){if(this.closed)throw new Error("transport closed");const r=new URL(t,this.baseUrl);this.directory&&r.searchParams.set("directory",this.directory);const n=new AbortController,c=setTimeout(()=>n.abort(),f);try{const i={method:e,headers:{"Content-Type":"application/json"},signal:n.signal};s!==void 0&&(i.body=JSON.stringify(s));const o=await fetch(r.toString(),i);if(o.status===204)return;const a=await o.json();if(!o.ok)throw new Error(`REST ${e} ${t}: ${o.status} ${JSON.stringify(a)}`);return a}finally{clearTimeout(c)}}async createSession(e){return this.request("POST","/session",e??{})}async getSession(e){return this.request("GET",`/session/${e}`)}async deleteSession(e){await this.request("DELETE",`/session/${e}`)}async sendPromptAsync(e,t){await this.request("POST",`/session/${e}/prompt_async`,t)}async abortSession(e){await this.request("POST",`/session/${e}/abort`)}async respondPermission(e,t,s){await this.request("POST",`/session/${e}/permissions/${t}`,{response:s})}async listProviders(){return(await this.request("GET","/config/providers"))?.providers??[]}async healthCheck(){try{return(await fetch(new URL("/session",this.baseUrl).toString(),{method:"GET",headers:{"Content-Type":"application/json"},signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,this.removeAllListeners()}get isConnected(){return this.sseConnected&&!this.closed}}export{w as OpenCodeTransport};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{OpenHumanAdapter as r}from"./openhuman-adapter.js";export{r as OpenHumanAdapter};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{EventEmitter as u}from"node:events";import{homedir as d}from"node:os";import{join as v}from"node:path";import{resolveCommandPath as R,spawnCommand as I,killProcessGroup as p}from"../../core/runtime/spawn.js";import{OpenHumanTransport as g,readBearerToken as T}from"./openhuman-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as k}from"../../core/protocol/index.js";class _ extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitError(t){if(this.listenerCount("error")===0){n.warn("openhuman-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const x=200,S=2e3,b=12e4,w=500,f=3e4,E=7788,$="127.0.0.1";class q extends u{type="openhuman";config;callbacks;options;process=null;transport=new g;alive=!1;stopped=!1;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;composingSessionId=null;composingInterval=null;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`}async start(){const t=this.options.host??$,e=this.options.port??E;this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),i=await T(s),a=`http://${t}:${e}`;if(await this.transport.connect(a,i),this.transport.on("event",o=>this.handleSocketEvent(o)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),n.info("openhuman-adapter","Session token stored")}catch(o){n.warn("openhuman-adapter",`Failed to store session token: ${o}`)}this.transport.socketId&&(this.clientId=this.transport.socketId),n.info("openhuman-adapter",`Ready (pid=${this.process?.pid}, clientId=${this.clientId})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.process){const t=this.process;try{p(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{p(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){return this.clientId}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new _(t.adapterSessionId),s=this.buildPromptText(t);return this.doWebChat(t.adapterSessionId,s).then(i=>{this.activeRun&&(this.activeRun.requestId=i.request_id)}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){if(this.activeRun)try{await this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId})}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,a=this.buildPromptTextFromEvent(t);if(this.completedEvents.has(e)){n.info("openhuman-adapter",`Dropping duplicate event ${e}`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"responded");return}if(!this.alive){n.warn("openhuman-adapter",`Dropping event ${e}: process not alive`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==e&&(n.info("openhuman-adapter",`steer: ${this.activeRun.eventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("openhuman-adapter",`prompt: event=${e} session=${s}`),this.callbacks.sendEventAck(e,s),this.startRun(e,s,s),this.startComposing(s,e),this.resetIdleTimer(),this.doWebChat(s,a).then(o=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=o.request_id,o.accepted||(n.warn("openhuman-adapter",`web_chat not accepted: ${o.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(o=>{n.error("openhuman-adapter",`web_chat failed: ${o}`),this.finishRun("failed",String(o))})}deliverStopEvent(t,e){this.activeRun&&this.activeRun.eventId===t&&(n.info("openhuman-adapter",`stop: event=${t}`),this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId}).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(t){}async ping(t){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.activeRun?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){return null}spawnProcess(t,e){const s=this.config.command||"openhuman-core",i=R(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),a=this.config.args??[],c=a.includes("run")||a.includes("serve")?a:["run","--host",t,"--port",String(e),...a],h={...process.env,...this.config.env};n.info("openhuman-adapter",`Spawning: ${i} ${c.join(" ")}`);try{this.process=I(i,c,{env:h,cwd:this.resolveCwd()}).process}catch(r){throw n.error("openhuman-adapter",`Spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{n.error("openhuman-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{n.info("openhuman-adapter",`Process exited (code=${r})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const l=r.toString().trim();l&&n.info("openhuman-adapter",`[stderr] ${l}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,i=Date.now()+f;for(;Date.now()<i;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(a=>setTimeout(a,w))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${f/1e3}s`)}async doWebChat(t,e){return this.transport.webChat({client_id:this.clientId,thread_id:t,message:e})}handleSocketEvent(t){if(this.stopped||!this.activeRun||t.request_id&&this.activeRun.requestId&&t.request_id!==this.activeRun.requestId)return;this.resetIdleTimer();const e=t.event;switch(e){case"text_delta":{t.delta&&t.delta_kind==="text"&&this.appendText(t.delta);break}case"thinking_delta":{t.delta&&t.delta_kind==="thinking"&&this.activeRun&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t.delta);break}case"tool_call":{if(this.flushTextBuffer(),this.activeRun&&t.tool_name){const s=typeof t.args=="string"?t.args:JSON.stringify(t.args??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"tool_result":{if(this.activeRun&&t.tool_name){const s=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"chat_done":{n.info("openhuman-adapter",`chat_done request=${t.request_id}`),this.activeRun&&this.activeRun.textBuffer.length===0&&t.full_response&&(this.activeRun.textBuffer=t.full_response),this.flushTextBuffer(),this.finishRun("responded");break}case"chat_error":{if(n.error("openhuman-adapter",`chat_error: ${t.error_type} ${t.message}`),this.flushTextBuffer(),this.activeRun){const s=t.message??t.error_type??"unknown error";this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,s)}this.finishRun("failed",t.message);break}case"chat_segment":case"inference_start":case"iteration_start":case"chat_accepted":break;default:{n.debug("openhuman-adapter",`Unhandled event: ${e}`);break}}}startRun(t,e,s){this.activeRun={eventId:t,sessionId:e,requestId:"",threadId:s,chunkSeq:0,clientMsgId:`oh_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(t,e){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>1e3){const o=this.completedEvents.values();for(let h=0;h<500;h++)o.next();const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,a=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,a).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),this.callbacks.sendEventResult(s.eventId,t,e))}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer),this.activeRun=null}appendText(t){if(this.activeRun){if(this.activeRun.textBuffer+=t,this.activeRun.textBuffer.length>=S){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},x))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of k(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,t,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionComposing(t,!0),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionComposing(this.composingSessionId,!0)},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionComposing(this.composingSessionId,!1),this.composingSessionId=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{n.error("openhuman-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},b))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,d()):v(d(),".openhuman")}resolveCwd(){const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}buildPromptText(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
2
|
+
`)+`
|
|
3
|
+
|
|
4
|
+
`+e),e}buildPromptTextFromEvent(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(a=>`[context] ${a.sender_id??"unknown"}: ${a.content}`).join(`
|
|
5
|
+
`)+`
|
|
6
|
+
|
|
7
|
+
`+e)}catch{}return e}}export{q as OpenHumanAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EventEmitter as h}from"node:events";import{readFile as d}from"node:fs/promises";import{join as u}from"node:path";import{io as p}from"socket.io-client";import{log as a}from"../../core/log/index.js";const m=3e4,_=1e4,k=500,l=15e3;class b extends h{socket=null;rpcUrl="";bearerToken="";nextRpcId=1;closed=!1;async connect(t,o){this.bearerToken=o,this.rpcUrl=`${t}/rpc`,this.socket=p(t,{auth:{token:o},transports:["websocket"],reconnection:!0,reconnectionAttempts:10,reconnectionDelay:1e3,reconnectionDelayMax:3e4}),this.socket.on("connect",()=>{a.info("openhuman-transport",`Socket.io connected sid=${this.socket?.id}`)}),this.socket.on("disconnect",e=>{a.warn("openhuman-transport",`Socket.io disconnected: ${e}`)}),this.socket.on("connect_error",e=>{a.warn("openhuman-transport",`Socket.io connect error: ${e.message}`)}),this.registerEventHandlers(),await new Promise((e,s)=>{if(this.socket.connected){e();return}const n=setTimeout(()=>s(new Error("Socket.io connection timeout")),_);this.socket.once("connect",()=>{clearTimeout(n),e()}),this.socket.once("connect_error",i=>{clearTimeout(n),s(i)})})}registerEventHandlers(){if(!this.socket)return;const t=["inference_start","iteration_start","text_delta","thinking_delta","tool_args_delta","tool_call","tool_result","subagent_spawned","subagent_completed","subagent_failed","chat_done","chat_error","chat_accepted","chat_segment","task_board_updated"];for(const o of t)this.socket.on(o,e=>{this.emit("event",e)})}async call(t,o){if(this.closed)throw new Error("transport closed");const s={jsonrpc:"2.0",id:this.nextRpcId++,method:t,params:o},n=new AbortController,i=setTimeout(()=>n.abort(),m);try{const r=await(await fetch(this.rpcUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.bearerToken}`},body:JSON.stringify(s),signal:n.signal})).json();if(r.error)throw new Error(`RPC ${t}: ${r.error.message} (code=${r.error.code})`);if(!r.result?.result)throw new Error(`RPC ${t}: missing result payload`);return r.result.result}finally{clearTimeout(i)}}async webChat(t){return this.call("openhuman.channel_web_chat",t)}async webCancel(t){await this.call("openhuman.channel_web_cancel",t)}async storeSession(t){await this.call("openhuman.auth_store_session",t)}async healthCheck(){try{return(await fetch(this.rpcUrl.replace("/rpc","/health"),{method:"GET",signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null),this.removeAllListeners()}get socketId(){return this.socket?.id}}async function S(c){const t=u(c,"core.token"),o=Date.now()+l;for(;Date.now()<o;){try{const e=(await d(t,"utf-8")).trim();if(e)return e}catch{}await new Promise(e=>setTimeout(e,k))}throw new Error(`core.token not found in ${c} after ${l/1e3}s`)}export{b as OpenHumanTransport,S as readBearerToken};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PiAdapter as o}from"./pi-adapter.js";export{o as PiAdapter};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{EventEmitter as p}from"node:events";import{resolveCommandPath as f,spawnCommand as v,killProcessGroup as u}from"../../core/runtime/spawn.js";import{PiTransport as g}from"./pi-transport.js";import{log as a}from"../../core/log/index.js";import{scanSkills as S}from"../claude/skill-scanner.js";import{SessionBindingStore as I}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as x}from"../../core/protocol/index.js";class T extends p{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){a.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const b=12e4,k=500,E=2e3;class m extends p{type="pi";config;callbacks;process=null;transport=new g;alive=!1;stopped=!1;piSessionPath=null;activeEventId=null;activeSessionId=null;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;idleTimer=null;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;constructor(t,e){super(),this.config=t,this.callbacks=e;const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.bindingStore=s.bindingStore instanceof I?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){this.spawnPi(),await this.ensureSessionReady(),a.info("pi-adapter",`Ready (pid=${this.process?.pid})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.process){const t=this.process;try{u(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{u(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return a.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}async destroySession(t){this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new T(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);this.isStreaming?(this.activeEventId&&this.activeEventId!==e&&(a.info("pi-adapter",`steer: cancel ${this.activeEventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeEventId,"canceled","steered to new event")),this.activeEventId=e,this.activeSessionId=s,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(r=>{a.error("pi-adapter",`steer failed: ${r}`),this.callbacks.sendEventResult(e,"failed",String(r))})):(a.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(r=>{r.success||(a.error("pi-adapter",`prompt rejected: ${r.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",r.error),this.clearActive())}).catch(r=>{a.error("pi-adapter",`prompt error: ${r}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(r)),this.clearActive()}))}deliverStopEvent(t,e){if(this.activeEventId===t){a.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const{provider:s,modelId:i}=t.params??{};if(s&&i)try{return await this.transport.send("set_model",{provider:s,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:s,modelId:i}),{handled:!0,kind:"set_model"}}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}return{handled:!1,kind:""}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId=null}getMcpConfig(){return null}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const o=i.slice(0,c),d=i.slice(c+1);if(!d)return{status:"failed",message:"Format: provider:model_id"};const l=await this.transport.send("set_model",{provider:o,modelId:d});return l?.success?{status:"ok",message:`Model set to ${o}:${d}`}:{status:"failed",message:`Failed to set model: ${l?.error??"unknown error"}`}}const r=(await this.transport.send("get_available_models"))?.data?.models;return r&&r.length>0?{status:"ok",message:`Available models:
|
|
2
|
+
${r.map(o=>` ${o.provider??"unknown"}:${o.id} (${o.name??o.id})`).join(`
|
|
3
|
+
`)}`,data:r}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=S({mode:"pi"}),n=i.map(r=>`- ${r.name}${r.trigger?` (${r.trigger})`:""} [${r.source}]: ${r.description}`);return{status:"ok",message:n.length>0?n.join(`
|
|
4
|
+
`):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}spawnPi(){const t=this.config.command||"pi",e=f(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(o=>o.startsWith("--mode"))?s:["--mode","rpc",...s],r={...process.env,...this.config.env},c=this.resolveCwd();a.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{this.process=v(e,n,{env:r,cwd:c}).process}catch(o){throw a.error("pi-adapter",`PI spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{a.error("pi-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${o.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{a.info("pi-adapter",`PI process exited (code=${o})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${o})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const d=o.toString().trim();d&&a.info("pi-adapter",`[pi stderr] ${d}`)}),this.transport.on("event",o=>this.handlePiEvent(o)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const r=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(r,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,r=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(r,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"&&this.activeEventId&&this.activeSessionId){const n=s.reason??"stream error";this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,String(n),this.nextStreamSeq(),this.activeClientMsgId??void 0)}this.scheduleDoneGuard()}break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=typeof s.args=="object"&&s.args!==null?JSON.stringify(s.args):String(s.args??"");this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t,i=_(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{if(a.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.activeEventId){const s=this.activeEventId,i=this.activeSessionId??"",n=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(s,i,n)}else this.clearActive();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(a.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=E){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},k))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of x(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
|
|
5
|
+
`)+`
|
|
6
|
+
|
|
7
|
+
`+e)}catch{}return e}buildPromptTextFromRequest(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
8
|
+
`)+`
|
|
9
|
+
|
|
10
|
+
`+e),e}clearActive(){this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear()}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}sendFinalStreamChunk(t,e){this.callbacks.sendStreamChunk(t,e,"",this.nextStreamSeq(),!0,this.activeClientMsgId??void 0)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,i,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{a.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed event=${t}`))}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}a.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{if(this.doneGuardTimer=null,!this.activeEventId||!this.isStreaming)return;a.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer();const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(t,e,s)},m.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{a.error("pi-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed","idle timeout"),this.clearActive(),this.emit("exit",-1)},b))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&a.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}function _(h){if(!h||typeof h!="object")return"";const t=h.content;if(!Array.isArray(t))return"";const e=[];for(const s of t)if(s&&typeof s=="object"){const i=s;i.type==="text"&&typeof i.text=="string"&&e.push(i.text)}return e.join("")}export{m as PiAdapter};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{EventEmitter as a}from"node:events";import{log as s}from"../../core/log/index.js";const h=3e4;class u extends a{stdin=null;nextId=1;pending=new Map;buf="";closed=!1;bind(i,e){this.stdin=i,e.on("data",t=>{this.buf+=t.toString("utf-8"),this.drain()}),e.on("error",t=>{s.error("pi-transport",`stdout error: ${t.message}`)}),i.on("error",t=>{s.error("pi-transport",`stdin error: ${t.message}`)})}send(i,e){return new Promise((t,n)=>{if(this.closed||!this.stdin){n(new Error("transport closed"));return}const r=this.nextId++,p={id:r,type:i,...e},l=JSON.stringify(p)+`
|
|
2
|
+
`,o=setTimeout(()=>{this.pending.delete(r),n(new Error(`command timeout: ${i} (id=${r})`))},h);this.pending.set(r,{resolve:t,reject:n,timer:o}),this.stdin.write(l,d=>{d&&(clearTimeout(o),this.pending.delete(r),n(d))})})}sendNoWait(i){if(this.closed||!this.stdin)return;const e=JSON.stringify(i)+`
|
|
3
|
+
`;this.stdin.write(e,t=>{t&&s.error("pi-transport",`sendNoWait write error: ${t.message}`)})}close(){this.closed=!0;for(const[i,e]of this.pending)clearTimeout(e.timer),e.reject(new Error("transport closed"));this.pending.clear(),this.removeAllListeners()}drain(){let i;for(;(i=this.buf.indexOf(`
|
|
4
|
+
`))!==-1;){const e=this.buf.substring(0,i);this.buf=this.buf.substring(i+1),e.length!==0&&this.handleLine(e)}}handleLine(i){let e;try{e=JSON.parse(i)}catch{s.error("pi-transport",`invalid JSON: ${i.slice(0,200)}`);return}if("type"in e&&e.type==="response"&&"id"in e&&typeof e.id=="number"){const t=e,n=this.pending.get(t.id);n?(clearTimeout(n.timer),this.pending.delete(t.id),n.resolve(t)):s.info("pi-transport",`[WARN] response for unknown id=${t.id} cmd=${t.command}`)}else{const t=e;s.info("pi-transport",`event: type=${t.type}`),this.emit("event",t)}}}export{u as PiTransport};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createReadStream as m}from"node:fs";import{createInterface as T}from"node:readline";import{log as i}from"../../core/log/index.js";function g(){return{inputTokens:0,outputTokens:0,cacheReadInputTokens:0,cacheCreationInputTokens:0}}function k(e,n){e.inputTokens+=n.inputTokens,e.outputTokens+=n.outputTokens,e.cacheReadInputTokens+=n.cacheReadInputTokens,e.cacheCreationInputTokens+=n.cacheCreationInputTokens}async function R(e){i.info("pi-usage-parser",`Parsing session usage from ${e}`);const n=new Map,c=g();let a=0;try{const t=T({input:m(e,"utf8"),crlfDelay:1/0});for await(const o of t)if(o.trim())try{const p=JSON.parse(o);if(p.type!=="message")continue;const s=p.message;if(!s||s.role!=="assistant"||!s.usage)continue;const u=s.usage,f=s.model??"unknown";a++;const l={inputTokens:u.input??0,outputTokens:u.output??0,cacheReadInputTokens:u.cacheRead??0,cacheCreationInputTokens:u.cacheWrite??0};k(c,l);let r=n.get(f);r||(r={turns:0,usage:g()},n.set(f,r)),r.turns++,k(r.usage,l)}catch{}}catch(t){return t.code==="ENOENT"?(i.info("pi-usage-parser",`Session file not found: ${e}`),null):(i.error("pi-usage-parser",`Failed to parse session: ${t}`),null)}return a===0?null:{models:[...n.entries()].map(([t,o])=>({model:t,turns:o.turns,total:o.usage})),total:c,turns:a}}export{R as parsePiSessionUsage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{QwenAdapter as e}from"./qwen-adapter.js";export{e as QwenAdapter};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import l from"node:path";import{fileURLToPath as m}from"node:url";import{EventEmitter as h}from"node:events";import{AgentProcess as f}from"../../agent/process.js";import{AcpClient as v,AcpAuthRequiredError as g}from"../../protocol/acp-client.js";import{AgentEventType as a}from"../../types/events.js";import{QuotedMessageStream as R}from"../../core/util/quoted-message-stream.js";import{InternalApiServer as A}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as b}from"../../core/persistence/event-results-store.js";import{log as r}from"../../core/log/index.js";const d=l.dirname(m(import.meta.url)),w=300*1e3,u=60*1e3,I=200;class T extends h{type="qwen";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;pendingApprovals=new Map;bindingStore;eventResults=null;currentAibotSessionId;stopped=!1;cwd;model;promptTimeoutMs;clientMsgSeq=0;deferredEvents=new Map;sessionBindings=new Map;constructor(e,t,s,i){super(),this.config=e,this.callbacks=t,this.bindingStore=s,this.cwd=e.cwd??process.cwd(),this.model=e.options?.model,this.promptTimeoutMs=e.options?.promptTimeoutMs??w,i&&(this.eventResults=new b(i))}async start(){const e=[...this.config.args??[],"--acp"];this.model&&e.push("--model",this.model);const t={command:this.config.command||"qwen",args:e,cwd:this.cwd,env:this.config.env},s=await this.startInternalApiAndMcp();this.agentProcess=new f;const i=await this.agentProcess.start(t);r.info("qwen-adapter","Qwen process started"),this.agentProcess.on("exit",n=>{this.stopped||(r.error("qwen-adapter",`Process exited unexpectedly (code=${n})`),this.activeRun&&this.finishRun("failed",`qwen process exited (code=${n})`),this.emit("exit",n))}),this.acpClient=new v,this.acpClient.on("event",n=>this.handleAcpEvent(n));const o=this.currentAibotSessionId?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0;try{await this.acpClient.connect({transport:i,initialMode:"bypass",mcpServers:s,sessionId:o,cwd:this.cwd}),r.info("qwen-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.currentAibotSessionId&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[n,c]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",c)}catch(n){if(n instanceof g){await this.handleAuthRequired(n);return}throw n}}async stop(){this.stopped=!0,this.deferredEvents.clear(),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.timeoutTimer&&(clearTimeout(this.activeRun.timeoutTimer),this.activeRun.timeoutTimer=null),this.activeRun.firstResponseTimer&&(clearTimeout(this.activeRun.firstResponseTimer),this.activeRun.firstResponseTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.acpClient?.isAlive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new S(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.ping(e)??!1}getStatus(){return{alive:this.acpClient?.isAlive??!1,busy:this.activeRun!==null,sessions:this.acpClient?1:0}}getMcpConfig(){if(!this.internalApi)return null;const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}get pendingApprovalEntries(){return this.pendingApprovals}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}async handleLocalAction(e){const t=e.action_type??"",s=e.params??{};if(t==="exec_approve"||t==="exec_reject"){const i=String(s.tool_call_id??""),o=t==="exec_approve";if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"};const n=this.pendingApprovals.get(i);return n?(this.pendingApprovals.delete(i),this.acpClient&&this.acpClient.respondPermission(n,{behavior:o?"allow":"deny"}).catch(c=>{r.error("qwen-adapter",`Failed to respond to permission: ${c}`)}),this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${i}`),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async startInternalApiAndMcp(){try{this.internalApi=new A,this.internalApi.setInvokeHandler(async(t,s)=>this.callbacks.agentInvoke(t,s)),await this.internalApi.start(0),r.info("qwen-adapter",`Internal API started at ${this.internalApi.url}`);const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return[{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]}catch(e){r.error("qwen-adapter",`Failed to start MCP tools: ${e}`);return}}bindSession(e,t){return this.sessionBindings.get(e)?!1:(this.sessionBindings.set(e,t),this.bindingStore.set(e,t),this.acpClient?.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.acpClient?.isAlive&&this.callbacks.sendUpdateBindingCard(e,"connected",t),!0)}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}deliverInboundEvent(e){if(this.callbacks.sendEventAck(e.event_id,e.session_id),this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("qwen-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.callbacks.sendEventResult(e.event_id,t.status,t.msg);return}if(this.activeRun){r.info("qwen-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.acpClient?.isAlive){this.callbacks.sendEventResult(e.event_id,"failed","qwen agent not alive");return}if(e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),!this.sessionBindings.has(e.session_id)){this.deferEvent(e),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"Qwen needs a workspace before it can reply. Use /grix open <directory> to bind.",1,!1),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"",2,!0),this.callbacks.sendEventResult(e.event_id,"responded");return}this.startRun(e,!1)}deliverStopEvent(e){this.activeRun?.eventId===e&&(this.flushStream(),this.finishRun("canceled","stopped by user"))}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(s=>s.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("qwen-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`))}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!(!t||t.length===0)){this.deferredEvents.delete(e),r.info("qwen-adapter",`Replaying ${t.length} deferred events for session ${e}`);for(const{event:s}of t){if(this.activeRun){r.info("qwen-adapter",`Cannot replay ${s.event_id}: agent busy, dropping`);continue}this.startRun(s,!0)}}}startRun(e,t){this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgId:`qwen_${++this.clientMsgSeq}_${Date.now()}`,chunkSeq:0,buffer:"",quotedStream:new R,responded:!1,silent:t,flushTimer:null,timeoutTimer:null,firstResponseTimer:null};const s=this.activeRun;s.firstResponseTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&!s.responded&&(r.error("qwen-adapter",`No response from agent within ${u}ms for ${e.event_id}`),this.finishRun("failed","agent not responding"))},u),s.timeoutTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&(r.error("qwen-adapter",`Prompt timed out for ${e.event_id}`),this.finishRun("failed","agent response timed out"))},this.promptTimeoutMs),this.callbacks.sendSessionComposing(e.session_id,!0),this.acpClient.send(e.content).catch(i=>{r.error("qwen-adapter",`Prompt failed: ${i}`),this.finishRun("failed",i instanceof Error?i.message:String(i))})}handleAcpEvent(e){if(e.type===a.PermissionRequest){this.handlePermissionRequest(e);return}const t=this.activeRun;if(t)switch(t.responded||(t.responded=!0,t.firstResponseTimer&&(clearTimeout(t.firstResponseTimer),t.firstResponseTimer=null)),e.type){case a.Text:{e.content&&this.appendToStream(t,e.content);break}case a.ToolUse:{e.toolName&&this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??"");break}case a.ToolResult:{e.content&&this.callbacks.sendToolResult(t.eventId,t.sessionId,e.content);break}case a.Thinking:{e.content&&this.callbacks.sendThinking(t.eventId,t.sessionId,e.content);break}case a.Error:{r.error("qwen-adapter",`ACP error: ${e.error}`);break}case a.Result:{this.flushStream(),this.finishRun("responded");break}}}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;const s=t.toolCallId;this.pendingApprovals.set(s,e.requestId);const i=this.activeRun;i?this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:s,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}):(r.info("qwen-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(s))}async handleAuthRequired(e){r.info("qwen-adapter",`Auth required, methods: ${e.authMethods.map(o=>o.id).join(", ")}`);const t=e.authMethods.find(o=>/oauth|browser/i.test(o.id))??e.authMethods[0];if(!t)throw e;const s=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(s,`Qwen authentication required (${t.id}). Initiating auth flow...`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"failed",n);const i=await this.captureAuthUrl();i&&this.callbacks.sendAuthNotification(s,`Please open this URL to authenticate:
|
|
2
|
+
${i}
|
|
3
|
+
|
|
4
|
+
Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("qwen-adapter","Authentication successful"),this.callbacks.sendAuthNotification(s,"Authentication successful. Resuming..."),await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:"bypass",mcpServers:this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[l.resolve(d,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0}),r.info("qwen-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"ready",n)}catch(o){throw r.error("qwen-adapter",`Auth retry failed: ${o}`),o}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let s=!1;const i=o=>{if(s)return;const n=o.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);n&&(s=!0,this.agentProcess.removeListener("stderr",i),e(n[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{s||(s=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}appendToStream(e,t){const s=e.quotedStream.consume(t);s.deltaContent&&(e.buffer+=s.deltaContent,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),I)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.callbacks.sendStreamChunk(e.eventId,e.sessionId,t,++e.chunkSeq,!1)}finishRun(e,t){const s=this.activeRun;if(!s)return;this.activeRun=null,this.callbacks.sendSessionComposing(s.sessionId,!1),s.flushTimer&&(clearTimeout(s.flushTimer),s.flushTimer=null),s.timeoutTimer&&(clearTimeout(s.timeoutTimer),s.timeoutTimer=null),s.firstResponseTimer&&(clearTimeout(s.firstResponseTimer),s.firstResponseTimer=null);const i=s.quotedStream.flush();i.deltaContent&&(s.buffer+=i.deltaContent),s.buffer&&(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,s.buffer,++s.chunkSeq,!1),s.buffer=""),t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t),this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",++s.chunkSeq,!0),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.eventResults&&!s.silent&&this.eventResults.set({sessionId:s.sessionId,eventId:s.eventId,status:e,msg:t,updatedAt:Date.now()})}}class S extends h{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){this.emit("error",e)}async cancel(){}}export{T as QwenAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AgentEventType as e}from"../types/events.js";export{e as AgentEventType};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AgentProcess as r}from"./process.js";export{r as AgentProcess};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{EventEmitter as l}from"node:events";import{JsonRpcTransport as d}from"../core/transport/json-rpc.js";import{log as a}from"../core/log/index.js";import{resolveCommandPath as m,spawnCommand as h,killProcessGroup as c}from"../core/runtime/spawn.js";class g extends l{proc=null;_transport=null;_alive=!1;get transport(){return this._transport}get alive(){return this._alive}async start(t){if(this.proc)throw new Error("process already started");const i={...process.env,...t.env},s=m(t.command,typeof i.PATH=="string"?i.PATH:void 0),r=h(s,t.args??[],{cwd:t.cwd,env:i}).process;if(this.proc=r,this._alive=!0,r.on("error",e=>{this._alive=!1,this.emitProcessError(e)}),r.on("exit",(e,o)=>{this._alive=!1,a.info("agent-process",`exited command=${s} pid=${r.pid} code=${e} signal=${o}`),this.emit("exit",e)}),r.stderr?.on("data",e=>{const o=e.toString();if(o.trim()){process.stderr.write(`[agent stderr] ${o}`);for(const p of o.split(`
|
|
2
|
+
`))p.trim()&&a.info("agent-stderr",`[${s}] ${p.trimEnd()}`)}this.emit("stderr",e)}),!r.stdin||!r.stdout)throw new Error("failed to create stdin/stdout pipes");const n=new d(r.stdout,r.stdin);return n.on("close",()=>{this._alive=!1}),this._transport=n,n}async close(t=5e3){if(this._transport?.close(),this._transport=null,!this.proc||!this.proc.pid){this._alive=!1;return}c(this.proc,"SIGTERM"),!await Promise.race([new Promise(s=>{this.proc.once("exit",()=>s(!0))}),new Promise(s=>{setTimeout(()=>s(!1),t)})])&&this.proc.pid&&c(this.proc,"SIGKILL"),this._alive=!1,this.proc=null}emitProcessError(t){if(this.listenerCount("error")===0){a.error("agent-process",`Process error (no listeners): ${t.message}`);return}this.emit("error",t)}}export{g as AgentProcess};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EventEmitter as o}from"node:events";import c from"ws";const r="aibot-agent-api-v1",h=1;class l extends o{ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;config;constructor(e){super(),this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,capabilities:e.capabilities??["stream_chunk","local_action_v1"],localActions:e.localActions??["exec_approve","exec_reject"]}}get isConnected(){return this.connected}async connect(){return new Promise((e,a)=>{const s=new c(this.config.url);this.ws=s;const i=setTimeout(()=>{a(new Error("Auth timeout: no auth_ack received within 15s")),s.close()},15e3);s.on("open",()=>{this.sendPacket("auth",{agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:r,contract_version:h,capabilities:this.config.capabilities,local_actions:this.config.localActions})}),s.on("message",t=>{let n;try{n=JSON.parse(t.toString())}catch{return}this.handlePacket(n,i,e,a)}),s.on("close",(t,n)=>{this.connected=!1,this.stopHeartbeat(),clearTimeout(i),this.emit("close",t,n.toString())}),s.on("error",t=>{clearTimeout(i),this.emit("error",t),this.connected||a(t)})})}handlePacket(e,a,s,i){switch(e.cmd){case"auth_ack":{clearTimeout(a);const t=e.payload;t.code===0?(this.connected=!0,t.heartbeat_sec&&(this.heartbeatSec=t.heartbeat_sec),this.startHeartbeat(),this.emit("auth",t),s(t)):i(new Error(`Auth failed: code=${t.code} msg=${t.msg}`));break}case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"kicked":{this.emit("kicked",e.payload),this.disconnect();break}case"error":{const t=e.payload;this.emit("error",new Error(`Server error: code=${t.code} msg=${t.msg}`));break}case"send_ack":case"send_nack":case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)}sendStreamChunk(e){this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)}sendEventResult(e){this.sendPacket("event_result",e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendPing(){this.sendPacket("ping",{})}disconnect(){this.connected=!1,this.stopHeartbeat(),this.ws&&(this.ws.close(),this.ws=null)}sendPacket(e,a){if(!this.ws||this.ws.readyState!==c.OPEN)return;const s={cmd:e,seq:++this.seq,payload:a};this.ws.send(JSON.stringify(s))}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.sendPing()},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{l as AibotClient};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./types.js";import{AibotClient as o}from"./client.js";export{o as AibotClient};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{log as i}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as u}from"./event-queue.js";class p extends Error{constructor(t){super(`adapter pool full (maxSize=${t})`),this.name="PoolFullError"}}const f=6e4;class S{slots=new Map;config;factory;sendAck;onEventState=null;stopped=!1;sweepTimer=null;constructor(t,e,s){this.config=t,this.factory=e,this.sendAck=s}setEventStateHandler(t){this.onEventState=t}async deliverInboundEvent(t){const e=t.session_id,s=this.slots.get(e);if(s){if(s.startPromise&&await s.startPromise,s.state==="stopped"){s.lastActivityAt=Date.now(),this.sendAck(t.event_id,t.session_id),s.pendingEvents.push(t),i.info("adapter-pool",`Queued event ${t.event_id} for stopped slot ${e} (${s.pendingEvents.length} pending)`);return}s.lastActivityAt=Date.now(),this.sendAck(t.event_id,t.session_id),this.submitToSlotQueue(s,t);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new p(this.config.maxPoolSize);this.sendAck(t.event_id,t.session_id);const o=this.createSlot(e);try{await o.startPromise}catch(n){throw n}this.submitToSlotQueue(o,t)}submitToSlotQueue(t,e){if(!this.config.eventQueue){t.adapter.deliverInboundEvent(e);return}t.eventQueue.submit(e)==="rejected"&&i.info("adapter-pool",`Event ${e.event_id} rejected by EventQueue (queue full)`)}eventComplete(t,e){if(!this.config.eventQueue)return;const s=this.slots.get(e);s&&s.eventQueue.complete(t)}cancelEvent(t,e){if(!this.config.eventQueue)return!1;const s=this.slots.get(e);return s?s.eventQueue.cancel(t):!1}clearQueue(t){if(!this.config.eventQueue)return[];const e=this.slots.get(t);return e?e.eventQueue.clear(t):[]}getQueueSnapshot(t){if(!this.config.eventQueue)return null;const e=this.slots.get(t);return e?e.eventQueue.snapshot(t):null}deliverStopEvent(t,e){if(e){this.slots.get(e)?.adapter.deliverStopEvent(t,e);return}for(const s of this.slots.values())s.adapter.deliverStopEvent(t)}collectActiveEventIds(){const t=[];for(const e of this.slots.values()){const s=e.adapter.getActiveEventIds;typeof s=="function"&&t.push(...s.call(e.adapter))}return t}clearActiveEventsForShutdown(){for(const t of this.slots.values()){const e=t.adapter.clearActiveEventForShutdown;typeof e=="function"&&e.call(t.adapter)}}async deliverLocalAction(t,e){const s=String((t.params??{}).session_id??"");let o=s?this.slots.get(s):void 0;if(!o&&s&&e?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){i.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const r=this.createSlot(s);try{await r.startPromise,o=r}catch(c){i.error("adapter-pool",`Failed to auto-create slot for ${s}: ${c}`)}}return o?.adapter?.handleLocalAction?(o.lastActivityAt=Date.now(),o.adapter.handleLocalAction(t)):(await Promise.allSettled([...this.slots.values()].map(r=>r.adapter.handleLocalAction?.(t)??Promise.resolve({handled:!1,kind:""})))).find(r=>r.status==="fulfilled"&&r.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(t){const e=this.slots.get(t);if(e)return e;if(!this.stopped)return this.createSlot(t)}getSlot(t){return this.slots.get(t)}getAllSlots(){return[...this.slots.values()]}async removeSlot(t){const e=this.slots.get(t);e&&(e.eventQueue.destroy(),e.respawn.stopAll(),e.adapter.removeAllListeners("exit"),await e.adapter.stop().catch(()=>{}),this.slots.delete(t))}async stop(){this.stopped=!0,this.stopIdleSweep();const t=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(t.map(async e=>{e.eventQueue.destroy(),e.respawn.stopAll(),e.adapter.removeAllListeners("exit"),await e.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),f)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const t=Date.now();for(const[e,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){i.info("adapter-pool",`Removing dead slot for session ${e} (sweep)`),this.removeSlot(e).catch(()=>{});continue}if(s.state==="ready"){if(s.adapter.getStatus().busy){s.lastActivityAt=t;continue}t-s.lastActivityAt>this.config.idleTimeoutMs&&(i.info("adapter-pool",`Evicting idle slot for session ${e}`),this.removeSlot(e).catch(o=>{i.error("adapter-pool",`Failed to evict slot ${e}: ${o}`)}))}}}evictDeadSlot(){for(const[t,e]of this.slots)if(e.state==="stopped"&&e.respawn.exhausted)return i.info("adapter-pool",`Removing dead slot for session ${t} (exhausted respawn)`),this.removeSlot(t).catch(()=>{}),!0;return!1}evictIdleSlot(){let t=null;for(const e of this.slots.values())e.state==="ready"&&(e.adapter.getStatus().busy||(!t||e.lastActivityAt<t.lastActivityAt)&&(t=e));return t?(i.info("adapter-pool",`Evicting idle slot for session ${t.sessionId} (pool full)`),this.removeSlot(t.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let t=null,e="";for(const[s,o]of this.slots)o.state==="ready"&&(o.adapter.getStatus().busy||(!t||o.lastActivityAt<t.lastActivityAt)&&(t=o,e=s));return!t||!e?!1:(i.info("adapter-pool",`Evicting idle slot for session ${e} (pool full, sync)`),this.slots.delete(e),t.respawn.stopAll(),t.adapter.removeAllListeners("exit"),t.adapter.stop().catch(()=>{}),!0)}getStatus(){let t=0,e=0;for(const s of this.slots.values())s.state==="ready"&&(t++,s.adapter.getStatus().busy&&e++);return{total:this.slots.size,ready:t,busy:e}}drainPendingEvents(t){const e=t.pendingEvents.splice(0);if(e.length!==0){i.info("adapter-pool",`Draining ${e.length} pending events for slot ${t.sessionId}`);for(const s of e)t.adapter.deliverInboundEvent(s)}}createSlot(t){const e=this.factory(t),s=new d,o=this.createEventQueue(t),n={sessionId:t,adapter:e,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,pendingEvents:[],eventQueue:o};this.slots.set(t,n);const a=(async()=>{try{this.wireSlotEvents(n),await e.start(),n.state="ready"}catch(r){throw n.adapter.removeAllListeners("exit"),n.adapter.removeAllListeners("error"),this.slots.delete(t),r}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(t){t.adapter.on("exit",e=>{this.stopped||(i.error("adapter-pool",`Slot ${t.sessionId} adapter exited (code=${e})`),t.state="stopped",t.respawn.scheduleRespawn(this.slotRespawnCtx(t)))}),t.adapter.on?.("error",e=>{if(this.stopped)return;const s=e instanceof Error?e.message:String(e);i.error("adapter-pool",`Slot ${t.sessionId} adapter error: ${s}`)})}slotRespawnCtx(t){const e=this,s=t.sessionId;return{name:`slot-${s}`,get stopped(){return e.stopped||!e.slots.has(s)},get agentAlive(){return t.adapter.isAlive()},pingAgent:o=>t.adapter.ping(o),onRespawnNeeded:async()=>{t.adapter.removeAllListeners("exit"),await t.adapter.stop().catch(()=>{}),t.adapter=e.factory(s),t.state="starting",e.wireSlotEvents(t),await t.adapter.start(),t.state="ready",t.respawn.resetAttempts(),t.respawn.resetHealthFailures(),t.respawn.stopSlowRetry(),t.respawn.startHealthCheck(e.slotRespawnCtx(t)),e.drainPendingEvents(t)},onCleanupAgent:()=>{"cleanup"in t.adapter&&t.adapter.cleanup()},onAbortActiveRun:o=>{}}}createEventQueue(t){const e=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:o=>{const n=this.slots.get(t);n&&n.adapter.deliverInboundEvent(o)},onStateChange:(o,n,a,r)=>{this.onEventState?.(o,n,a,r)},onCancelRunning:o=>{this.deliverStopEvent(o,t)},onRejected:(o,n)=>{this.onEventState?.(o.event_id,o.session_id,"failed",{reason:n})}};return new u(e,s)}}export{S as AdapterPool,p as PoolFullError};
|