@messenger-agent/claude-agent 0.38.0-alpha.0 → 0.38.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/routes/chat.js +6 -6
- 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 ct,mkdirSync as pt}from"node:fs";import{join as ft}from"node:path";import{Hono as mt}from"hono";import{HTTPException as Te}from"hono/http-exception";import{zValidator as wt}from"@hono/zod-validator";import{streamSSE as yt}from"hono/streaming";import{loadClaudePlatformInstructions as vt}from"../platform-instructions.js";import{logger 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 ct,mkdirSync as pt}from"node:fs";import{join as ft}from"node:path";import{Hono as mt}from"hono";import{HTTPException as Te}from"hono/http-exception";import{zValidator as wt}from"@hono/zod-validator";import{streamSSE as yt}from"hono/streaming";import{loadClaudePlatformInstructions as vt}from"../platform-instructions.js";import{logger as q}from"@messenger-agent/shared/logger";import{appConfig as c}from"../config.js";import{resolveAgentWorkdir as kt,resolveAgentWorkspaceRoot as gt}from"@messenger-agent/shared/agent-workspaces";import{isManagedTaskToolName as He}from"@messenger-agent/shared/managed-task-tools";import{saveUploadedFilePart as Tt}from"@messenger-agent/shared/uploaded-files";import{WakeupIdempotencyStore as _t}from"@messenger-agent/shared/wakeup-idempotency";import{answerPendingApproval as Fe,answerPendingAsk as Ke,deletePendingClientTool as Xe,findPendingClientToolByConversation as It,listPendingClientTools as Qe,loadPendingClientTool as ht,savePendingAsk as Ct,savePendingApproval as bt,updatePendingAskConversation as xt,waitPendingApproval as St,waitPendingAsk as At}from"../pending-asks.js";import{AgentAnswerToolPartSchema as Et,ChatBodySchema as Mt}from"../schemas.js";import{createTaskAggregator as Pt}from"../task-aggregator.js";import{createManagedTaskMcpServer as Dt,MANAGED_TASK_MCP_SERVER_NAME as Je}from"../managed-task-mcp.js";import{createScheduleMcpServer as Rt,SCHEDULE_MCP_SERVER_NAME as Ot}from"../schedule-mcp.js";import{claudeExecutionPermissionMode as Nt,claudePermissionOptions as jt}from"../permission.js";import{claudeContextSnapshot as Ut,claudeTokenUsage as qt}from"../token-usage.js";import{SessionManager as Bt}from"../session-manager.js";import{getLlmProxyBinding as Lt,moveLlmProxyBinding as $t,setLlmProxyBinding as Wt}from"../llm-proxy-bindings.js";const oe=new mt;let Ht;function _e(){return Ht??=new _t(ft(c.dataDir,"wakeup-idempotency.sqlite"))}const X=new Bt(()=>c.claude.sessionIdleTimeoutMs??36e5),Ft=vt(void 0,c.schedule?.enabled===!0),Q=new Set;function Kt(){const e={...c.claude.apiKey?{ANTHROPIC_API_KEY:c.claude.apiKey}:null,...c.claude.baseUrl?{ANTHROPIC_BASE_URL:c.claude.baseUrl}:null};return Object.keys(e).length>0?{env:e}:void 0}function Xt(e){if(He(e))return!0;const n=`mcp__${Je}__`;return e.startsWith(n)&&He(e.slice(n.length))}function Qt(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 Jt(){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 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 I(e){return!!(e&&typeof e=="object")}function Vt(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 Gt(e){if(e.type==="text"&&typeof e.text=="string")return e.text;if(Vt(e))return Tt(e,c.fileUploads.tempDir);if(e.type!=="step-start")throw new Te(400,{message:`Unsupported message part type: ${e.type}`})}async function Yt(e){const r=(await Promise.all(e.map(Gt))).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 Zt(e){for(const n of e){const r=Et.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
4
|
`)}return JSON.stringify(e)}function Ve(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
|
|
6
|
-
`)}function dn(e){return!!(e&&e.state==="output-available"&&(e.toolName==="AskUserQuestion"||e.toolName==="ask_user_question"))}function un(e){return!!(e&&e.state==="approval-responded")}async function cn(e,n){return e?e.state==="approval-responded"?e.approval.reason??"":Ve(e.output):Gt(n)}function pn(e,n){return kt(f,e,n)}function fn(e){return e==="AskUserQuestion"}function mn(e){return e==="ExitPlanMode"}function wn(e){return e==="Agent"||e==="Task"}function yn(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 vn(e){return"session_id"in e?e.session_id:void 0}function kn(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 gn(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 Tn(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 _n(n)?e.json({success:!0}):e.json({error:Jt(n)},404)});function _n(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=Qt();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"),c=gn(n),J=Tn(n);let x,z;try{x=pn(l,r),z=gt(f,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=Yt(e.message.parts),s=w&&(dn(w)||un(w))?ht(w.toolCallId):e.conversationId?It(e.conversationId):void 0,y=s?M.get(s.conversationId):void 0,S=await cn(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 E=V?void 0:K.get($);const ae=E?.llmProxyBinding??$t($);if(re&&ae&&re!==ae)throw new B(409,"LLM proxy binding does not match the existing conversation");const P=ae??re;if(E&&P&&!E.llmProxyBinding){if(E.hasActiveOwner())throw new B(409,"Cannot attach an LLM proxy binding while the conversation is active");E.abort(),E=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?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,F)=>{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,on(s.input,w,S));else{const p=he(s,w,S);p.approved&&await K.get(s.conversationId)?.setPermissionMode(Ot()),He(s.toolCallId,p)}await y.done}catch(p){U.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,N=new Set,O=new Set;const se=new Map,le=new Map,de=new Map;let ue=!1,ce=!1,pe,fe="stop",me,we,j,Me=new Set,Ee=!1,T;const D=Pt();let Z=!1,et=0,q=!1;const ye=new Set;let k=!1,ee,ve=!1,A,H;const tt=new Promise(t=>{H=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}})},X=async t=>{if(!(ie||k)){ie=!0;for(const a of N)await d({type:"text-end",id:a});N=new Set;for(const a of O)await d({type:"reasoning-end",id:a});O=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 N)await d({type:"text-end",id:t});N=new Set},it=async()=>{for(const t of O)await d({type:"reasoning-end",id:t});O=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"})},Ne=async(t,a,i,o)=>{ye.has(t)||(ye.add(t),q=!0,F?.removeEventListener("abort",ne),o&&await d({type:"tool-approval-request",toolCallId:t,approveId:o.approveId}),await Re(),A=setTimeout(()=>{U.warn(`Claude ${a} timed out after ${f.claude.askTimeoutMs}ms`,{conversationId:v,toolCallId:t}),M.delete(v),_.abort(),H()},f.claude.askTimeoutMs),A.unref?.(),Pe())},Oe=async t=>{N.has(t)||(N.add(t),await d({type:"text-start",id:t}))},je=async t=>{O.has(t)||(O.add(t),await d({type:"reasoning-start",id:t}))},st=t=>{if(t===v)return;const a=v;v=t,K.bind(_,t),Lt(a,t,P),xt(a,t);const i=M.get(a);i&&(M.delete(a),M.set(t,i))},qe=async t=>{Se||(Se=!0,st(t),ve||(ve=!0,M.set(t,{attach:async a=>{A&&(clearTimeout(A),A=void 0),ee=a,k=!1,q=!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)},Ue=()=>{ve&&(M.get(v)?.dispose(),M.delete(v));for(const t of ye)Ke(t,{rejectWait:!0})},lt=async()=>(await X(fe),Ue(),H(),!0);De();const dt=async(t,a,i)=>{const{toolUseID:o}=i;if(fn(t)){Ct({conversationId:v,toolCallId:o,workdir:G,model:R,effort:W,agentMode:L,toolName:"AskUserQuestion",input:a}),await Ne(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(mn(t)){const m=bt({conversationId:v,toolCallId:o,workdir:G,model:R,effort:W,agentMode:L,toolName:"ExitPlanMode",input:a});await Ne(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 wn(t)?{behavior:"allow",updatedInput:{...a,model:R},toolUseID:o}:{behavior:"allow",toolUseID:o}},ut=async t=>{if(yn(t))return!1;const a=vn(t);if(a&&(await qe(a),D.setSessionId(a)),t.type==="system"&&t.subtype==="background_tasks_changed")return Me=new Set(t.tasks.map(i=>i.task_id)),!1;if(q)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 Oe(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){U.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){U.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 Oe(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?Kt(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:Zt(ge)});else{const Le=de.get(p);await d({type:"tool-output-available",toolCallId:p,output:Le&&Xt(Le)&&m!==void 0?m:ge})}de.delete(p)}return!1}case"result":{if(Me.size>0)return t.origin?.kind!=="task-notification"&&(Ee=!0),!1;if(kn(t)&&!Ee)return!1;fe=t.subtype==="success"||_.wasCancelled?"stop":"error";const i=Ut(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}`}),lt()}}return!1},Be={threadRef:
|
|
8
|
-
`)}:null,CLAUDE_AGENT_SDK_CLIENT_APP:"coding-agent/claude-agent"}})});Wt(Y,P);const ne=()=>_.abort();let
|
|
5
|
+
`)}return Ie(e)}function en(e){return typeof e=="string"?e:JSON.stringify(e)}function tn(e,n){return I(e)&&typeof e.question=="string"?e.question:`Question ${n+1}`}function Ge(e){return I(e)&&Array.isArray(e.questions)?e.questions:[]}function nn(e,n){const r=Ge(e);return I(n)&&I(n.answers)?Object.fromEntries(Object.entries(n.answers).map(([l,p])=>[l,String(p)])):r.length>0?Object.fromEntries(r.map((l,p)=>[tn(l,p),Ie(n)])):{answer:Ie(n)}}function on(e,n,r){return n&&n.state==="output-available"?n.output:{answers:r}}function rn(e,n,r){return n&&n.state==="output-available"?{answers:nn(e,n.output),output:n.output}:{answers:r}}function an(e,n){return!e||e.state!=="output-available"?`User answered previous question: ${n}`:Ve(e.output)}function sn(e){return I(e)&&typeof e.plan=="string"?e.plan:JSON.stringify(e)}function ln(e){return{approved:!1,reason:e.trim()||void 0}}function Ye(e,n,r){return n&&n.state==="approval-responded"?n.approval:{id:e.approveId,...ln(r)}}function he(e,n,r){const l=Ye(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 dn(e,n,r){const l=he(e,n,r),p=[l.approved?`User approved tool ${e.toolName} plan:`:`User rejected tool ${e.toolName} plan:`,sn(e.input)];return l.reason&&p.push(`Reason: ${l.reason}`),p.join(`
|
|
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}`}),lt()}}return!1},Be={threadRef:p,matrixEvent:J,llmProxyBinding:P},_=M??X.create({conversationId:Y,workdir:G,model:R,effort:W,agentMode:$,managedTaskContext:Be,llmProxyBinding:P,buildOptions:(t,a,i)=>({abortController:t,cwd:G,includePartialMessages:!0,model:R,effort:W,maxTurns:c.claude.maxTurns,settings:Kt(),systemPrompt:{type:"preset",preset:"claude_code",append:Ft},...jt($),tools:{type:"preset",preset:"claude_code"},mcpServers:{[Je]:Dt({workspaceRoot:z,context:i}),...c.schedule?.enabled===!0?{[Ot]:Rt({config:c.schedule,context:i})}:null},disallowedTools:["EnterWorktree","ExitWorktree","EnterPlanMode","ExitPlanMode"],...V?{resume:V.sessionId,resumeSessionAt:V.messageId,forkSession:!0}:L?{resume:L}:null,canUseTool:a,env:{...process.env,...c.claude.apiKey?{ANTHROPIC_API_KEY:c.claude.apiKey}:null,...c.claude.baseUrl?{ANTHROPIC_BASE_URL:c.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"}})});Wt(Y,P);const ne=()=>_.abort();let Le=!1;try{g&&y&&(await y.attach({writeData:async()=>{},writeDone:async()=>{}}),g.kind==="ask_user"?Ke(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:on(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:Ye(s,w,S)}),(!g||!y)&&Xe(s.toolCallId));const t=s&&!y&&!g?s.kind==="ask_user"?an(w,S):dn(s,w,S):S,a=await _.reserveTurn(t,ut,dt,{model:R,effort:W,agentMode:$,managedTaskContext:Be});if(a.kind==="interjection"){T&&clearTimeout(T),await C.writeDone();return}Le=!0,H?.aborted&&ne(),H?.addEventListener("abort",ne,{once:!0}),await Promise.race([a.done,nt]),!U&&!ie&&!k&&await K(_.wasCancelled||!_.isDead?"stop":"error")}catch(t){k?(q.error("Claude stream failed after downstream closed:",t),F()):(await Ue(v),H?.aborted||_.wasCancelled?await K("stop"):_.isDead?await K("error"):(q.error("Claude stream failed:",t),await d({type:"error",errorText:t instanceof Error?t.message:String(t)}),await K("error")))}finally{Le&&H?.removeEventListener("abort",ne),T&&clearTimeout(T),Q.delete(xe),U||qe(),U||F()}}}}async function Qn(e,n){await(await Ze(e)).run(n,e.signal)}oe.post("/v1/chat/stream",wt("json",Mt),async e=>{const n=h(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 Ze({body:e.req.valid("json"),headers:e.req.raw.headers})}catch(p){if(n&&r&&_e().release(n,r),p instanceof B)return e.json({error:p.message},p.status);throw p}return yt(e,async p=>{let J=!1;const x=()=>{!n||!r||J||(J=!0,_e().markStarted(n,r))};await l.run({writeData:async z=>{x(),await p.writeSSE({data:JSON.stringify(z)})},writeDone:async()=>{x(),await p.writeSSE({data:"[DONE]"})}},e.req.raw.signal)})});var Jn=oe;export{B as ChatRequestError,In as cancelClaudeConversation,Jt as claudeActivitySnapshot,Jn as default,Qn 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.38.0-alpha.
|
|
3
|
+
"version": "0.38.0-alpha.2",
|
|
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.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/ws": "^8.18.1"
|