@messenger-agent/claude-agent 0.39.0-alpha.2 → 0.40.0-alpha.0

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