@messenger-agent/claude-agent 0.26.0-alpha.2 → 0.26.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- import{randomUUID as Ue}from"node:crypto";import{existsSync as lt,mkdirSync as dt}from"node:fs";import{Hono as ut}from"hono";import{HTTPException as ke}from"hono/http-exception";import{zValidator as ct}from"@hono/zod-validator";import{streamSSE as pt}from"hono/streaming";import{claudePlatformInstructions as ft}from"../platform-instructions.js";import{logger as j}from"@messenger-agent/shared/logger";import{appConfig as y}from"../config.js";import{resolveAgentWorkdir as mt,resolveAgentWorkspaceRoot as wt}from"@messenger-agent/shared/agent-workspaces";import{isManagedTaskToolName as $e}from"@messenger-agent/shared/managed-task-tools";import{saveUploadedFilePart as yt}from"@messenger-agent/shared/uploaded-files";import{answerPendingApproval as qe,answerPendingAsk as Le,deletePendingClientTool as We,findPendingClientToolByConversation as vt,listPendingClientTools as Fe,loadPendingClientTool as kt,savePendingAsk as gt,savePendingApproval as Tt,updatePendingAskConversation as _t,waitPendingApproval as It,waitPendingAsk as ht}from"../pending-asks.js";import{AgentAnswerToolPartSchema as Ct,ChatBodySchema as xt}from"../schemas.js";import{createTaskAggregator as bt}from"../task-aggregator.js";import{createManagedTaskMcpServer as St,MANAGED_TASK_MCP_SERVER_NAME as He}from"../managed-task-mcp.js";import{claudeExecutionPermissionMode as At,claudePermissionOptions as Mt}from"../permission.js";import{claudeContextSnapshot as Et,claudeTokenUsage as Pt}from"../token-usage.js";import{SessionManager as Dt}from"../session-manager.js";import{getLlmProxyBinding as Nt,moveLlmProxyBinding as Ot,setLlmProxyBinding as Rt}from"../llm-proxy-bindings.js";const ee=new ut,X=new Dt(()=>y.claude.sessionIdleTimeoutMs??36e5),Q=new Set;function jt(e){if($e(e))return!0;const n=`mcp__${He}__`;return e.startsWith(n)&&$e(e.slice(n.length))}function Bt(e){if(!e||typeof e!="object"||Array.isArray(e))return;const n=e.structuredContent;return n&&typeof n=="object"&&!Array.isArray(n)?n:void 0}function Ut(){const e=new Set(Fe().map(n=>n.conversationId));return{active:Q.size,waiting:e.size}}class B extends Error{status;constructor(n,r){super(r),this.status=n,this.name="ChatRequestError"}}const A=new Map;function Xe(e){return e?.trim().toLowerCase()==="plan"?"plan":"default"}function $t(e){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${e}`,param:"conversation_id"}}function _(e){return!!(e&&typeof e=="object")}function qt(e){return e.type==="file"&&"mediaType"in e&&typeof e.mediaType=="string"&&"url"in e&&typeof e.url=="string"&&(!("filename"in e)||e.filename===void 0||typeof e.filename=="string")}async function Lt(e){if(e.type==="text"&&typeof e.text=="string")return e.text;if(qt(e))return yt(e,y.fileUploads.tempDir);if(e.type!=="step-start")throw new ke(400,{message:`Unsupported message part type: ${e.type}`})}async function Wt(e){const r=(await Promise.all(e.map(Lt))).filter(s=>!!(s&&s.trim().length>0)).join(`
1
+ import{randomUUID as Ue}from"node:crypto";import{existsSync as lt,mkdirSync as dt}from"node:fs";import{Hono as ut}from"hono";import{HTTPException as ke}from"hono/http-exception";import{zValidator as ct}from"@hono/zod-validator";import{streamSSE as pt}from"hono/streaming";import{claudePlatformInstructions as ft}from"../platform-instructions.js";import{logger as B}from"@messenger-agent/shared/logger";import{appConfig as y}from"../config.js";import{resolveAgentWorkdir as mt,resolveAgentWorkspaceRoot as wt}from"@messenger-agent/shared/agent-workspaces";import{isManagedTaskToolName as $e}from"@messenger-agent/shared/managed-task-tools";import{saveUploadedFilePart as yt}from"@messenger-agent/shared/uploaded-files";import{answerPendingApproval as qe,answerPendingAsk as Le,deletePendingClientTool as We,findPendingClientToolByConversation as vt,listPendingClientTools as Fe,loadPendingClientTool as kt,savePendingAsk as gt,savePendingApproval as Tt,updatePendingAskConversation as _t,waitPendingApproval as It,waitPendingAsk as ht}from"../pending-asks.js";import{AgentAnswerToolPartSchema as Ct,ChatBodySchema as xt}from"../schemas.js";import{createTaskAggregator as bt}from"../task-aggregator.js";import{createManagedTaskMcpServer as St,MANAGED_TASK_MCP_SERVER_NAME as He}from"../managed-task-mcp.js";import{claudeExecutionPermissionMode as At,claudePermissionOptions as Mt}from"../permission.js";import{claudeContextSnapshot as Et,claudeTokenUsage as Pt}from"../token-usage.js";import{SessionManager as Dt}from"../session-manager.js";import{getLlmProxyBinding as Ot,moveLlmProxyBinding as Rt,setLlmProxyBinding as Nt}from"../llm-proxy-bindings.js";const ee=new ut,X=new Dt(()=>y.claude.sessionIdleTimeoutMs??36e5),Q=new Set;function jt(e){if($e(e))return!0;const n=`mcp__${He}__`;return e.startsWith(n)&&$e(e.slice(n.length))}function Bt(e){if(!e||typeof e!="object"||Array.isArray(e))return;const n=e.structuredContent;return n&&typeof n=="object"&&!Array.isArray(n)?n:void 0}function Ut(){const e=new Set(Fe().map(n=>n.conversationId));return{active:Q.size,waiting:e.size}}class U extends Error{status;constructor(n,r){super(r),this.status=n,this.name="ChatRequestError"}}const A=new Map;function Xe(e){return e?.trim().toLowerCase()==="plan"?"plan":"default"}function $t(e){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${e}`,param:"conversation_id"}}function _(e){return!!(e&&typeof e=="object")}function qt(e){return e.type==="file"&&"mediaType"in e&&typeof e.mediaType=="string"&&"url"in e&&typeof e.url=="string"&&(!("filename"in e)||e.filename===void 0||typeof e.filename=="string")}async function Lt(e){if(e.type==="text"&&typeof e.text=="string")return e.text;if(qt(e))return yt(e,y.fileUploads.tempDir);if(e.type!=="step-start")throw new ke(400,{message:`Unsupported message part type: ${e.type}`})}async function Wt(e){const r=(await Promise.all(e.map(Lt))).filter(s=>!!(s&&s.trim().length>0)).join(`
2
2
  `).trim();if(!r)throw new ke(400,{message:"No supported message content found"});return r}function Ft(e){for(const n of e){const r=Ct.safeParse(n);if(r.success)return r.data}}function ge(e){if(typeof e=="string")return e;if(_(e)){if(typeof e.answer=="string")return e.answer;if(typeof e.text=="string")return e.text;if(_(e.answers))return Object.values(e.answers).map(n=>String(n)).join(`
3
3
  `);if(Array.isArray(e.answers))return e.answers.map(n=>{if(typeof n=="string")return n;if(_(n)){if(typeof n.answer=="string")return n.answer;if(typeof n.text=="string")return n.text}return JSON.stringify(n)}).join(`
4
4
  `)}return JSON.stringify(e)}function Qe(e){if(_(e)&&_(e.answers)){const n=Object.entries(e.answers).map(([r,s])=>`- ${r}:${String(s)}`);if(n.length>0)return["User answered previous question:",...n].join(`
5
5
  `)}return ge(e)}function Ht(e){return typeof e=="string"?e:JSON.stringify(e)}function Xt(e,n){return _(e)&&typeof e.question=="string"?e.question:`Question ${n+1}`}function Ke(e){return _(e)&&Array.isArray(e.questions)?e.questions:[]}function Qt(e,n){const r=Ke(e);return _(n)&&_(n.answers)?Object.fromEntries(Object.entries(n.answers).map(([s,x])=>[s,String(x)])):r.length>0?Object.fromEntries(r.map((s,x)=>[Xt(s,x),ge(n)])):{answer:ge(n)}}function Kt(e,n,r){return n&&n.state==="output-available"?n.output:{answers:r}}function Jt(e,n,r){return n&&n.state==="output-available"?{answers:Qt(e,n.output),output:n.output}:{answers:r}}function zt(e,n){return!e||e.state!=="output-available"?`User answered previous question: ${n}`:Qe(e.output)}function Gt(e){return _(e)&&typeof e.plan=="string"?e.plan:JSON.stringify(e)}function Vt(e){return{approved:!1,reason:e.trim()||void 0}}function Je(e,n,r){return n&&n.state==="approval-responded"?n.approval:{id:e.approveId,...Vt(r)}}function Te(e,n,r){const s=Je(e,n,r);if(s.id!==e.approveId)throw new ke(400,{message:"Tool approval id does not match pending approval"});return{approved:s.approved,reason:s.reason}}function Yt(e,n,r){const s=Te(e,n,r),x=[s.approved?`User approved tool ${e.toolName} plan:`:`User rejected tool ${e.toolName} plan:`,Gt(e.input)];return s.reason&&x.push(`Reason: ${s.reason}`),x.join(`
6
- `)}function Zt(e){return!!(e&&e.state==="output-available"&&(e.toolName==="AskUserQuestion"||e.toolName==="ask_user_question"))}function en(e){return!!(e&&e.state==="approval-responded")}async function tn(e,n){return e?e.state==="approval-responded"?e.approval.reason??"":Qe(e.output):Wt(n)}function nn(e,n){return mt(y,e,n)}function on(e){return e==="AskUserQuestion"}function rn(e){return e==="ExitPlanMode"}function an(e){const n="subagent_type"in e?e.subagent_type:void 0,r="parent_tool_use_id"in e?e.parent_tool_use_id:void 0;return!!n||!!r}function sn(e){return"session_id"in e?e.session_id:void 0}function ln(e){return e.subtype==="success"&&e.origin?.kind==="task-notification"&&e.num_turns===0&&e.result===""&&e.usage.input_tokens===0&&e.usage.output_tokens===0&&e.usage.cache_creation_input_tokens===0&&e.usage.cache_read_input_tokens===0&&Object.keys(e.modelUsage).length===0}function C(e,n){return e?.get(n)??void 0}function dn(e){const n=C(e,"X-Matrix-Room-Id")?.trim(),r=C(e,"X-Matrix-Thread-ID")?.trim();return n&&r?`${n}/${r}`:void 0}function un(e){const n=C(e,"X-Matrix-Room-Id")?.trim(),r=C(e,"X-Matrix-Event-ID")?.trim(),s=C(e,"X-Matrix-Thread-ID")?.trim();if(!(!n||!r))return{roomId:n,eventId:r,...s?{threadRootEventId:s}:null}}ee.post("/v1/chat/:conversation_id/cancel",e=>{const n=e.req.param("conversation_id");return cn(n)?e.json({success:!0}):e.json({error:$t(n)},404)});function cn(e){return X.cancel(e)}ee.get("/admin/status",e=>{const n=Fe().map(s=>({conversationId:s.conversationId,toolCallId:s.toolCallId,toolName:s.toolName,kind:s.kind,approveId:s.kind==="approval"?s.approveId:void 0,since:s.createdAt,agentMode:s.agentMode,model:s.model})),r=Ut();return e.json({activeTaskCount:r.active,waitingClientToolCount:n.length,clientToolWaits:n})});async function ze({body:e,headers:n}){const r=C(n,"X-Matrix-Workdir"),s=C(n,"X-Matrix-Workspace-ID"),x=dn(n),Ge=un(n);let U,_e;try{U=nn(s,r),_e=wt(y,s)}catch(I){throw new B(400,I instanceof Error?I.message:"Invalid workspace")}if(r&&!lt(U))throw new B(400,`Workdir not found: ${U}`);if(dt(U,{recursive:!0}),e.fork&&!("sessionId"in e.fork))throw new B(400,"Claude fork requires sessionId and messageId");const K=e.fork&&"sessionId"in e.fork?e.fork:void 0,f=Ft(e.message.parts),l=f&&(Zt(f)||en(f))?kt(f.toolCallId):e.conversationId?vt(e.conversationId):void 0,m=l?A.get(l.conversationId):void 0,b=await tn(f,e.message.parts),g=f?void 0:l,$=l&&!m?l.conversationId:e.conversationId,te=C(n,"X-Elevo-LLM-Binding")?.trim()||void 0;let M=K?void 0:X.get($);const ne=M?.llmProxyBinding??Nt($);if(te&&ne&&te!==ne)throw new B(409,"LLM proxy binding does not match the existing conversation");const P=ne??te;if(M&&P&&!M.llmProxyBinding){if(M.hasActiveOwner())throw new B(409,"Cannot attach an LLM proxy binding while the conversation is active");M.abort(),M=void 0}const Ie=e.model||y.claude.defaultModel,J=l&&!m?l.workdir:U,q=l?.kind==="approval"&&Te(l,f,b).approved?"default":l?.agentMode?Xe(l.agentMode):Xe(C(n,"X-Matrix-Agent-Mode")),L=l&&!m?l.model??Ie:Ie;return{run:async(I,W)=>{if(l&&m&&!g){const t={};Q.add(t);let a=!1,i;const o=async()=>{a||(a=!0,i&&(clearTimeout(i),i=void 0),await I.writeDone())},p=async c=>{i&&clearTimeout(i),await I.writeData(c),a||(i=setTimeout(()=>{I.writeData({type:"heartbeat"}).catch(()=>{})},6e4))},u=async c=>{a||await p(c)};try{if(await m.attach({writeData:u,writeDone:o}),l.kind==="ask_user")Le(l.toolCallId,Jt(l.input,f,b));else{const c=Te(l,f,b);c.approved&&await X.get(l.conversationId)?.setPermissionMode(At()),qe(l.toolCallId,c)}await m.done}catch(c){j.error("Claude live ask continuation failed:",c),await u({type:"error",errorText:c instanceof Error?c.message:String(c)}),await o()}finally{i&&clearTimeout(i),Q.delete(t)}return}const he={};Q.add(he);const z=$??Ue();let w=z,Ce=!1,xe=!1,oe=!1,D=new Set,N=new Set;const re=new Map,ae=new Map,ie=new Map;let se=!1,le=!1,de,ue="stop",ce,pe,O,be=new Set,Se=!1,T;const E=bt();let G=!1,Ve=0,R=!1;const fe=new Set;let v=!1,V,me=!1,S,F;const Ye=new Promise(t=>{F=t});let Ae;const Ze=new Promise(t=>{Ae=t}),Me=()=>{T=setTimeout(()=>{d({type:"heartbeat"}).catch(()=>{})},6e4)},et=async t=>{T&&clearTimeout(T),await I.writeData(t),v||Me()};V={writeData:async t=>{v||await et(t)},writeDone:async()=>{v||(v=!0,T&&(clearTimeout(T),T=void 0),await I.writeDone())}};const d=async t=>{v||await V.writeData(t)},Ee=async()=>{v||(await V.writeDone(),v=!0)},tt=async()=>{O||(O={activityId:Ue()},await d({type:"data",kind:"compact",data:{phase:"start",activityId:O.activityId}}))},we=async(t,a={})=>{if(!O)return;const i=O;O=void 0,await d({type:"data",kind:"compact",data:{phase:"end",activityId:i.activityId,status:t,...a}})},H=async t=>{if(!(oe||v)){oe=!0;for(const a of D)await d({type:"text-end",id:a});D=new Set;for(const a of N)await d({type:"reasoning-end",id:a});N=new Set,xe&&(t==="stop"&&ce&&await d({type:"data",kind:"token_usage",data:ce}),await d({type:"finish-step"})),await d({type:"finish",finishReason:t}),await Ee()}},nt=async()=>{for(const t of D)await d({type:"text-end",id:t});D=new Set},ot=async()=>{for(const t of N)await d({type:"reasoning-end",id:t});N=new Set},Y=async()=>{await nt(),await ot()},h=async()=>{if(!G)return;G=!1,E.clearDirty(),await Y();const t=`todo-${z}-${Ve++}`;await d({type:"tool-input-available",toolCallId:t,toolName:"TodoWrite",input:E.snapshot()}),await d({type:"tool-output-available",toolCallId:t,output:"Task updated"})},Pe=async(t,a,i,o)=>{fe.has(t)||(fe.add(t),R=!0,W?.removeEventListener("abort",Z),o&&await d({type:"tool-approval-request",toolCallId:t,approveId:o.approveId}),await Ee(),S=setTimeout(()=>{j.warn(`Claude ${a} timed out after ${y.claude.askTimeoutMs}ms`,{conversationId:w,toolCallId:t}),A.delete(w),k.abort(),F()},y.claude.askTimeoutMs),S.unref?.(),Ae())},De=async t=>{D.has(t)||(D.add(t),await d({type:"text-start",id:t}))},Ne=async t=>{N.has(t)||(N.add(t),await d({type:"reasoning-start",id:t}))},rt=t=>{if(t===w)return;const a=w;w=t,X.bind(k,t),Ot(a,t,P),_t(a,t);const i=A.get(a);i&&(A.delete(a),A.set(t,i))},Oe=async t=>{Ce||(Ce=!0,rt(t),me||(me=!0,A.set(t,{attach:async a=>{S&&(clearTimeout(S),S=void 0),V=a,v=!1,R=!1},done:Ye,dispose:()=>{S&&(clearTimeout(S),S=void 0)}})),await d({type:"start",messageMetadata:{conversationId:t,threadId:t}}),await d({type:"start-step"}),xe=!0)},Re=()=>{me&&(A.get(w)?.dispose(),A.delete(w));for(const t of fe)We(t,{rejectWait:!0})},at=async()=>(await H(ue),Re(),F(),!0);Me();const it=async(t,a,i)=>{const{toolUseID:o}=i;if(on(t)){gt({conversationId:w,toolCallId:o,workdir:J,model:L,agentMode:q,toolName:"AskUserQuestion",input:a}),await Pe(o,"AskUserQuestion",a);const p=await ht(o);return p?"interrupted"in p?{behavior:"deny",message:"Question interrupted by text response",interrupt:!0,toolUseID:o}:{behavior:"allow",updatedInput:{...a,questions:Ke(a),answers:p.answers},toolUseID:o}:{behavior:"deny",message:`${t} requires user input; session will resume after user responds.`,toolUseID:o}}if(rn(t)){const p=Tt({conversationId:w,toolCallId:o,workdir:J,model:L,agentMode:q,toolName:"ExitPlanMode",input:a});await Pe(o,"ExitPlanMode",a,p);const u=await It(o);return u&&"interrupted"in u?{behavior:"deny",message:"Approval interrupted by text response",interrupt:!0,toolUseID:o,decisionClassification:"user_reject"}:u?.approved?{behavior:"allow",updatedInput:{...a,approval:u},toolUseID:o}:{behavior:"deny",message:u?.reason||"Denied by user",toolUseID:o,decisionClassification:"user_reject"}}return{behavior:"allow",toolUseID:o}},st=async t=>{if(an(t))return!1;const a=sn(t);if(a&&(await Oe(a),E.setSessionId(a)),R)return!1;switch(t.type){case"system":return t.subtype==="background_tasks_changed"?be=new Set(t.tasks.map(i=>i.task_id)):t.subtype==="status"?(t.status==="compacting"&&await tt(),t.compact_result==="success"?await we("completed"):t.compact_result==="failed"&&await we("failed",{error:t.compact_error})):t.subtype==="compact_boundary"&&await we("completed",{trigger:t.compact_metadata.trigger,preTokens:t.compact_metadata.pre_tokens,postTokens:t.compact_metadata.post_tokens,durationMs:t.compact_metadata.duration_ms}),!1;case"stream_event":{const i=t.event;switch(i.type){case"message_start":{de=i.message.id,se=!1,le=!1;break}case"content_block_start":{const o=`${de??t.session_id}:${i.index}`;i.content_block.type==="text"?(await h(),re.set(o,t.uuid),await De(t.uuid)):i.content_block.type==="thinking"&&(await h(),ae.set(o,t.uuid),await Ne(t.uuid));break}case"content_block_delta":{const o=i.delta,p=`${de??t.session_id}:${i.index}`;if(o.type==="text_delta"){await h(),se=!0;const u=re.get(p);if(!u){j.warn("Ignoring Claude text delta without content_block_start",{conversationId:w,blockKey:p});break}await d({type:"text-delta",id:u,delta:o.text})}else if(o.type==="thinking_delta"){await h(),le=!0;const u=ae.get(p);if(!u){j.warn("Ignoring Claude reasoning delta without content_block_start",{conversationId:w,blockKey:p});break}await d({type:"reasoning-delta",id:u,delta:o.thinking})}break}case"content_block_stop":break}return!1}case"assistant":{pe=Et(t.message)??pe,t.session_id&&t.uuid&&await d({type:"data",kind:"agent_session",data:{agentType:"claude",sessionId:t.session_id,messageId:t.uuid}});const i=t.message.content;if(typeof i=="string")break;for(const o of i)switch(o.type){case"thinking":{if(le||!o.thinking)break;await h(),await Ne(t.uuid),await d({type:"reasoning-delta",id:t.uuid,delta:o.thinking});break}case"text":{if(se||!o.text)break;await h(),await De(t.uuid),await d({type:"text-delta",id:t.uuid,delta:o.text});break}case"tool_use":{if(await Y(),ie.set(o.id,o.name),E.isTaskTool(o.name)){E.applyToolUse(o.id,o.name,o.input)&&(G=!0);break}await h(),await Y(),await d({type:"tool-input-available",toolCallId:o.id,toolName:o.name,input:o.input});break}}return await Y(),re.clear(),ae.clear(),!1}case"user":{const i=t.message.content;if(typeof i=="string")break;const o=i.filter(u=>u.type==="tool_result"),p=o.length===1?Bt(t.tool_use_result):void 0;for(const u of o){const c=u.tool_use_id,ve=u.content;if(E.isTaskToolCall(c)){E.applyToolResult(c,ve)&&(G=!0);continue}if(await h(),u.is_error===!0)await d({type:"tool-output-error",toolCallId:c,errorText:Ht(ve)});else{const Be=ie.get(c);await d({type:"tool-output-available",toolCallId:c,output:Be&&jt(Be)&&p!==void 0?p:ve})}ie.delete(c)}return!1}case"result":{if(be.size>0)return t.origin?.kind!=="task-notification"&&(Se=!0),!1;if(ln(t)&&!Se)return!1;ue=t.subtype==="success"||k.wasCancelled?"stop":"error";const i=Pt(t,pe);return ce=ue==="stop"?i:void 0,await h(),t.subtype!=="success"&&!k.wasCancelled&&await d({type:"error",errorText:t.errors?.filter(o=>o.trim()).join(`
7
- `)||`Claude Code failed: ${t.subtype}`}),at()}}return!1},ye={threadRef:x,matrixEvent:Ge},k=M??X.create({conversationId:z,workdir:J,model:L,agentMode:q,managedTaskContext:ye,llmProxyBinding:P,buildOptions:(t,a,i)=>({abortController:t,cwd:J,includePartialMessages:!0,model:L,maxTurns:y.claude.maxTurns,systemPrompt:{type:"preset",preset:"claude_code",append:ft},...Mt(q),tools:{type:"preset",preset:"claude_code"},mcpServers:{[He]:St({workspaceRoot:_e,context:i})},disallowedTools:["EnterWorktree","ExitWorktree"],...K?{resume:K.sessionId,resumeSessionAt:K.messageId,forkSession:!0}:$?{resume:$}:null,canUseTool:a,env:{...process.env,...y.claude.apiKey?{ANTHROPIC_API_KEY:y.claude.apiKey}:null,...y.claude.baseUrl?{ANTHROPIC_BASE_URL:y.claude.baseUrl}:null,...P?{ANTHROPIC_CUSTOM_HEADERS:[process.env.ANTHROPIC_CUSTOM_HEADERS,`X-Elevo-LLM-Binding: ${P}`].filter(Boolean).join(`
8
- `)}:null,CLAUDE_AGENT_SDK_CLIENT_APP:"coding-agent/claude-agent"}})});Rt(z,P),k.updateManagedTaskContext(ye);const Z=()=>k.abort();let je=!1;try{g&&m&&(await m.attach({writeData:async()=>{},writeDone:async()=>{}}),g.kind==="ask_user"?Le(g.toolCallId,{interrupted:!0}):qe(g.toolCallId,{interrupted:!0}),await m.done),l&&(!m||g)&&(l.kind==="ask_user"?await d(g?{type:"tool-output-error",toolCallId:l.toolCallId,toolName:l.toolName,errorText:"Question interrupted by text response"}:{type:"tool-output-available",toolCallId:l.toolCallId,output:Kt(l,f,b)}):await d(g?{type:"tool-output-error",toolCallId:l.toolCallId,toolName:l.toolName,errorText:"Approval interrupted by text response"}:{type:"tool-output-available",toolCallId:l.toolCallId,output:Je(l,f,b)}),(!g||!m)&&We(l.toolCallId));const t=l&&!m&&!g?l.kind==="ask_user"?zt(f,b):Yt(l,f,b):b,a=await k.reserveTurn(t,st,it,{model:L,agentMode:q,managedTaskContext:ye});if(a.kind==="interjection"){T&&clearTimeout(T),await I.writeDone();return}je=!0,W?.aborted&&Z(),W?.addEventListener("abort",Z,{once:!0}),await Promise.race([a.done,Ze]),!R&&!oe&&!v&&await H(k.wasCancelled||!k.isDead?"stop":"error")}catch(t){v?(j.error("Claude stream failed after downstream closed:",t),F()):(await Oe(w),W?.aborted||k.wasCancelled?await H("stop"):k.isDead?await H("error"):(j.error("Claude stream failed:",t),await d({type:"error",errorText:t instanceof Error?t.message:String(t)}),await H("error")))}finally{je&&W?.removeEventListener("abort",Z),T&&clearTimeout(T),Q.delete(he),R||Re(),R||F()}}}}async function Nn(e,n){await(await ze(e)).run(n,e.signal)}ee.post("/v1/chat/stream",ct("json",xt),async e=>{let n;try{n=await ze({body:e.req.valid("json"),headers:e.req.raw.headers})}catch(r){if(r instanceof B)return e.json({error:r.message},r.status);throw r}return pt(e,async r=>{await n.run({writeData:async s=>{await r.writeSSE({data:JSON.stringify(s)})},writeDone:async()=>{await r.writeSSE({data:"[DONE]"})}},e.req.raw.signal)})});var On=ee;export{B as ChatRequestError,cn as cancelClaudeConversation,Ut as claudeActivitySnapshot,On as default,Nn as handleClaudeChatStream,ze as prepareClaudeChatStream};
6
+ `)}function Zt(e){return!!(e&&e.state==="output-available"&&(e.toolName==="AskUserQuestion"||e.toolName==="ask_user_question"))}function en(e){return!!(e&&e.state==="approval-responded")}async function tn(e,n){return e?e.state==="approval-responded"?e.approval.reason??"":Qe(e.output):Wt(n)}function nn(e,n){return mt(y,e,n)}function on(e){return e==="AskUserQuestion"}function rn(e){return e==="ExitPlanMode"}function an(e){return e==="Agent"||e==="Task"}function sn(e){const n="subagent_type"in e?e.subagent_type:void 0,r="parent_tool_use_id"in e?e.parent_tool_use_id:void 0;return!!n||!!r}function ln(e){return"session_id"in e?e.session_id:void 0}function dn(e){return e.subtype==="success"&&e.origin?.kind==="task-notification"&&e.num_turns===0&&e.result===""&&e.usage.input_tokens===0&&e.usage.output_tokens===0&&e.usage.cache_creation_input_tokens===0&&e.usage.cache_read_input_tokens===0&&Object.keys(e.modelUsage).length===0}function C(e,n){return e?.get(n)??void 0}function un(e){const n=C(e,"X-Matrix-Room-Id")?.trim(),r=C(e,"X-Matrix-Thread-ID")?.trim();return n&&r?`${n}/${r}`:void 0}function cn(e){const n=C(e,"X-Matrix-Room-Id")?.trim(),r=C(e,"X-Matrix-Event-ID")?.trim(),s=C(e,"X-Matrix-Thread-ID")?.trim();if(!(!n||!r))return{roomId:n,eventId:r,...s?{threadRootEventId:s}:null}}ee.post("/v1/chat/:conversation_id/cancel",e=>{const n=e.req.param("conversation_id");return pn(n)?e.json({success:!0}):e.json({error:$t(n)},404)});function pn(e){return X.cancel(e)}ee.get("/admin/status",e=>{const n=Fe().map(s=>({conversationId:s.conversationId,toolCallId:s.toolCallId,toolName:s.toolName,kind:s.kind,approveId:s.kind==="approval"?s.approveId:void 0,since:s.createdAt,agentMode:s.agentMode,model:s.model})),r=Ut();return e.json({activeTaskCount:r.active,waitingClientToolCount:n.length,clientToolWaits:n})});async function ze({body:e,headers:n}){const r=C(n,"X-Matrix-Workdir"),s=C(n,"X-Matrix-Workspace-ID"),x=un(n),Ge=cn(n);let $,_e;try{$=nn(s,r),_e=wt(y,s)}catch(I){throw new U(400,I instanceof Error?I.message:"Invalid workspace")}if(r&&!lt($))throw new U(400,`Workdir not found: ${$}`);if(dt($,{recursive:!0}),e.fork&&!("sessionId"in e.fork))throw new U(400,"Claude fork requires sessionId and messageId");const K=e.fork&&"sessionId"in e.fork?e.fork:void 0,f=Ft(e.message.parts),l=f&&(Zt(f)||en(f))?kt(f.toolCallId):e.conversationId?vt(e.conversationId):void 0,m=l?A.get(l.conversationId):void 0,b=await tn(f,e.message.parts),g=f?void 0:l,q=l&&!m?l.conversationId:e.conversationId,te=C(n,"X-Elevo-LLM-Binding")?.trim()||void 0;let M=K?void 0:X.get(q);const ne=M?.llmProxyBinding??Ot(q);if(te&&ne&&te!==ne)throw new U(409,"LLM proxy binding does not match the existing conversation");const P=ne??te;if(M&&P&&!M.llmProxyBinding){if(M.hasActiveOwner())throw new U(409,"Cannot attach an LLM proxy binding while the conversation is active");M.abort(),M=void 0}const Ie=e.model||y.claude.defaultModel,J=l&&!m?l.workdir:$,L=l?.kind==="approval"&&Te(l,f,b).approved?"default":l?.agentMode?Xe(l.agentMode):Xe(C(n,"X-Matrix-Agent-Mode")),D=l&&!m?l.model??Ie:Ie;return{run:async(I,W)=>{if(l&&m&&!g){const t={};Q.add(t);let a=!1,i;const o=async()=>{a||(a=!0,i&&(clearTimeout(i),i=void 0),await I.writeDone())},p=async c=>{i&&clearTimeout(i),await I.writeData(c),a||(i=setTimeout(()=>{I.writeData({type:"heartbeat"}).catch(()=>{})},6e4))},u=async c=>{a||await p(c)};try{if(await m.attach({writeData:u,writeDone:o}),l.kind==="ask_user")Le(l.toolCallId,Jt(l.input,f,b));else{const c=Te(l,f,b);c.approved&&await X.get(l.conversationId)?.setPermissionMode(At()),qe(l.toolCallId,c)}await m.done}catch(c){B.error("Claude live ask continuation failed:",c),await u({type:"error",errorText:c instanceof Error?c.message:String(c)}),await o()}finally{i&&clearTimeout(i),Q.delete(t)}return}const he={};Q.add(he);const z=q??Ue();let w=z,Ce=!1,xe=!1,oe=!1,O=new Set,R=new Set;const re=new Map,ae=new Map,ie=new Map;let se=!1,le=!1,de,ue="stop",ce,pe,N,be=new Set,Se=!1,T;const E=bt();let G=!1,Ve=0,j=!1;const fe=new Set;let v=!1,V,me=!1,S,F;const Ye=new Promise(t=>{F=t});let Ae;const Ze=new Promise(t=>{Ae=t}),Me=()=>{T=setTimeout(()=>{d({type:"heartbeat"}).catch(()=>{})},6e4)},et=async t=>{T&&clearTimeout(T),await I.writeData(t),v||Me()};V={writeData:async t=>{v||await et(t)},writeDone:async()=>{v||(v=!0,T&&(clearTimeout(T),T=void 0),await I.writeDone())}};const d=async t=>{v||await V.writeData(t)},Ee=async()=>{v||(await V.writeDone(),v=!0)},tt=async()=>{N||(N={activityId:Ue()},await d({type:"data",kind:"compact",data:{phase:"start",activityId:N.activityId}}))},we=async(t,a={})=>{if(!N)return;const i=N;N=void 0,await d({type:"data",kind:"compact",data:{phase:"end",activityId:i.activityId,status:t,...a}})},H=async t=>{if(!(oe||v)){oe=!0;for(const a of O)await d({type:"text-end",id:a});O=new Set;for(const a of R)await d({type:"reasoning-end",id:a});R=new Set,xe&&(t==="stop"&&ce&&await d({type:"data",kind:"token_usage",data:ce}),await d({type:"finish-step"})),await d({type:"finish",finishReason:t}),await Ee()}},nt=async()=>{for(const t of O)await d({type:"text-end",id:t});O=new Set},ot=async()=>{for(const t of R)await d({type:"reasoning-end",id:t});R=new Set},Y=async()=>{await nt(),await ot()},h=async()=>{if(!G)return;G=!1,E.clearDirty(),await Y();const t=`todo-${z}-${Ve++}`;await d({type:"tool-input-available",toolCallId:t,toolName:"TodoWrite",input:E.snapshot()}),await d({type:"tool-output-available",toolCallId:t,output:"Task updated"})},Pe=async(t,a,i,o)=>{fe.has(t)||(fe.add(t),j=!0,W?.removeEventListener("abort",Z),o&&await d({type:"tool-approval-request",toolCallId:t,approveId:o.approveId}),await Ee(),S=setTimeout(()=>{B.warn(`Claude ${a} timed out after ${y.claude.askTimeoutMs}ms`,{conversationId:w,toolCallId:t}),A.delete(w),k.abort(),F()},y.claude.askTimeoutMs),S.unref?.(),Ae())},De=async t=>{O.has(t)||(O.add(t),await d({type:"text-start",id:t}))},Oe=async t=>{R.has(t)||(R.add(t),await d({type:"reasoning-start",id:t}))},rt=t=>{if(t===w)return;const a=w;w=t,X.bind(k,t),Rt(a,t,P),_t(a,t);const i=A.get(a);i&&(A.delete(a),A.set(t,i))},Re=async t=>{Ce||(Ce=!0,rt(t),me||(me=!0,A.set(t,{attach:async a=>{S&&(clearTimeout(S),S=void 0),V=a,v=!1,j=!1},done:Ye,dispose:()=>{S&&(clearTimeout(S),S=void 0)}})),await d({type:"start",messageMetadata:{conversationId:t,threadId:t}}),await d({type:"start-step"}),xe=!0)},Ne=()=>{me&&(A.get(w)?.dispose(),A.delete(w));for(const t of fe)We(t,{rejectWait:!0})},at=async()=>(await H(ue),Ne(),F(),!0);Me();const it=async(t,a,i)=>{const{toolUseID:o}=i;if(on(t)){gt({conversationId:w,toolCallId:o,workdir:J,model:D,agentMode:L,toolName:"AskUserQuestion",input:a}),await Pe(o,"AskUserQuestion",a);const p=await ht(o);return p?"interrupted"in p?{behavior:"deny",message:"Question interrupted by text response",interrupt:!0,toolUseID:o}:{behavior:"allow",updatedInput:{...a,questions:Ke(a),answers:p.answers},toolUseID:o}:{behavior:"deny",message:`${t} requires user input; session will resume after user responds.`,toolUseID:o}}if(rn(t)){const p=Tt({conversationId:w,toolCallId:o,workdir:J,model:D,agentMode:L,toolName:"ExitPlanMode",input:a});await Pe(o,"ExitPlanMode",a,p);const u=await It(o);return u&&"interrupted"in u?{behavior:"deny",message:"Approval interrupted by text response",interrupt:!0,toolUseID:o,decisionClassification:"user_reject"}:u?.approved?{behavior:"allow",updatedInput:{...a,approval:u},toolUseID:o}:{behavior:"deny",message:u?.reason||"Denied by user",toolUseID:o,decisionClassification:"user_reject"}}return an(t)?{behavior:"allow",updatedInput:{...a,model:D},toolUseID:o}:{behavior:"allow",toolUseID:o}},st=async t=>{if(sn(t))return!1;const a=ln(t);if(a&&(await Re(a),E.setSessionId(a)),j)return!1;switch(t.type){case"system":return t.subtype==="background_tasks_changed"?be=new Set(t.tasks.map(i=>i.task_id)):t.subtype==="status"?(t.status==="compacting"&&await tt(),t.compact_result==="success"?await we("completed"):t.compact_result==="failed"&&await we("failed",{error:t.compact_error})):t.subtype==="compact_boundary"&&await we("completed",{trigger:t.compact_metadata.trigger,preTokens:t.compact_metadata.pre_tokens,postTokens:t.compact_metadata.post_tokens,durationMs:t.compact_metadata.duration_ms}),!1;case"stream_event":{const i=t.event;switch(i.type){case"message_start":{de=i.message.id,se=!1,le=!1;break}case"content_block_start":{const o=`${de??t.session_id}:${i.index}`;i.content_block.type==="text"?(await h(),re.set(o,t.uuid),await De(t.uuid)):i.content_block.type==="thinking"&&(await h(),ae.set(o,t.uuid),await Oe(t.uuid));break}case"content_block_delta":{const o=i.delta,p=`${de??t.session_id}:${i.index}`;if(o.type==="text_delta"){await h(),se=!0;const u=re.get(p);if(!u){B.warn("Ignoring Claude text delta without content_block_start",{conversationId:w,blockKey:p});break}await d({type:"text-delta",id:u,delta:o.text})}else if(o.type==="thinking_delta"){await h(),le=!0;const u=ae.get(p);if(!u){B.warn("Ignoring Claude reasoning delta without content_block_start",{conversationId:w,blockKey:p});break}await d({type:"reasoning-delta",id:u,delta:o.thinking})}break}case"content_block_stop":break}return!1}case"assistant":{pe=Et(t.message)??pe,t.session_id&&t.uuid&&await d({type:"data",kind:"agent_session",data:{agentType:"claude",sessionId:t.session_id,messageId:t.uuid}});const i=t.message.content;if(typeof i=="string")break;for(const o of i)switch(o.type){case"thinking":{if(le||!o.thinking)break;await h(),await Oe(t.uuid),await d({type:"reasoning-delta",id:t.uuid,delta:o.thinking});break}case"text":{if(se||!o.text)break;await h(),await De(t.uuid),await d({type:"text-delta",id:t.uuid,delta:o.text});break}case"tool_use":{if(await Y(),ie.set(o.id,o.name),E.isTaskTool(o.name)){E.applyToolUse(o.id,o.name,o.input)&&(G=!0);break}await h(),await Y(),await d({type:"tool-input-available",toolCallId:o.id,toolName:o.name,input:o.input});break}}return await Y(),re.clear(),ae.clear(),!1}case"user":{const i=t.message.content;if(typeof i=="string")break;const o=i.filter(u=>u.type==="tool_result"),p=o.length===1?Bt(t.tool_use_result):void 0;for(const u of o){const c=u.tool_use_id,ve=u.content;if(E.isTaskToolCall(c)){E.applyToolResult(c,ve)&&(G=!0);continue}if(await h(),u.is_error===!0)await d({type:"tool-output-error",toolCallId:c,errorText:Ht(ve)});else{const Be=ie.get(c);await d({type:"tool-output-available",toolCallId:c,output:Be&&jt(Be)&&p!==void 0?p:ve})}ie.delete(c)}return!1}case"result":{if(be.size>0)return t.origin?.kind!=="task-notification"&&(Se=!0),!1;if(dn(t)&&!Se)return!1;ue=t.subtype==="success"||k.wasCancelled?"stop":"error";const i=Pt(t,pe);return ce=ue==="stop"?i:void 0,await h(),t.subtype!=="success"&&!k.wasCancelled&&await d({type:"error",errorText:t.errors?.filter(o=>o.trim()).join(`
7
+ `)||`Claude Code failed: ${t.subtype}`}),at()}}return!1},ye={threadRef:x,matrixEvent:Ge},k=M??X.create({conversationId:z,workdir:J,model:D,agentMode:L,managedTaskContext:ye,llmProxyBinding:P,buildOptions:(t,a,i)=>({abortController:t,cwd:J,includePartialMessages:!0,model:D,maxTurns:y.claude.maxTurns,systemPrompt:{type:"preset",preset:"claude_code",append:ft},...Mt(L),tools:{type:"preset",preset:"claude_code"},mcpServers:{[He]:St({workspaceRoot:_e,context:i})},disallowedTools:["EnterWorktree","ExitWorktree"],...K?{resume:K.sessionId,resumeSessionAt:K.messageId,forkSession:!0}:q?{resume:q}:null,canUseTool:a,env:{...process.env,...y.claude.apiKey?{ANTHROPIC_API_KEY:y.claude.apiKey}:null,...y.claude.baseUrl?{ANTHROPIC_BASE_URL:y.claude.baseUrl}:null,...P?{ANTHROPIC_CUSTOM_HEADERS:[process.env.ANTHROPIC_CUSTOM_HEADERS,`X-Elevo-LLM-Binding: ${P}`].filter(Boolean).join(`
8
+ `)}:null,CLAUDE_AGENT_SDK_CLIENT_APP:"coding-agent/claude-agent"}})});Nt(z,P),k.updateManagedTaskContext(ye);const Z=()=>k.abort();let je=!1;try{g&&m&&(await m.attach({writeData:async()=>{},writeDone:async()=>{}}),g.kind==="ask_user"?Le(g.toolCallId,{interrupted:!0}):qe(g.toolCallId,{interrupted:!0}),await m.done),l&&(!m||g)&&(l.kind==="ask_user"?await d(g?{type:"tool-output-error",toolCallId:l.toolCallId,toolName:l.toolName,errorText:"Question interrupted by text response"}:{type:"tool-output-available",toolCallId:l.toolCallId,output:Kt(l,f,b)}):await d(g?{type:"tool-output-error",toolCallId:l.toolCallId,toolName:l.toolName,errorText:"Approval interrupted by text response"}:{type:"tool-output-available",toolCallId:l.toolCallId,output:Je(l,f,b)}),(!g||!m)&&We(l.toolCallId));const t=l&&!m&&!g?l.kind==="ask_user"?zt(f,b):Yt(l,f,b):b,a=await k.reserveTurn(t,st,it,{model:D,agentMode:L,managedTaskContext:ye});if(a.kind==="interjection"){T&&clearTimeout(T),await I.writeDone();return}je=!0,W?.aborted&&Z(),W?.addEventListener("abort",Z,{once:!0}),await Promise.race([a.done,Ze]),!j&&!oe&&!v&&await H(k.wasCancelled||!k.isDead?"stop":"error")}catch(t){v?(B.error("Claude stream failed after downstream closed:",t),F()):(await Re(w),W?.aborted||k.wasCancelled?await H("stop"):k.isDead?await H("error"):(B.error("Claude stream failed:",t),await d({type:"error",errorText:t instanceof Error?t.message:String(t)}),await H("error")))}finally{je&&W?.removeEventListener("abort",Z),T&&clearTimeout(T),Q.delete(he),j||Ne(),j||F()}}}}async function Rn(e,n){await(await ze(e)).run(n,e.signal)}ee.post("/v1/chat/stream",ct("json",xt),async e=>{let n;try{n=await ze({body:e.req.valid("json"),headers:e.req.raw.headers})}catch(r){if(r instanceof U)return e.json({error:r.message},r.status);throw r}return pt(e,async r=>{await n.run({writeData:async s=>{await r.writeSSE({data:JSON.stringify(s)})},writeDone:async()=>{await r.writeSSE({data:"[DONE]"})}},e.req.raw.signal)})});var Nn=ee;export{U as ChatRequestError,pn as cancelClaudeConversation,Ut as claudeActivitySnapshot,Nn as default,Rn as handleClaudeChatStream,ze as prepareClaudeChatStream};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-agent/claude-agent",
3
- "version": "0.26.0-alpha.2",
3
+ "version": "0.26.0-alpha.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "ws": "^8.21.0",
20
20
  "yaml": "^2.9.0",
21
21
  "zod": "^4.4.3",
22
- "@messenger-agent/shared": "0.26.0-alpha.2"
22
+ "@messenger-agent/shared": "0.26.0-alpha.3"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/ws": "^8.18.1"