@messenger-agent/claude-agent 0.27.0 → 0.29.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{existsSync as r,readFileSync as i}from"node:fs";import{fileURLToPath as n}from"node:url";import{logger as c}from"@messenger-agent/shared/logger";const d=[n(new URL("./assets/codex-home/AGENTS.md",import.meta.url)),n(new URL("../../../assets/codex-home/AGENTS.md",import.meta.url)),"/app/assets/codex-home/AGENTS.md"],m=["search_managed_tasks","get_managed_task","create_managed_task","update_managed_task","apply_managed_task_patch","delete_managed_task"],l=["## Claude Managed Task Tools","","When the user asks to create, update, inspect, continue, summarize, or delete managed tasks, use the managed task MCP tools. The available tool basenames are: "+m.map(e=>`\`${e}\``).join(", ")+". Claude may expose them with an MCP namespace such as `mcp__managed_tasks__<tool>`.",""].join(`
2
- `),u=["## Response Style","","Do not use emoji in responses.","Use Markdown headings, bold, and italic formatting sparingly.","Other Markdown formatting, such as lists, inline code, links, and code blocks, may be used as needed.",""].join(`
3
- `),_=["## Schedule Tool","","Use the single schedule MCP tool to create, list, inspect, update, pause, resume, or cancel schedules in the current trusted Matrix context. Select the action inside `input.action`; never ask for or invent a Room ID, actor ID, source event, or service credential. After create or update succeeds, repeat the service's normalized rule, absolute next run time, IANA timezone, message, and mention MXIDs to the user so relative times and recurrence are unambiguous.",""].join(`
4
- `);function A(e,o=!1){const t=`${l}
5
- ${o?`${_}
6
- `:""}${u}`,a=[e??process.env.CLAUDE_MEMORY_TEMPLATE_PATH,...d].find(s=>!!(s&&r(s)));return a?`${i(a,"utf-8").trimEnd()}
1
+ import{existsSync as r,readFileSync as i}from"node:fs";import{fileURLToPath as n}from"node:url";import{logger as c}from"@messenger-agent/shared/logger";const l=[n(new URL("./assets/codex-home/AGENTS.md",import.meta.url)),n(new URL("../../../assets/codex-home/AGENTS.md",import.meta.url)),"/app/assets/codex-home/AGENTS.md"],d=["search_managed_tasks","get_managed_task","create_managed_task","update_managed_task","apply_managed_task_patch","delete_managed_task"],m=["## Claude Managed Task Tools","","When the user asks to create, update, inspect, continue, summarize, or delete managed tasks, use the managed task MCP tools. The available tool basenames are: "+d.map(e=>`\`${e}\``).join(", ")+". Claude may expose them with an MCP namespace such as `mcp__managed_tasks__<tool>`.",""].join(`
2
+ `),p=["## Response Style","","Do not use emoji in responses.","Use Markdown headings, bold, and italic formatting sparingly.","Other Markdown formatting, such as lists, inline code, links, and code blocks, may be used as needed.",""].join(`
3
+ `),u=["## Schedule Tool","","Use the single schedule MCP tool to prepare create/update proposals or list, inspect, pause, resume, and cancel schedules in the current trusted Matrix context. Select the action inside `input.action`; never ask for or invent a Room ID, actor ID, source event, or service credential. A successful create/update result is a proposal awaiting one explicit Messenger confirmation, not an active schedule. Do not ask for a chat reply or invoke another confirmation tool.",""].join(`
4
+ `);function A(e,s=!1){const a=`${m}
5
+ ${s?`${u}
6
+ `:""}${p}`,t=[e??process.env.CLAUDE_MEMORY_TEMPLATE_PATH,...l].find(o=>!!(o&&r(o)));return t?`${i(t,"utf-8").trimEnd()}
7
7
 
8
- ${t}`:(c.warn("Claude platform instructions template not found"),t)}export{A as loadClaudePlatformInstructions};
8
+ ${a}`:(c.warn("Claude platform instructions template not found"),a)}export{A as loadClaudePlatformInstructions};
@@ -1,8 +1,8 @@
1
- import{randomUUID as $e}from"node:crypto";import{existsSync as ut,mkdirSync as ct}from"node:fs";import{Hono as pt}from"hono";import{HTTPException as ke}from"hono/http-exception";import{zValidator as ft}from"@hono/zod-validator";import{streamSSE as mt}from"hono/streaming";import{loadClaudePlatformInstructions as wt}from"../platform-instructions.js";import{logger as U}from"@messenger-agent/shared/logger";import{appConfig as f}from"../config.js";import{resolveAgentWorkdir as yt,resolveAgentWorkspaceRoot as vt}from"@messenger-agent/shared/agent-workspaces";import{isManagedTaskToolName as Le}from"@messenger-agent/shared/managed-task-tools";import{saveUploadedFilePart as kt}from"@messenger-agent/shared/uploaded-files";import{answerPendingApproval as We,answerPendingAsk as Fe,deletePendingClientTool as He,findPendingClientToolByConversation as gt,listPendingClientTools as Xe,loadPendingClientTool as Tt,savePendingAsk as _t,savePendingApproval as It,updatePendingAskConversation as ht,waitPendingApproval as Ct,waitPendingAsk as bt}from"../pending-asks.js";import{AgentAnswerToolPartSchema as xt,ChatBodySchema as St}from"../schemas.js";import{createTaskAggregator as At}from"../task-aggregator.js";import{createManagedTaskMcpServer as Mt,MANAGED_TASK_MCP_SERVER_NAME as Qe}from"../managed-task-mcp.js";import{createScheduleMcpServer as Et,SCHEDULE_MCP_SERVER_NAME as Pt}from"../schedule-mcp.js";import{claudeExecutionPermissionMode as Dt,claudePermissionOptions as Rt}from"../permission.js";import{claudeContextSnapshot as Nt,claudeTokenUsage as Ot}from"../token-usage.js";import{SessionManager as jt}from"../session-manager.js";import{getLlmProxyBinding as Ut,moveLlmProxyBinding as Bt,setLlmProxyBinding as qt}from"../llm-proxy-bindings.js";const te=new pt,Q=new jt(()=>f.claude.sessionIdleTimeoutMs??36e5),$t=wt(void 0,f.schedule?.enabled===!0),K=new Set;function Lt(e){if(Le(e))return!0;const n=`mcp__${Qe}__`;return e.startsWith(n)&&Le(e.slice(n.length))}function Wt(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 Ft(){const e=new Set(Xe().map(n=>n.conversationId));return{active:K.size,waiting:e.size}}class B extends Error{status;constructor(n,r){super(r),this.status=n,this.name="ChatRequestError"}}const A=new Map;function Ke(e){return e?.trim().toLowerCase()==="plan"?"plan":"default"}function Ht(e){return{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${e}`,param:"conversation_id"}}function _(e){return!!(e&&typeof e=="object")}function Xt(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 Qt(e){if(e.type==="text"&&typeof e.text=="string")return e.text;if(Xt(e))return kt(e,f.fileUploads.tempDir);if(e.type!=="step-start")throw new ke(400,{message:`Unsupported message part type: ${e.type}`})}async function Kt(e){const r=(await Promise.all(e.map(Qt))).filter(l=>!!(l&&l.trim().length>0)).join(`
2
- `).trim();if(!r)throw new ke(400,{message:"No supported message content found"});return r}function Jt(e){for(const n of e){const r=xt.safeParse(n);if(r.success)return r.data}}function ge(e){if(typeof e=="string")return e;if(_(e)){if(typeof e.answer=="string")return e.answer;if(typeof e.text=="string")return e.text;if(_(e.answers))return Object.values(e.answers).map(n=>String(n)).join(`
3
- `);if(Array.isArray(e.answers))return e.answers.map(n=>{if(typeof n=="string")return n;if(_(n)){if(typeof n.answer=="string")return n.answer;if(typeof n.text=="string")return n.text}return JSON.stringify(n)}).join(`
4
- `)}return JSON.stringify(e)}function Je(e){if(_(e)&&_(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 ge(e)}function zt(e){return typeof e=="string"?e:JSON.stringify(e)}function Vt(e,n){return _(e)&&typeof e.question=="string"?e.question:`Question ${n+1}`}function ze(e){return _(e)&&Array.isArray(e.questions)?e.questions:[]}function Gt(e,n){const r=ze(e);return _(n)&&_(n.answers)?Object.fromEntries(Object.entries(n.answers).map(([l,b])=>[l,String(b)])):r.length>0?Object.fromEntries(r.map((l,b)=>[Vt(l,b),ge(n)])):{answer:ge(n)}}function Yt(e,n,r){return n&&n.state==="output-available"?n.output:{answers:r}}function Zt(e,n,r){return n&&n.state==="output-available"?{answers:Gt(e,n.output),output:n.output}:{answers:r}}function en(e,n){return!e||e.state!=="output-available"?`User answered previous question: ${n}`:Je(e.output)}function tn(e){return _(e)&&typeof e.plan=="string"?e.plan:JSON.stringify(e)}function nn(e){return{approved:!1,reason:e.trim()||void 0}}function Ve(e,n,r){return n&&n.state==="approval-responded"?n.approval:{id:e.approveId,...nn(r)}}function Te(e,n,r){const l=Ve(e,n,r);if(l.id!==e.approveId)throw new ke(400,{message:"Tool approval id does not match pending approval"});return{approved:l.approved,reason:l.reason}}function on(e,n,r){const l=Te(e,n,r),b=[l.approved?`User approved tool ${e.toolName} plan:`:`User rejected tool ${e.toolName} plan:`,tn(e.input)];return l.reason&&b.push(`Reason: ${l.reason}`),b.join(`
6
- `)}function rn(e){return!!(e&&e.state==="output-available"&&(e.toolName==="AskUserQuestion"||e.toolName==="ask_user_question"))}function an(e){return!!(e&&e.state==="approval-responded")}async function sn(e,n){return e?e.state==="approval-responded"?e.approval.reason??"":Je(e.output):Kt(n)}function ln(e,n){return yt(f,e,n)}function dn(e){return e==="AskUserQuestion"}function un(e){return e==="ExitPlanMode"}function cn(e){return e==="Agent"||e==="Task"}function pn(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 fn(e){return"session_id"in e?e.session_id:void 0}function mn(e){return e.subtype==="success"&&e.origin?.kind==="task-notification"&&e.num_turns===0&&e.result===""&&e.usage.input_tokens===0&&e.usage.output_tokens===0&&e.usage.cache_creation_input_tokens===0&&e.usage.cache_read_input_tokens===0&&Object.keys(e.modelUsage).length===0}function C(e,n){return e?.get(n)??void 0}function wn(e){const n=C(e,"X-Matrix-Room-Id")?.trim(),r=C(e,"X-Matrix-Thread-ID")?.trim();return n&&r?`${n}/${r}`:void 0}function yn(e){const n=C(e,"X-Matrix-Room-Id")?.trim(),r=C(e,"X-Matrix-Event-ID")?.trim(),l=C(e,"X-Matrix-Thread-ID")?.trim();if(!(!n||!r))return{roomId:n,eventId:r,...l?{threadRootEventId:l}:null}}te.post("/v1/chat/:conversation_id/cancel",e=>{const n=e.req.param("conversation_id");return vn(n)?e.json({success:!0}):e.json({error:Ht(n)},404)});function vn(e){return Q.cancel(e)}te.get("/admin/status",e=>{const n=Xe().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=Ft();return e.json({activeTaskCount:r.active,waitingClientToolCount:n.length,clientToolWaits:n})});async function Ge({body:e,headers:n}){const r=C(n,"X-Matrix-Workdir"),l=C(n,"X-Matrix-Workspace-ID"),b=wn(n),Ye=yn(n);let q,_e;try{q=ln(l,r),_e=vt(f,l)}catch(I){throw new B(400,I instanceof Error?I.message:"Invalid workspace")}if(r&&!ut(q))throw new B(400,`Workdir not found: ${q}`);if(ct(q,{recursive:!0}),e.fork&&!("sessionId"in e.fork))throw new B(400,"Claude fork requires sessionId and messageId");const J=e.fork&&"sessionId"in e.fork?e.fork:void 0,m=Jt(e.message.parts),s=m&&(rn(m)||an(m))?Tt(m.toolCallId):e.conversationId?gt(e.conversationId):void 0,w=s?A.get(s.conversationId):void 0,x=await sn(m,e.message.parts),k=m?void 0:s,$=s&&!w?s.conversationId:e.conversationId,ne=C(n,"X-Elevo-LLM-Binding")?.trim()||void 0;let M=J?void 0:Q.get($);const oe=M?.llmProxyBinding??Ut($);if(ne&&oe&&ne!==oe)throw new B(409,"LLM proxy binding does not match the existing conversation");const E=oe??ne;if(M&&E&&!M.llmProxyBinding){if(M.hasActiveOwner())throw new B(409,"Cannot attach an LLM proxy binding while the conversation is active");M.abort(),M=void 0}const Ie=e.model||f.claude.defaultModel,he=e.effort,z=s&&!w?s.workdir:q,L=s?.kind==="approval"&&Te(s,m,x).approved?"default":s?.agentMode?Ke(s.agentMode):Ke(C(n,"X-Matrix-Agent-Mode")),D=s&&!w?s.model??Ie:Ie,W=s&&!w?s.effort??he:he;return{run:async(I,F)=>{if(s&&w&&!k){const t={};K.add(t);let a=!1,i;const o=async()=>{a||(a=!0,i&&(clearTimeout(i),i=void 0),await I.writeDone())},p=async c=>{i&&clearTimeout(i),await I.writeData(c),a||(i=setTimeout(()=>{I.writeData({type:"heartbeat"}).catch(()=>{})},6e4))},u=async c=>{a||await p(c)};try{if(await w.attach({writeData:u,writeDone:o}),s.kind==="ask_user")Fe(s.toolCallId,Zt(s.input,m,x));else{const c=Te(s,m,x);c.approved&&await Q.get(s.conversationId)?.setPermissionMode(Dt()),We(s.toolCallId,c)}await w.done}catch(c){U.error("Claude live ask continuation failed:",c),await u({type:"error",errorText:c instanceof Error?c.message:String(c)}),await o()}finally{i&&clearTimeout(i),K.delete(t)}return}const Ce={};K.add(Ce);const V=$??$e();let y=V,be=!1,xe=!1,re=!1,R=new Set,N=new Set;const ae=new Map,ie=new Map,se=new Map;let le=!1,de=!1,ue,ce="stop",pe,fe,O,Se=new Set,Ae=!1,g;const P=At();let G=!1,Ze=0,j=!1;const me=new Set;let v=!1,Y,we=!1,S,H;const et=new Promise(t=>{H=t});let Me;const tt=new Promise(t=>{Me=t}),Ee=()=>{g=setTimeout(()=>{d({type:"heartbeat"}).catch(()=>{})},6e4)},nt=async t=>{g&&clearTimeout(g),await I.writeData(t),v||Ee()};Y={writeData:async t=>{v||await nt(t)},writeDone:async()=>{v||(v=!0,g&&(clearTimeout(g),g=void 0),await I.writeDone())}};const d=async t=>{v||await Y.writeData(t)},Pe=async()=>{v||(await Y.writeDone(),v=!0)},ot=async()=>{O||(O={activityId:$e()},await d({type:"data",kind:"compact",data:{phase:"start",activityId:O.activityId}}))},ye=async(t,a={})=>{if(!O)return;const i=O;O=void 0,await d({type:"data",kind:"compact",data:{phase:"end",activityId:i.activityId,status:t,...a}})},X=async t=>{if(!(re||v)){re=!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,xe&&(t==="stop"&&pe&&await d({type:"data",kind:"token_usage",data:pe}),await d({type:"finish-step"})),await d({type:"finish",finishReason:t}),await Pe()}},rt=async()=>{for(const t of R)await d({type:"text-end",id:t});R=new Set},at=async()=>{for(const t of N)await d({type:"reasoning-end",id:t});N=new Set},Z=async()=>{await rt(),await at()},h=async()=>{if(!G)return;G=!1,P.clearDirty(),await Z();const t=`todo-${V}-${Ze++}`;await d({type:"tool-input-available",toolCallId:t,toolName:"TodoWrite",input:P.snapshot()}),await d({type:"tool-output-available",toolCallId:t,output:"Task updated"})},De=async(t,a,i,o)=>{me.has(t)||(me.add(t),j=!0,F?.removeEventListener("abort",ee),o&&await d({type:"tool-approval-request",toolCallId:t,approveId:o.approveId}),await Pe(),S=setTimeout(()=>{U.warn(`Claude ${a} timed out after ${f.claude.askTimeoutMs}ms`,{conversationId:y,toolCallId:t}),A.delete(y),T.abort(),H()},f.claude.askTimeoutMs),S.unref?.(),Me())},Re=async t=>{R.has(t)||(R.add(t),await d({type:"text-start",id:t}))},Ne=async t=>{N.has(t)||(N.add(t),await d({type:"reasoning-start",id:t}))},it=t=>{if(t===y)return;const a=y;y=t,Q.bind(T,t),Bt(a,t,E),ht(a,t);const i=A.get(a);i&&(A.delete(a),A.set(t,i))},Oe=async t=>{be||(be=!0,it(t),we||(we=!0,A.set(t,{attach:async a=>{S&&(clearTimeout(S),S=void 0),Y=a,v=!1,j=!1},done:et,dispose:()=>{S&&(clearTimeout(S),S=void 0)}})),await d({type:"start",messageMetadata:{conversationId:t,threadId:t}}),await d({type:"start-step"}),xe=!0)},je=()=>{we&&(A.get(y)?.dispose(),A.delete(y));for(const t of me)He(t,{rejectWait:!0})},st=async()=>(await X(ce),je(),H(),!0);Ee();const lt=async(t,a,i)=>{const{toolUseID:o}=i;if(dn(t)){_t({conversationId:y,toolCallId:o,workdir:z,model:D,effort:W,agentMode:L,toolName:"AskUserQuestion",input:a}),await De(o,"AskUserQuestion",a);const p=await bt(o);return p?"interrupted"in p?{behavior:"deny",message:"Question interrupted by text response",interrupt:!0,toolUseID:o}:{behavior:"allow",updatedInput:{...a,questions:ze(a),answers:p.answers},toolUseID:o}:{behavior:"deny",message:`${t} requires user input; session will resume after user responds.`,toolUseID:o}}if(un(t)){const p=It({conversationId:y,toolCallId:o,workdir:z,model:D,effort:W,agentMode:L,toolName:"ExitPlanMode",input:a});await De(o,"ExitPlanMode",a,p);const u=await Ct(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 cn(t)?{behavior:"allow",updatedInput:{...a,model:D},toolUseID:o}:{behavior:"allow",toolUseID:o}},dt=async t=>{if(pn(t))return!1;const a=fn(t);if(a&&(await Oe(a),P.setSessionId(a)),t.type==="system"&&t.subtype==="background_tasks_changed")return Se=new Set(t.tasks.map(i=>i.task_id)),!1;if(j)return!1;switch(t.type){case"system":return t.subtype==="status"?(t.status==="compacting"&&await ot(),t.compact_result==="success"?await ye("completed"):t.compact_result==="failed"&&await ye("failed",{error:t.compact_error})):t.subtype==="compact_boundary"&&await ye("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":{ue=i.message.id,le=!1,de=!1;break}case"content_block_start":{const o=`${ue??t.session_id}:${i.index}`;i.content_block.type==="text"?(await h(),ae.set(o,t.uuid),await Re(t.uuid)):i.content_block.type==="thinking"&&(await h(),ie.set(o,t.uuid),await Ne(t.uuid));break}case"content_block_delta":{const o=i.delta,p=`${ue??t.session_id}:${i.index}`;if(o.type==="text_delta"){await h(),le=!0;const u=ae.get(p);if(!u){U.warn("Ignoring Claude text delta without content_block_start",{conversationId:y,blockKey:p});break}await d({type:"text-delta",id:u,delta:o.text})}else if(o.type==="thinking_delta"){await h(),de=!0;const u=ie.get(p);if(!u){U.warn("Ignoring Claude reasoning delta without content_block_start",{conversationId:y,blockKey:p});break}await d({type:"reasoning-delta",id:u,delta:o.thinking})}break}case"content_block_stop":break}return!1}case"assistant":{fe=Nt(t.message)??fe,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(de||!o.thinking)break;await h(),await Ne(t.uuid),await d({type:"reasoning-delta",id:t.uuid,delta:o.thinking});break}case"text":{if(le||!o.text)break;await h(),await Re(t.uuid),await d({type:"text-delta",id:t.uuid,delta:o.text});break}case"tool_use":{if(await Z(),se.set(o.id,o.name),P.isTaskTool(o.name)){P.applyToolUse(o.id,o.name,o.input)&&(G=!0);break}await h(),await Z(),await d({type:"tool-input-available",toolCallId:o.id,toolName:o.name,input:o.input});break}}return await Z(),ae.clear(),ie.clear(),!1}case"user":{const i=t.message.content;if(typeof i=="string")break;const o=i.filter(u=>u.type==="tool_result"),p=o.length===1?Wt(t.tool_use_result):void 0;for(const u of o){const c=u.tool_use_id,ve=u.content;if(P.isTaskToolCall(c)){P.applyToolResult(c,ve)&&(G=!0);continue}if(await h(),u.is_error===!0)await d({type:"tool-output-error",toolCallId:c,errorText:zt(ve)});else{const qe=se.get(c);await d({type:"tool-output-available",toolCallId:c,output:qe&&Lt(qe)&&p!==void 0?p:ve})}se.delete(c)}return!1}case"result":{if(Se.size>0)return t.origin?.kind!=="task-notification"&&(Ae=!0),!1;if(mn(t)&&!Ae)return!1;ce=t.subtype==="success"||T.wasCancelled?"stop":"error";const i=Ot(t,fe);return pe=ce==="stop"?i:void 0,await h(),t.subtype!=="success"&&!T.wasCancelled&&await d({type:"error",errorText:t.errors?.filter(o=>o.trim()).join(`
7
- `)||`Claude Code failed: ${t.subtype}`}),st()}}return!1},Ue={threadRef:b,matrixEvent:Ye,llmProxyBinding:E},T=M??Q.create({conversationId:V,workdir:z,model:D,effort:W,agentMode:L,managedTaskContext:Ue,llmProxyBinding:E,buildOptions:(t,a,i)=>({abortController:t,cwd:z,includePartialMessages:!0,model:D,effort:W,maxTurns:f.claude.maxTurns,systemPrompt:{type:"preset",preset:"claude_code",append:$t},...Rt(L),tools:{type:"preset",preset:"claude_code"},mcpServers:{[Qe]:Mt({workspaceRoot:_e,context:i}),...f.schedule?.enabled===!0?{[Pt]:Et({config:f.schedule,context:i})}:null},disallowedTools:["EnterWorktree","ExitWorktree"],...J?{resume:J.sessionId,resumeSessionAt:J.messageId,forkSession:!0}:$?{resume:$}:null,canUseTool:a,env:{...process.env,...f.claude.apiKey?{ANTHROPIC_API_KEY:f.claude.apiKey}:null,...f.claude.baseUrl?{ANTHROPIC_BASE_URL:f.claude.baseUrl}:null,...E?{ANTHROPIC_CUSTOM_HEADERS:[process.env.ANTHROPIC_CUSTOM_HEADERS,`X-Elevo-LLM-Binding: ${E}`].filter(Boolean).join(`
8
- `)}:null,CLAUDE_AGENT_SDK_CLIENT_APP:"coding-agent/claude-agent"}})});qt(V,E);const ee=()=>T.abort();let Be=!1;try{k&&w&&(await w.attach({writeData:async()=>{},writeDone:async()=>{}}),k.kind==="ask_user"?Fe(k.toolCallId,{interrupted:!0}):We(k.toolCallId,{interrupted:!0}),await w.done),s&&(!w||k)&&(s.kind==="ask_user"?await d(k?{type:"tool-output-error",toolCallId:s.toolCallId,toolName:s.toolName,errorText:"Question interrupted by text response"}:{type:"tool-output-available",toolCallId:s.toolCallId,output:Yt(s,m,x)}):await d(k?{type:"tool-output-error",toolCallId:s.toolCallId,toolName:s.toolName,errorText:"Approval interrupted by text response"}:{type:"tool-output-available",toolCallId:s.toolCallId,output:Ve(s,m,x)}),(!k||!w)&&He(s.toolCallId));const t=s&&!w&&!k?s.kind==="ask_user"?en(m,x):on(s,m,x):x,a=await T.reserveTurn(t,dt,lt,{model:D,effort:W,agentMode:L,managedTaskContext:Ue});if(a.kind==="interjection"){g&&clearTimeout(g),await I.writeDone();return}Be=!0,F?.aborted&&ee(),F?.addEventListener("abort",ee,{once:!0}),await Promise.race([a.done,tt]),!j&&!re&&!v&&await X(T.wasCancelled||!T.isDead?"stop":"error")}catch(t){v?(U.error("Claude stream failed after downstream closed:",t),H()):(await Oe(y),F?.aborted||T.wasCancelled?await X("stop"):T.isDead?await X("error"):(U.error("Claude stream failed:",t),await d({type:"error",errorText:t instanceof Error?t.message:String(t)}),await X("error")))}finally{Be&&F?.removeEventListener("abort",ee),g&&clearTimeout(g),K.delete(Ce),j||je(),j||H()}}}}async function $n(e,n){await(await Ge(e)).run(n,e.signal)}te.post("/v1/chat/stream",ft("json",St),async e=>{let n;try{n=await Ge({body:e.req.valid("json"),headers:e.req.raw.headers})}catch(r){if(r instanceof B)return e.json({error:r.message},r.status);throw r}return mt(e,async r=>{await n.run({writeData:async l=>{await r.writeSSE({data:JSON.stringify(l)})},writeDone:async()=>{await r.writeSSE({data:"[DONE]"})}},e.req.raw.signal)})});var Ln=te;export{B as ChatRequestError,vn as cancelClaudeConversation,Ft as claudeActivitySnapshot,Ln as default,$n as handleClaudeChatStream,Ge as prepareClaudeChatStream};
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 U}from"@messenger-agent/shared/logger";import{appConfig as f}from"../config.js";import{resolveAgentWorkdir as kt,resolveAgentWorkspaceRoot as gt}from"@messenger-agent/shared/agent-workspaces";import{isManagedTaskToolName as Fe}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 He,answerPendingAsk as Xe,deletePendingClientTool as Ke,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 Mt,ChatBodySchema as Et}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 Nt}from"../schedule-mcp.js";import{claudeExecutionPermissionMode as Ot,claudePermissionOptions as jt}from"../permission.js";import{claudeContextSnapshot as qt,claudeTokenUsage as Ut}from"../token-usage.js";import{SessionManager as Bt}from"../session-manager.js";import{getLlmProxyBinding as $t,moveLlmProxyBinding as Lt,setLlmProxyBinding as Wt}from"../llm-proxy-bindings.js";const oe=new mt;let Ft;function _e(){return Ft??=new _t(ft(f.dataDir,"wakeup-idempotency.sqlite"))}const K=new Bt(()=>f.claude.sessionIdleTimeoutMs??36e5),Ht=vt(void 0,f.schedule?.enabled===!0),Q=new Set;function Xt(e){if(Fe(e))return!0;const n=`mcp__${Je}__`;return e.startsWith(n)&&Fe(e.slice(n.length))}function Kt(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 Qt(){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 M=new Map;function ze(e){return e?.trim().toLowerCase()==="plan"?"plan":"default"}function Jt(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 zt(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 Vt(e){if(e.type==="text"&&typeof e.text=="string")return e.text;if(zt(e))return Tt(e,f.fileUploads.tempDir);if(e.type!=="step-start")throw new Te(400,{message:`Unsupported message part type: ${e.type}`})}async function Gt(e){const r=(await Promise.all(e.map(Vt))).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 Yt(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 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 Zt(e){return typeof e=="string"?e:JSON.stringify(e)}function en(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 tn(e,n){const r=Ge(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)=>[en(l,c),Ie(n)])):{answer:Ie(n)}}function nn(e,n,r){return n&&n.state==="output-available"?n.output:{answers:r}}function on(e,n,r){return n&&n.state==="output-available"?{answers:tn(e,n.output),output:n.output}:{answers:r}}function rn(e,n){return!e||e.state!=="output-available"?`User answered previous question: ${n}`:Ve(e.output)}function an(e){return I(e)&&typeof e.plan=="string"?e.plan:JSON.stringify(e)}function sn(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,...sn(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 ln(e,n,r){const l=he(e,n,r),c=[l.approved?`User approved tool ${e.toolName} plan:`:`User rejected tool ${e.toolName} plan:`,an(e.input)];return l.reason&&c.push(`Reason: ${l.reason}`),c.join(`
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:c,matrixEvent:J,llmProxyBinding:P},_=E??K.create({conversationId:Y,workdir:G,model:R,effort:W,agentMode:L,managedTaskContext:Be,llmProxyBinding:P,buildOptions:(t,a,i)=>({abortController:t,cwd:G,includePartialMessages:!0,model:R,effort:W,maxTurns:f.claude.maxTurns,systemPrompt:{type:"preset",preset:"claude_code",append:Ht},...jt(L),tools:{type:"preset",preset:"claude_code"},mcpServers:{[Je]:Dt({workspaceRoot:z,context:i}),...f.schedule?.enabled===!0?{[Nt]:Rt({config:f.schedule,context:i})}:null},disallowedTools:["EnterWorktree","ExitWorktree"],...V?{resume:V.sessionId,resumeSessionAt:V.messageId,forkSession:!0}:$?{resume:$}:null,canUseTool:a,env:{...process.env,...f.claude.apiKey?{ANTHROPIC_API_KEY:f.claude.apiKey}:null,...f.claude.baseUrl?{ANTHROPIC_BASE_URL:f.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 $e=!1;try{g&&y&&(await y.attach({writeData:async()=>{},writeDone:async()=>{}}),g.kind==="ask_user"?Xe(g.toolCallId,{interrupted:!0}):He(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:nn(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)&&Ke(s.toolCallId));const t=s&&!y&&!g?s.kind==="ask_user"?rn(w,S):ln(s,w,S):S,a=await _.reserveTurn(t,ut,dt,{model:R,effort:W,agentMode:L,managedTaskContext:Be});if(a.kind==="interjection"){T&&clearTimeout(T),await C.writeDone();return}$e=!0,F?.aborted&&ne(),F?.addEventListener("abort",ne,{once:!0}),await Promise.race([a.done,nt]),!q&&!ie&&!k&&await X(_.wasCancelled||!_.isDead?"stop":"error")}catch(t){k?(U.error("Claude stream failed after downstream closed:",t),H()):(await qe(v),F?.aborted||_.wasCancelled?await X("stop"):_.isDead?await X("error"):(U.error("Claude stream failed:",t),await d({type:"error",errorText:t instanceof Error?t.message:String(t)}),await X("error")))}finally{$e&&F?.removeEventListener("abort",ne),T&&clearTimeout(T),Q.delete(xe),q||Ue(),q||H()}}}}async function Kn(e,n){await(await Ze(e)).run(n,e.signal)}oe.post("/v1/chat/stream",wt("json",Et),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 Ze({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 yt(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 Qn=oe;export{B as ChatRequestError,_n as cancelClaudeConversation,Qt as claudeActivitySnapshot,Qn as default,Kn as handleClaudeChatStream,Ze as prepareClaudeChatStream};
@@ -1 +1 @@
1
- import{createSdkMcpServer as a,tool as c}from"@anthropic-ai/claude-agent-sdk";import{executeScheduleTool as s,SCHEDULE_TOOL_DEFINITION as r,ScheduleToolError as i}from"@messenger-agent/shared/schedule-tools";const u="schedule";function y(t){return a({name:u,version:"0.1.0",instructions:"Manage schedules in the current trusted Matrix context.",alwaysLoad:!0,tools:[c(r.name,r.description,r.schema.shape,async n=>{try{const e=await s(t.config,t.context,n);return{content:[{type:"text",text:JSON.stringify(e)}],...e&&typeof e=="object"&&!Array.isArray(e)?{structuredContent:e}:null}}catch(e){const o=e instanceof i?{error:{code:e.code,message:e.message,retryable:e.retryable,currentRevision:e.currentRevision}}:{error:{code:"service_unavailable",message:e instanceof Error?e.message:String(e)}};return{content:[{type:"text",text:JSON.stringify(o)}],structuredContent:o,isError:!0}}},{alwaysLoad:!0,searchHint:r.name})]})}export{u as SCHEDULE_MCP_SERVER_NAME,y as createScheduleMcpServer};
1
+ import{randomUUID as a}from"node:crypto";import{createSdkMcpServer as c,tool as s}from"@anthropic-ai/claude-agent-sdk";import{executeScheduleTool as i,SCHEDULE_TOOL_DEFINITION as r,ScheduleToolError as u}from"@messenger-agent/shared/schedule-tools";const d="schedule";function p(t){return c({name:d,version:"0.1.0",instructions:"Manage schedules in the current trusted Matrix context.",alwaysLoad:!0,tools:[s(r.name,r.description,r.schema.shape,async n=>{try{const e=await i(t.config,t.context,n,fetch,a());return{content:[{type:"text",text:JSON.stringify(e)}],...e&&typeof e=="object"&&!Array.isArray(e)?{structuredContent:e}:null}}catch(e){const o=e instanceof u?{error:{code:e.code,message:e.message,retryable:e.retryable,currentRevision:e.currentRevision}}:{error:{code:"service_unavailable",message:e instanceof Error?e.message:String(e)}};return{content:[{type:"text",text:JSON.stringify(o)}],structuredContent:o,isError:!0}}},{alwaysLoad:!0,searchHint:r.name})]})}export{d as SCHEDULE_MCP_SERVER_NAME,p as createScheduleMcpServer};
@@ -1 +1 @@
1
- import{createRequire as f}from"node:module";import u from"ws";import{appConfig as p}from"./config.js";import{ChatBodySchema as y}from"./schemas.js";import{cancelClaudeConversation as C,ChatRequestError as b,handleClaudeChatStream as g}from"./routes/chat.js";import{logger as l}from"@messenger-agent/shared/logger";import{sendHttpTunnelResponse as m}from"@messenger-agent/shared/tunnel-http";import{CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE as w,CODING_AGENT_TUNNEL_PROTOCOL_VERSION as T}from"@messenger-agent/shared/tunnel-protocol";const v=f(import.meta.url)("../package.json").version;function _(r){return new Promise(e=>setTimeout(e,r))}function k(r){try{const e=JSON.parse(r.toString());return!e||typeof e!="object"||!("type"in e)?void 0:e}catch{return}}function I(r){const e=new Headers;for(const[t,c]of Object.entries(r??{}))e.set(t,c);return e}function O(r){if(r.method==="POST")try{const e=new URL(r.path,"http://coding-agent-tunnel.local").pathname,t=/^\/v1\/chat\/([^/]+)\/cancel$/.exec(e);return t?decodeURIComponent(t[1]):void 0}catch{return}}class q{config;stopped=!1;socket;running=new Map;constructor(e){this.config=e}async start(){if(!this.config.enabled)return;if(!this.config.serverUrl||!this.config.tunnelId||!this.config.token)throw new Error("Tunnel config requires server_url, tunnel_id, and token when enabled");let e=this.config.reconnectInitialMs;for(;!this.stopped;){try{await this.connectOnce(),e=this.config.reconnectInitialMs}catch(t){if(this.stopped)break;l.warn("[TunnelClient] websocket tunnel connection failed:",t)}this.stopped||(await _(e),e=Math.min(e*2,this.config.reconnectMaxMs))}}stop(){this.stopped=!0;for(const e of this.running.values())e.abortController.abort();this.running.clear(),this.socket?.close()}connectOnce(){return new Promise((e,t)=>{const c={Authorization:`Bearer ${this.config.token}`},o=new u(this.config.serverUrl,{headers:c});this.socket=o;let n,s=!1;const d=()=>{n&&clearInterval(n),this.socket===o&&(this.socket=void 0);for(const a of this.running.values())a.abortController.abort(new Error("Tunnel connection closed"));this.running.clear()};o.on("open",()=>{this.send({type:"hello",protocolVersion:T,tunnelId:this.config.tunnelId,agentType:"claude",clientVersion:v}),n=setInterval(()=>{this.send({type:"heartbeat"})},this.config.heartbeatIntervalMs),l.info(`[TunnelClient] connected to ${this.config.serverUrl} as ${this.config.tunnelId}`)}),o.on("message",a=>{const i=k(a);if(!i){l.warn("[TunnelClient] received invalid websocket message");return}if(i.type!=="heartbeat"){if(i.type==="cancel"){this.running.get(i.id)?.abortController.abort();return}i.type==="request"&&this.handleRequest(i).catch(h=>{l.error(`[TunnelClient] request ${i.id} failed:`,h)})}}),o.once("error",a=>{s||(s=!0,d(),t(a))}),o.once("close",(a,i)=>{d(),a===w&&(this.stopped=!0,l.warn(`[TunnelClient] duplicate websocket tunnel connection rejected; reconnect disabled: ${i.toString()}`)),s||(s=!0,e())})})}send(e){const t=this.socket;!t||t.readyState!==u.OPEN||t.send(JSON.stringify(e))}async handleRequest(e){const t=O(e);if(t){const n=C(t),s=Response.json(n?{success:!0}:{error:{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}},{status:n?200:404});await m(e.id,s,d=>this.send(d));return}if(e.method!=="POST"||e.path!=="/v1/chat/stream"){this.send({type:"response.error",id:e.id,code:"unsupported_request",errorText:`Unsupported tunnel request: ${e.method} ${e.path}`});return}const c=y.safeParse(e.body);if(!c.success){this.send({type:"response.error",id:e.id,code:"invalid_request",errorText:`Invalid chat request body: ${c.error.message}`});return}const o=new AbortController;this.running.set(e.id,{abortController:o}),this.send({type:"response.start",id:e.id,mode:"sse"});try{await g({body:c.data,headers:I(e.headers),signal:o.signal},{writeData:async n=>this.send({type:"response.chunk",id:e.id,data:n}),writeDone:async()=>this.send({type:"response.end",id:e.id})})}catch(n){const s=n instanceof Error?n.message:"Tunnel request failed",d=n instanceof b?`http_${n.status}`:"request_failed";this.send({type:"response.error",id:e.id,code:d,errorText:s})}finally{this.running.delete(e.id)}}}function P(r=p.tunnel){if(!r.enabled)return;const e=new q(r);return e.start().catch(t=>{l.error("[TunnelClient] stopped unexpectedly:",t)}),e}export{q as ClaudeTunnelClient,P as startClaudeTunnelClient};
1
+ import{createRequire as f}from"node:module";import u from"ws";import{appConfig as p}from"./config.js";import{ChatBodySchema as y}from"./schemas.js";import{cancelClaudeConversation as C,ChatRequestError as m,handleClaudeChatStream as b}from"./routes/chat.js";import{logger as l}from"@messenger-agent/shared/logger";import{reconnectDelayWithJitter as g}from"@messenger-agent/shared/reconnect-backoff";import{sendHttpTunnelResponse as w}from"@messenger-agent/shared/tunnel-http";import{CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE as T,CODING_AGENT_TUNNEL_PROTOCOL_VERSION as v}from"@messenger-agent/shared/tunnel-protocol";const _=f(import.meta.url)("../package.json").version;function k(r){return new Promise(e=>setTimeout(e,r))}function I(r){try{const e=JSON.parse(r.toString());return!e||typeof e!="object"||!("type"in e)?void 0:e}catch{return}}function O(r){const e=new Headers;for(const[t,c]of Object.entries(r??{}))e.set(t,c);return e}function q(r){if(r.method==="POST")try{const e=new URL(r.path,"http://coding-agent-tunnel.local").pathname,t=/^\/v1\/chat\/([^/]+)\/cancel$/.exec(e);return t?decodeURIComponent(t[1]):void 0}catch{return}}class E{config;stopped=!1;socket;running=new Map;constructor(e){this.config=e}async start(){if(!this.config.enabled)return;if(!this.config.serverUrl||!this.config.tunnelId||!this.config.token)throw new Error("Tunnel config requires server_url, tunnel_id, and token when enabled");let e=this.config.reconnectInitialMs;for(;!this.stopped;){try{await this.connectOnce(),e=this.config.reconnectInitialMs}catch(t){if(this.stopped)break;l.warn("[TunnelClient] websocket tunnel connection failed:",t)}this.stopped||(await k(g(e)),e=Math.min(e*2,this.config.reconnectMaxMs))}}stop(){this.stopped=!0;for(const e of this.running.values())e.abortController.abort();this.running.clear(),this.socket?.close()}connectOnce(){return new Promise((e,t)=>{const c={Authorization:`Bearer ${this.config.token}`},o=new u(this.config.serverUrl,{headers:c});this.socket=o;let n,s=!1;const d=()=>{n&&clearInterval(n),this.socket===o&&(this.socket=void 0);for(const a of this.running.values())a.abortController.abort(new Error("Tunnel connection closed"));this.running.clear()};o.on("open",()=>{this.send({type:"hello",protocolVersion:v,tunnelId:this.config.tunnelId,agentType:"claude",clientVersion:_}),n=setInterval(()=>{this.send({type:"heartbeat"})},this.config.heartbeatIntervalMs),l.info(`[TunnelClient] connected to ${this.config.serverUrl} as ${this.config.tunnelId}`)}),o.on("message",a=>{const i=I(a);if(!i){l.warn("[TunnelClient] received invalid websocket message");return}if(i.type!=="heartbeat"){if(i.type==="cancel"){this.running.get(i.id)?.abortController.abort();return}i.type==="request"&&this.handleRequest(i).catch(h=>{l.error(`[TunnelClient] request ${i.id} failed:`,h)})}}),o.once("error",a=>{s||(s=!0,d(),t(a))}),o.once("close",(a,i)=>{d(),a===T&&(this.stopped=!0,l.warn(`[TunnelClient] duplicate websocket tunnel connection rejected; reconnect disabled: ${i.toString()}`)),s||(s=!0,e())})})}send(e){const t=this.socket;!t||t.readyState!==u.OPEN||t.send(JSON.stringify(e))}async handleRequest(e){const t=q(e);if(t){const n=C(t),s=Response.json(n?{success:!0}:{error:{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}},{status:n?200:404});await w(e.id,s,d=>this.send(d));return}if(e.method!=="POST"||e.path!=="/v1/chat/stream"){this.send({type:"response.error",id:e.id,code:"unsupported_request",errorText:`Unsupported tunnel request: ${e.method} ${e.path}`});return}const c=y.safeParse(e.body);if(!c.success){this.send({type:"response.error",id:e.id,code:"invalid_request",errorText:`Invalid chat request body: ${c.error.message}`});return}const o=new AbortController;this.running.set(e.id,{abortController:o}),this.send({type:"response.start",id:e.id,mode:"sse"});try{await b({body:c.data,headers:O(e.headers),signal:o.signal},{writeData:async n=>this.send({type:"response.chunk",id:e.id,data:n}),writeDone:async()=>this.send({type:"response.end",id:e.id})})}catch(n){const s=n instanceof Error?n.message:"Tunnel request failed",d=n instanceof m?`http_${n.status}`:"request_failed";this.send({type:"response.error",id:e.id,code:d,errorText:s})}finally{this.running.delete(e.id)}}}function L(r=p.tunnel){if(!r.enabled)return;const e=new E(r);return e.start().catch(t=>{l.error("[TunnelClient] stopped unexpectedly:",t)}),e}export{E as ClaudeTunnelClient,L as startClaudeTunnelClient};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-agent/claude-agent",
3
- "version": "0.27.0",
3
+ "version": "0.29.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.27.0"
22
+ "@messenger-agent/shared": "0.29.0-alpha.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/ws": "^8.18.1"