@messenger-agent/claude-agent 0.38.0-alpha.2 → 0.38.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/routes/chat.js +7 -7
- package/package.json +2 -2
package/dist/routes/chat.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{randomUUID as We}from"node:crypto";import{existsSync as
|
|
2
|
-
`).trim();if(!r)throw new Te(400,{message:"No supported message content found"});return r}function
|
|
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
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
|
|
5
|
-
`)}return Ie(e)}function
|
|
6
|
-
`)}function un(e){return!!(e&&e.state==="output-available"&&(e.toolName==="AskUserQuestion"||e.toolName==="ask_user_question"))}function cn(e){return!!(e&&e.state==="approval-responded")}async function pn(e,n){return e?e.state==="approval-responded"?e.approval.reason??"":Ve(e.output):Yt(n)}function fn(e,n){return kt(c,e,n)}function mn(e){return e==="AskUserQuestion"}function wn(e){return e==="ExitPlanMode"}function yn(e){return e==="Agent"||e==="Task"}function vn(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 kn(e){return"session_id"in e?e.session_id:void 0}function gn(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 Tn(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 _n(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 In(n)?e.json({success:!0}):e.json({error:zt(n)},404)});function In(e){return X.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=Jt();return e.json({activeTaskCount:r.active,waitingClientToolCount:n.length,clientToolWaits:n})});async function Ze({body:e,headers:n}){const r=h(n,"X-Matrix-Workdir"),l=h(n,"X-Matrix-Workspace-ID"),p=Tn(n),J=_n(n);let x,z;try{x=fn(l,r),z=gt(c,l)}catch(C){throw new B(400,C instanceof Error?C.message:"Invalid workspace")}if(r&&!ct(x))throw new B(400,`Workdir not found: ${x}`);if(pt(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=Zt(e.message.parts),s=w&&(un(w)||cn(w))?ht(w.toolCallId):e.conversationId?It(e.conversationId):void 0,y=s?E.get(s.conversationId):void 0,S=await pn(w,e.message.parts),g=w?void 0:s,L=s&&!y?s.conversationId:e.conversationId,re=h(n,"X-Elevo-LLM-Binding")?.trim()||void 0;let M=V?void 0:X.get(L);const ae=M?.llmProxyBinding??Lt(L);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||c.claude.defaultModel,be=e.effort,G=s&&!y?s.workdir:x,$=s?.kind==="approval"&&he(s,w,S).approved?"default":s?.agentMode?ze(s.agentMode):ze(h(n,"X-Matrix-Agent-Mode")),R=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 f=>{i&&clearTimeout(i),await C.writeData(f),a||(i=setTimeout(()=>{C.writeData({type:"heartbeat"}).catch(()=>{})},6e4))},u=async f=>{a||await m(f)};try{if(await y.attach({writeData:u,writeDone:o}),s.kind==="ask_user")Ke(s.toolCallId,rn(s.input,w,S));else{const f=he(s,w,S);f.approved&&await X.get(s.conversationId)?.setPermissionMode(Nt()),Fe(s.toolCallId,f)}await y.done}catch(f){q.error("Claude live ask continuation failed:",f),await u({type:"error",errorText:f instanceof Error?f.message:String(f)}),await o()}finally{i&&clearTimeout(i),Q.delete(t)}return}const xe={};Q.add(xe);const Y=L??We();let v=Y,Se=!1,Ae=!1,ie=!1,O=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=Pt();let Z=!1,et=0,U=!1;const ye=new Set;let k=!1,ee,ve=!1,A,F;const tt=new Promise(t=>{F=t});let Pe;const nt=new Promise(t=>{Pe=t}),De=()=>{T=setTimeout(()=>{d({type:"heartbeat"}).catch(()=>{})},6e4)},ot=async t=>{T&&clearTimeout(T),await C.writeData(t),k||De()};ee={writeData:async t=>{k||await ot(t)},writeDone:async()=>{k||(k=!0,T&&(clearTimeout(T),T=void 0),await C.writeDone())}};const d=async t=>{k||await ee.writeData(t)},Re=async()=>{k||(await ee.writeDone(),k=!0)},rt=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}})},K=async t=>{if(!(ie||k)){ie=!0;for(const a of O)await d({type:"text-end",id:a});O=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 Re()}},at=async()=>{for(const t of O)await d({type:"text-end",id:t});O=new Set},it=async()=>{for(const t of N)await d({type:"reasoning-end",id:t});N=new Set},te=async()=>{await at(),await it()},b=async()=>{if(!Z)return;Z=!1,D.clearDirty(),await te();const t=`todo-${Y}-${et++}`;await d({type:"tool-input-available",toolCallId:t,toolName:"TodoWrite",input:D.snapshot()}),await d({type:"tool-output-available",toolCallId:t,output:"Task updated"})},Oe=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 Re(),A=setTimeout(()=>{q.warn(`Claude ${a} timed out after ${c.claude.askTimeoutMs}ms`,{conversationId:v,toolCallId:t}),E.delete(v),_.abort(),F()},c.claude.askTimeoutMs),A.unref?.(),Pe())},Ne=async t=>{O.has(t)||(O.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}))},st=t=>{if(t===v)return;const a=v;v=t,X.bind(_,t),$t(a,t,P),xt(a,t);const i=E.get(a);i&&(E.delete(a),E.set(t,i))},Ue=async t=>{Se||(Se=!0,st(t),ve||(ve=!0,E.set(t,{attach:async a=>{A&&(clearTimeout(A),A=void 0),ee=a,k=!1,U=!1},done:tt,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)Xe(t,{rejectWait:!0})},lt=async()=>(await K(fe),qe(),F(),!0);De();const dt=async(t,a,i)=>{const{toolUseID:o}=i;if(mn(t)){Ct({conversationId:v,toolCallId:o,workdir:G,model:R,effort:W,agentMode:$,toolName:"AskUserQuestion",input:a}),await Oe(o,"AskUserQuestion",a);const m=await At(o);return m?"interrupted"in m?{behavior:"deny",message:"Question interrupted by text response",interrupt:!0,toolUseID:o}:{behavior:"allow",updatedInput:{...a,questions:Ge(a),answers:m.answers},toolUseID:o}:{behavior:"deny",message:`${t} requires user input; session will resume after user responds.`,toolUseID:o}}if(wn(t)){const m=bt({conversationId:v,toolCallId:o,workdir:G,model:R,effort:W,agentMode:$,toolName:"ExitPlanMode",input:a});await Oe(o,"ExitPlanMode",a,m);const u=await St(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 yn(t)?{behavior:"allow",updatedInput:{...a,model:R},toolUseID:o}:{behavior:"allow",toolUseID:o}},ut=async t=>{if(vn(t))return!1;const a=kn(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 rt(),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=Ut(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?Qt(t.tool_use_result):void 0;for(const u of o){const f=u.tool_use_id,ge=u.content;if(D.isTaskToolCall(f)){D.applyToolResult(f,ge)&&(Z=!0);continue}if(await b(),u.is_error===!0)await d({type:"tool-output-error",toolCallId:f,errorText:en(ge)});else{const $e=de.get(f);await d({type:"tool-output-available",toolCallId:f,output:$e&&Xt($e)&&m!==void 0?m:ge})}de.delete(f)}return!1}case"result":{if(Ee.size>0)return t.origin?.kind!=="task-notification"&&(Me=!0),!1;if(gn(t)&&!Me)return!1;fe=t.subtype==="success"||_.wasCancelled?"stop":"error";const i=qt(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}`}),
|
|
8
|
-
`)}:null,CLAUDE_AGENT_SDK_CLIENT_APP:"coding-agent/claude-agent"}})});
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@messenger-agent/claude-agent",
|
|
3
|
-
"version": "0.38.0-alpha.
|
|
3
|
+
"version": "0.38.0-alpha.4",
|
|
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.38.0-alpha.
|
|
22
|
+
"@messenger-agent/shared": "0.38.0-alpha.4"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/ws": "^8.18.1"
|