@ian2018cs/agenthub 0.1.8 → 0.1.10

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.
@@ -68,7 +68,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
68
68
  Path: \`${E.path}\``,timestamp:Date.now()}]),E.exists&&l&&l(E.path));break;case"config":x&&x();break;default:console.warn("Unknown built-in command action:",v)}},[l,x,ht]),hr=s.useRef(null),na=s.useCallback(async(i,v)=>{const{content:E,hasBashCommands:j,hasFileIncludes:D}=i;if(j&&!window.confirm("此命令包含将被执行的 bash 命令。是否继续?")){X(de=>[...de,{type:"assistant",content:"❌ 命令执行已取消",timestamp:Date.now()}]);return}Z(E),setTimeout(()=>{if(hr.current){const G={preventDefault:()=>{}};hr.current(G)}},50)},[]),Gt=s.useCallback(async i=>{if(!(!i||!t))try{const v=F.match(new RegExp(`${i.name}\\s*(.*)`)),E=v&&v[1]?v[1].trim().split(/\s+/):[],j={projectPath:t.path,projectName:t.name,sessionId:M,provider:mt,model:ht,tokenUsage:Gr},D=await ie("/api/commands/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({commandName:i.name,commandPath:i.path,args:E,context:j})});if(!D.ok)throw new Error("Failed to execute command");const G=await D.json();G.type==="builtin"?aa(G):G.type==="custom"&&await na(G,E),Z(""),lt(!1),gt(-1),ct(""),dt(-1)}catch(v){console.error("Error executing command:",v),X(E=>[...E,{type:"assistant",content:`Error executing command: ${v.message}`,timestamp:Date.now()}])}},[F,t,M,mt,ht,Gr]),oa=s.useMemo(()=>{const i=new Map;return(v,E)=>{const j=`${v.length}-${E.length}-${v.slice(0,50)}`;if(i.has(j))return i.get(j);const D=ia(v,E);if(i.set(j,D),i.size>100){const G=i.keys().next().value;i.delete(G)}return D}},[]),Ot=s.useCallback(async(i,v,E=!1,j="claude")=>{if(!i||!v)return[];const D=!E;D?Ne(!0):be(!0);try{const G=E?Re:0,de=await ve.sessionMessages(i,v,Ie,G,j);if(!de.ok)throw new Error("Failed to load session messages");const H=await de.json();if(D&&H.tokenUsage&&Et(H.tokenUsage),H.hasMore!==void 0)return je(H.hasMore),ye(H.total),pe(G+(H.messages?.length||0)),H.messages||[];{const ee=H.messages||[];return je(!1),ye(ee.length),ee}}catch(G){return console.error("Error loading session messages:",G),[]}finally{D?Ne(!1):be(!1)}},[Re]),ia=(i,v)=>{const E=i.split(`
69
69
  `),j=v.split(`
70
70
  `),D=[];let G=0,de=0;for(;G<E.length||de<j.length;){const H=E[G],ee=j[de];G>=E.length?(D.push({type:"added",content:ee,lineNum:de+1}),de++):de>=j.length?(D.push({type:"removed",content:H,lineNum:G+1}),G++):H===ee?(G++,de++):(D.push({type:"removed",content:H,lineNum:G+1}),D.push({type:"added",content:ee,lineNum:de+1}),G++,de++)}return D},la=i=>{const v=[],E=new Map;for(const j of i)if(j.message?.role==="user"&&Array.isArray(j.message?.content))for(const D of j.message.content)D.type==="tool_result"&&E.set(D.tool_use_id,{content:D.content,isError:D.is_error,timestamp:new Date(j.timestamp||Date.now()),toolUseResult:j.toolUseResult||null});for(const j of i)if(!j.isMeta){if(j.message?.role==="user"&&j.message?.content){let D="",G="user";if(Array.isArray(j.message.content)){const ee=[];for(const fe of j.message.content)fe.type==="text"&&ee.push(Mt(fe.text));D=ee.join(`
71
- `)}else typeof j.message.content=="string"?D=Mt(j.message.content):D=Mt(String(j.message.content));const de=D.match(/<command-name>(\/[^<]+)<\/command-name>/);de&&(D=de[1]),!D||D.startsWith("<command-args>")||D.startsWith("<local-command-stdout>")||D.startsWith("<system-reminder>")||D.startsWith("Caveat:")||D.startsWith("This session is being continued from a previous")||D.startsWith("[Request interrupted")||(D=tr(D),v.push({type:G,content:D,timestamp:j.timestamp||new Date().toISOString()}))}else if(j.type==="thinking"&&j.message?.content)v.push({type:"assistant",content:tr(j.message.content),timestamp:j.timestamp||new Date().toISOString(),isThinking:!0});else if(j.type==="tool_use"&&j.toolName)v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:j.toolName,toolInput:j.toolInput||"",toolCallId:j.toolCallId});else if(j.type==="tool_result"){for(let D=v.length-1;D>=0;D--)if(v[D].isToolUse&&!v[D].toolResult&&(!j.toolCallId||v[D].toolCallId===j.toolCallId)){v[D].toolResult={content:j.output||"",isError:!1};break}}else if(j.message?.role==="assistant"&&j.message?.content){if(Array.isArray(j.message.content)){for(const D of j.message.content)if(D.type==="text"){let G=D.text;typeof G=="string"&&(G=tr(G)),v.push({type:"assistant",content:G,timestamp:j.timestamp||new Date().toISOString()})}else if(D.type==="tool_use"){const G=E.get(D.id);v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:D.name,toolInput:JSON.stringify(D.input),toolResult:G?{content:typeof G.content=="string"?G.content:JSON.stringify(G.content),isError:G.isError,toolUseResult:G.toolUseResult}:null,toolError:G?.isError||!1,toolResultTimestamp:G?.timestamp||new Date})}}else if(typeof j.message.content=="string"){let D=j.message.content;D=tr(D),v.push({type:"assistant",content:D,timestamp:j.timestamp||new Date().toISOString()})}}}return v},Zr=s.useMemo(()=>la(ne),[ne]),ut=s.useCallback(()=>{we.current&&(we.current.scrollTop=we.current.scrollHeight)},[]),Yt=s.useCallback(()=>{if(!we.current)return!1;const{scrollTop:i,scrollHeight:v,clientHeight:E}=we.current;return v-i-E<50},[]),Xr=s.useCallback(async i=>{if(!i||J.current||Ee||!Se||!r||!t)return!1;J.current=!0;const v=i.scrollHeight,E=i.scrollTop;try{const j=await Ot(t.name,r.id,!0,"claude");return j.length>0&&(se.current={height:v,top:E},Y(D=>[...j,...D])),!0}finally{J.current=!1}},[Se,Ee,r,t,Ot]),Wt=s.useCallback(async()=>{if(we.current){const i=we.current,v=Yt();ke(!v),i.scrollTop<100?me.current||await Xr(i)&&(me.current=!0):me.current=!1}},[Yt,Xr]);s.useLayoutEffect(()=>{if(!se.current||!we.current)return;const{height:i,top:v}=se.current,E=we.current,D=E.scrollHeight-i;E.scrollTop=v+Math.max(D,0),se.current=null},[oe.length]),s.useEffect(()=>{(async()=>{if(r&&t){const v=localStorage.getItem("selected-provider")||"claude";if(Ke.current=!0,M!==null&&M!==r.id?(pe(0),je(!1),ye(0),Et(null),W(!1),a&&o&&o({type:"check-session-status",sessionId:r.id,provider:v})):M===null&&(pe(0),je(!1),ye(0),a&&o&&o({type:"check-session-status",sessionId:r.id,provider:v})),P(r.id),ze)Ce(!1);else{const j=await Ot(t.name,r.id,!1,"claude");Y(j)}}else!ze&&!ae&&(X([]),Y([])),P(null),pe(0),je(!1),ye(0);setTimeout(()=>{Ke.current=!1},250)})()},[r,t,ut,ze]),s.useEffect(()=>{c>0&&r&&t&&(async()=>{try{const v=await Ot(t.name,r.id,!1,"claude");Y(v),S&&Yt()&&setTimeout(()=>ut(),200)}catch(v){console.error("Error reloading messages from external update:",v)}})()},[c,r,t,Ot,Yt,S,ut]),s.useEffect(()=>{ne.length>0&&!ae&&X(Zr)},[Zr,ne,ae]),s.useEffect(()=>{d&&d(Q)},[Q,d]),s.useEffect(()=>{t&&F!==""?He.setItem(`draft_input_${t.name}`,F):t&&F===""&&He.removeItem(`draft_input_${t.name}`)},[F,t]),s.useEffect(()=>{t&&oe.length>0&&He.setItem(`chat_messages_${t.name}`,JSON.stringify(oe))},[oe,t]),s.useEffect(()=>{if(t){const i=He.getItem(`draft_input_${t.name}`)||"";i!==F&&Z(i)}},[t?.name]),s.useEffect(()=>{M&&ae&&h&&h(M)},[ae,M,h]),s.useEffect(()=>{M&&L&&L.has(M)&&!ae&&(W(!0),K(!0))},[M,L]),s.useEffect(()=>{if(n.length>0){const i=n[n.length-1];if(!["projects_updated","session-created","claude-complete"].includes(i.type)&&i.sessionId&&M&&i.sessionId!==M){console.log("⏭️ Skipping message for different session:",i.sessionId,"current:",M);return}switch(i.type){case"session-created":i.sessionId&&!M&&(sessionStorage.setItem("pendingSessionId",i.sessionId),Ce(!0),k&&k(i.sessionId),_(H=>H.map(ee=>ee.sessionId?ee:{...ee,sessionId:i.sessionId})));break;case"token-budget":i.data&&Et(i.data);break;case"claude-response":const j=i.data.message||i.data;if(j&&typeof j=="object"&&j.type){if(j.type==="content_block_delta"&&j.delta?.text){const H=Mt(j.delta.text);re.current+=H,xe.current||(xe.current=setTimeout(()=>{const ee=re.current;re.current="",xe.current=null,ee&&X(fe=>{const De=[...fe],We=De[De.length-1];return We&&We.type==="assistant"&&!We.isToolUse&&We.isStreaming?We.content=(We.content||"")+ee:De.push({type:"assistant",content:ee,timestamp:new Date,isStreaming:!0}),De})},100));return}if(j.type==="content_block_stop"){xe.current&&(clearTimeout(xe.current),xe.current=null);const H=re.current;re.current="",H&&X(ee=>{const fe=[...ee],De=fe[fe.length-1];return De&&De.type==="assistant"&&!De.isToolUse&&De.isStreaming?De.content=(De.content||"")+H:fe.push({type:"assistant",content:H,timestamp:new Date,isStreaming:!0}),fe}),X(ee=>{const fe=[...ee],De=fe[fe.length-1];return De&&De.type==="assistant"&&De.isStreaming&&(De.isStreaming=!1),fe});return}}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&M&&i.data.session_id!==M){console.log("🔄 Claude CLI session duplication detected:",{originalSession:M,newSession:i.data.session_id}),Ce(!0),C&&C(i.data.session_id);return}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&!M){console.log("🔄 New session init detected:",{newSession:i.data.session_id}),Ce(!0),C&&C(i.data.session_id);return}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&M&&i.data.session_id===M){console.log("🔄 System init message for current session, ignoring");return}if(Array.isArray(j.content)){for(const H of j.content)if(H.type==="tool_use"){const ee=H.input?JSON.stringify(H.input,null,2):"";X(fe=>[...fe,{type:"assistant",content:"",timestamp:new Date,isToolUse:!0,toolName:H.name,toolInput:ee,toolId:H.id,toolResult:null}])}else if(H.type==="text"&&H.text?.trim()){let ee=Mt(H.text);ee=Ar(ee),X(fe=>[...fe,{type:"assistant",content:ee,timestamp:new Date}])}}else if(typeof j.content=="string"&&j.content.trim()){let H=Mt(j.content);H=Ar(H),X(ee=>[...ee,{type:"assistant",content:H,timestamp:new Date}])}if(j.role==="user"&&Array.isArray(j.content))for(const H of j.content)H.type==="tool_result"&&X(ee=>ee.map(fe=>fe.isToolUse&&fe.toolId===H.tool_use_id?{...fe,toolResult:{content:H.content,isError:H.is_error,timestamp:new Date}}:fe));break;case"claude-output":{const H=String(i.data||"");H.trim()&&(re.current+=re.current?`
71
+ `)}else typeof j.message.content=="string"?D=Mt(j.message.content):D=Mt(String(j.message.content));const de=D.match(/<command-name>(\/[^<]+)<\/command-name>/);de&&(D=de[1]),!D||D.startsWith("<command-args>")||D.startsWith("<local-command-stdout>")||D.startsWith("<system-reminder>")||D.startsWith("Caveat:")||D.startsWith("This session is being continued from a previous")||D.startsWith("[Request interrupted")||(D=tr(D),v.push({type:G,content:D,timestamp:j.timestamp||new Date().toISOString()}))}else if(j.type==="thinking"&&j.message?.content)v.push({type:"assistant",content:tr(j.message.content),timestamp:j.timestamp||new Date().toISOString(),isThinking:!0});else if(j.type==="tool_use"&&j.toolName)v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:j.toolName,toolInput:j.toolInput||"",toolCallId:j.toolCallId});else if(j.type==="tool_result"){for(let D=v.length-1;D>=0;D--)if(v[D].isToolUse&&!v[D].toolResult&&(!j.toolCallId||v[D].toolCallId===j.toolCallId)){v[D].toolResult={content:j.output||"",isError:!1};break}}else if(j.message?.role==="assistant"&&j.message?.content){if(Array.isArray(j.message.content)){for(const D of j.message.content)if(D.type==="text"){let G=D.text;typeof G=="string"&&(G=tr(G)),v.push({type:"assistant",content:G,timestamp:j.timestamp||new Date().toISOString()})}else if(D.type==="tool_use"){const G=E.get(D.id);v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:D.name,toolInput:JSON.stringify(D.input),toolResult:G?{content:typeof G.content=="string"?G.content:JSON.stringify(G.content),isError:G.isError,toolUseResult:G.toolUseResult}:null,toolError:G?.isError||!1,toolResultTimestamp:G?.timestamp||new Date})}}else if(typeof j.message.content=="string"){let D=j.message.content;D=tr(D),v.push({type:"assistant",content:D,timestamp:j.timestamp||new Date().toISOString()})}}}return v},Zr=s.useMemo(()=>la(ne),[ne]),ut=s.useCallback(()=>{we.current&&(we.current.scrollTop=we.current.scrollHeight)},[]),Yt=s.useCallback(()=>{if(!we.current)return!1;const{scrollTop:i,scrollHeight:v,clientHeight:E}=we.current;return v-i-E<50},[]),Xr=s.useCallback(async i=>{if(!i||J.current||Ee||!Se||!r||!t)return!1;J.current=!0;const v=i.scrollHeight,E=i.scrollTop;try{const j=await Ot(t.name,r.id,!0,"claude");return j.length>0?(se.current={height:v,top:E},Y(D=>[...j,...D]),!0):!1}finally{J.current=!1}},[Se,Ee,r,t,Ot]),Wt=s.useCallback(async()=>{if(we.current){const i=we.current,v=Yt();ke(!v),i.scrollTop<100?me.current||await Xr(i)&&(me.current=!0):me.current=!1}},[Yt,Xr]);s.useLayoutEffect(()=>{if(!se.current||!we.current)return;const{height:i,top:v}=se.current,E=we.current,D=E.scrollHeight-i;E.scrollTop=v+Math.max(D,0),se.current=null,setTimeout(()=>{me.current=!1},100)},[oe.length]),s.useEffect(()=>{(async()=>{if(r&&t){const v=localStorage.getItem("selected-provider")||"claude";if(Ke.current=!0,M!==null&&M!==r.id?(pe(0),je(!1),ye(0),Et(null),W(!1),a&&o&&o({type:"check-session-status",sessionId:r.id,provider:v})):M===null&&(pe(0),je(!1),ye(0),a&&o&&o({type:"check-session-status",sessionId:r.id,provider:v})),P(r.id),ze)Ce(!1);else{const j=await Ot(t.name,r.id,!1,"claude");Y(j)}}else!ze&&!ae&&(X([]),Y([])),P(null),pe(0),je(!1),ye(0);setTimeout(()=>{Ke.current=!1},250)})()},[r,t,ut,ze]),s.useEffect(()=>{c>0&&r&&t&&(async()=>{try{const v=await Ot(t.name,r.id,!1,"claude");Y(v),S&&Yt()&&setTimeout(()=>ut(),200)}catch(v){console.error("Error reloading messages from external update:",v)}})()},[c,r,t,Ot,Yt,S,ut]),s.useEffect(()=>{ne.length>0&&!ae&&X(Zr)},[Zr,ne,ae]),s.useEffect(()=>{d&&d(Q)},[Q,d]),s.useEffect(()=>{t&&F!==""?He.setItem(`draft_input_${t.name}`,F):t&&F===""&&He.removeItem(`draft_input_${t.name}`)},[F,t]),s.useEffect(()=>{t&&oe.length>0&&He.setItem(`chat_messages_${t.name}`,JSON.stringify(oe))},[oe,t]),s.useEffect(()=>{if(t){const i=He.getItem(`draft_input_${t.name}`)||"";i!==F&&Z(i)}},[t?.name]),s.useEffect(()=>{M&&ae&&h&&h(M)},[ae,M,h]),s.useEffect(()=>{M&&L&&L.has(M)&&!ae&&(W(!0),K(!0))},[M,L]),s.useEffect(()=>{if(n.length>0){const i=n[n.length-1];if(!["projects_updated","session-created","claude-complete"].includes(i.type)&&i.sessionId&&M&&i.sessionId!==M){console.log("⏭️ Skipping message for different session:",i.sessionId,"current:",M);return}switch(i.type){case"session-created":i.sessionId&&!M&&(sessionStorage.setItem("pendingSessionId",i.sessionId),Ce(!0),k&&k(i.sessionId),_(H=>H.map(ee=>ee.sessionId?ee:{...ee,sessionId:i.sessionId})));break;case"token-budget":i.data&&Et(i.data);break;case"claude-response":const j=i.data.message||i.data;if(j&&typeof j=="object"&&j.type){if(j.type==="content_block_delta"&&j.delta?.text){const H=Mt(j.delta.text);re.current+=H,xe.current||(xe.current=setTimeout(()=>{const ee=re.current;re.current="",xe.current=null,ee&&X(fe=>{const De=[...fe],We=De[De.length-1];return We&&We.type==="assistant"&&!We.isToolUse&&We.isStreaming?We.content=(We.content||"")+ee:De.push({type:"assistant",content:ee,timestamp:new Date,isStreaming:!0}),De})},100));return}if(j.type==="content_block_stop"){xe.current&&(clearTimeout(xe.current),xe.current=null);const H=re.current;re.current="",H&&X(ee=>{const fe=[...ee],De=fe[fe.length-1];return De&&De.type==="assistant"&&!De.isToolUse&&De.isStreaming?De.content=(De.content||"")+H:fe.push({type:"assistant",content:H,timestamp:new Date,isStreaming:!0}),fe}),X(ee=>{const fe=[...ee],De=fe[fe.length-1];return De&&De.type==="assistant"&&De.isStreaming&&(De.isStreaming=!1),fe});return}}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&M&&i.data.session_id!==M){console.log("🔄 Claude CLI session duplication detected:",{originalSession:M,newSession:i.data.session_id}),Ce(!0),C&&C(i.data.session_id);return}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&!M){console.log("🔄 New session init detected:",{newSession:i.data.session_id}),Ce(!0),C&&C(i.data.session_id);return}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&M&&i.data.session_id===M){console.log("🔄 System init message for current session, ignoring");return}if(Array.isArray(j.content)){for(const H of j.content)if(H.type==="tool_use"){const ee=H.input?JSON.stringify(H.input,null,2):"";X(fe=>[...fe,{type:"assistant",content:"",timestamp:new Date,isToolUse:!0,toolName:H.name,toolInput:ee,toolId:H.id,toolResult:null}])}else if(H.type==="text"&&H.text?.trim()){let ee=Mt(H.text);ee=Ar(ee),X(fe=>[...fe,{type:"assistant",content:ee,timestamp:new Date}])}}else if(typeof j.content=="string"&&j.content.trim()){let H=Mt(j.content);H=Ar(H),X(ee=>[...ee,{type:"assistant",content:H,timestamp:new Date}])}if(j.role==="user"&&Array.isArray(j.content))for(const H of j.content)H.type==="tool_result"&&X(ee=>ee.map(fe=>fe.isToolUse&&fe.toolId===H.tool_use_id?{...fe,toolResult:{content:H.content,isError:H.is_error,timestamp:new Date}}:fe));break;case"claude-output":{const H=String(i.data||"");H.trim()&&(re.current+=re.current?`
72
72
  ${H}`:H,xe.current||(xe.current=setTimeout(()=>{const ee=re.current;re.current="",xe.current=null,ee&&X(fe=>{const De=[...fe],We=De[De.length-1];return We&&We.type==="assistant"&&!We.isToolUse&&We.isStreaming?We.content=We.content?`${We.content}
73
73
  ${ee}`:ee:De.push({type:"assistant",content:ee,timestamp:new Date,isStreaming:!0}),De})},100)))}break;case"claude-interactive-prompt":X(H=>[...H,{type:"assistant",content:i.data,timestamp:new Date,isInteractivePrompt:!0}]);break;case"claude-permission-request":{if(!i.requestId)break;_(H=>H.some(ee=>ee.requestId===i.requestId)?H:[...H,{requestId:i.requestId,toolName:i.toolName||"UnknownTool",input:i.input,context:i.context,sessionId:i.sessionId||null,receivedAt:new Date}]),W(!0),K(!0),yt({text:"等待权限",tokens:0,can_interrupt:!0});break}case"claude-permission-cancelled":{if(!i.requestId)break;_(H=>H.filter(ee=>ee.requestId!==i.requestId));break}case"claude-error":X(H=>[...H,{type:"error",content:`错误: ${i.error}`,timestamp:new Date}]);break;case"claude-complete":const D=i.sessionId||M||sessionStorage.getItem("pendingSessionId");(D===M||!M)&&(W(!1),K(!1),yt(null)),D&&(g&&g(D),y&&y(D));const G=sessionStorage.getItem("pendingSessionId");G&&!M&&i.exitCode===0&&(P(G),sessionStorage.removeItem("pendingSessionId"),console.log("✅ New session complete, ID set to:",G)),t&&i.exitCode===0&&He.removeItem(`chat_messages_${t.name}`),_([]);break;case"session-aborted":{const H=i.sessionId||M;H===M&&(W(!1),K(!1),yt(null)),H&&(g&&g(H),y&&y(H)),_([]),X(ee=>[...ee,{type:"assistant",content:"会话已被用户中断。",timestamp:new Date}]);break}case"session-status":{const H=i.sessionId;(H===M||r&&H===r.id)&&i.isProcessing&&(W(!0),K(!0),h&&h(H));break}case"claude-status":const de=i.data;if(de){let H={text:"处理中...",tokens:0,can_interrupt:!0};de.message?H.text=de.message:de.status?H.text=de.status:typeof de=="string"&&(H.text=de),de.tokens?H.tokens=de.tokens:de.token_count&&(H.tokens=de.token_count),de.can_interrupt!==void 0&&(H.can_interrupt=de.can_interrupt),yt(H),W(!0),K(H.can_interrupt)}break}}},[n]),s.useEffect(()=>{t&&ca()},[t]);const ca=async()=>{try{const i=await ve.getFiles(t.name);if(i.ok){const v=await i.json(),E=es(v);ur(E)}}catch(i){console.error("Error fetching files:",i)}},es=(i,v="")=>{let E=[];for(const j of i){const D=v?`${v}/${j.name}`:j.name;j.type==="directory"&&j.children?E=E.concat(es(j.children,D)):j.type==="file"&&E.push({name:j.name,path:D,relativePath:j.path})}return E};s.useEffect(()=>{const i=F.slice(0,Ht),v=i.lastIndexOf("@");if(v!==-1){const E=i.slice(v+1);if(E.includes(" "))qe(!1),at(-1);else{at(v),qe(!0);const j=qt.filter(D=>D.name.toLowerCase().includes(E.toLowerCase())||D.path.toLowerCase().includes(E.toLowerCase())).slice(0,10);bt(j),Ft(-1)}}else qe(!1),at(-1)},[F,Ht,qt]),s.useEffect(()=>{const i=setTimeout(()=>{Xe(F)},150);return()=>clearTimeout(i)},[F]);const ts=s.useMemo(()=>oe.length<=At?oe:oe.slice(-At),[oe,At]);s.useEffect(()=>{if(!S&&we.current){const i=we.current;it.current={height:i.scrollHeight,top:i.scrollTop}}}),s.useEffect(()=>{if(we.current&&oe.length>0)if(S)ce||setTimeout(()=>ut(),50);else{const i=we.current,v=it.current.height,E=it.current.top,D=i.scrollHeight-v;D>0&&E>0&&(i.scrollTop=E+D)}},[oe.length,ce,ut,S]),s.useEffect(()=>{we.current&&oe.length>0&&!Ke.current&&(ke(!1),setTimeout(()=>{ut()},200))},[r?.id,t?.name]),s.useEffect(()=>{const i=we.current;if(i)return i.addEventListener("scroll",Wt),()=>i.removeEventListener("scroll",Wt)},[Wt]),s.useEffect(()=>{if(A.current){A.current.style.height="auto",A.current.style.height=A.current.scrollHeight+"px";const i=parseInt(window.getComputedStyle(A.current).lineHeight),v=A.current.scrollHeight>i*2;xt(v)}},[]),s.useEffect(()=>{A.current&&!F.trim()&&(A.current.style.height="auto",xt(!1))},[F]),s.useEffect(()=>{if(!t||!r?.id||r.id.startsWith("new-session-")){Et(null);return}(async()=>{try{const v=`/api/projects/${t.name}/sessions/${r.id}/token-usage`,E=await ie(v);if(E.ok){const j=await E.json();Et(j)}else Et(null)}catch(v){console.error("Failed to fetch initial token usage:",v)}})()},[r?.id,r?.__provider,t?.path]);const da=s.useCallback(i=>{i.trim()&&Z(v=>{const E=v.trim()?`${v} ${i}`:i;return setTimeout(()=>{if(A.current){A.current.style.height="auto",A.current.style.height=A.current.scrollHeight+"px";const j=parseInt(window.getComputedStyle(A.current).lineHeight),D=A.current.scrollHeight>j*2;xt(D)}},0),E})},[]),ma=s.useCallback(()=>{ea(i=>i+100)},[]),Qt=s.useCallback(i=>{const v=i.filter(E=>{try{if(!E||typeof E!="object")return console.warn("Invalid file object:",E),!1;if(!E.type||!E.type.startsWith("image/"))return!1;if(!E.size||E.size>5242880){const j=E.name||"Unknown file";return Ue(D=>{const G=new Map(D);return G.set(j,"文件太大(最大 5MB)"),G}),!1}return!0}catch(j){return console.error("Error validating file:",j,E),!1}});v.length>0&&ue(E=>[...E,...v].slice(0,5))},[]),ua=s.useCallback(async i=>{const v=i.target.files;if(!(!v||v.length===0||!t)){$(!0);try{const E=new FormData;Array.from(v).forEach(G=>{E.append("files",G)});const j=await ve.uploadFiles(t.name,E);if(!j.ok){const G=await j.json();throw new Error(G.error||"上传失败")}const D=await j.json();if(D.files&&D.files.length>0){const G=D.files.map(H=>`@${H.name}`).join(" "),de=F?`${F} ${G} `:`${G} `;Z(de),A.current&&A.current.focus()}}catch(E){console.error("File upload failed:",E)}finally{$(!1),O.current&&(O.current.value="")}}},[t,F]),pa=s.useCallback(async i=>{const v=Array.from(i.clipboardData.items);for(const E of v)if(E.type.startsWith("image/")){const j=E.getAsFile();j&&Qt([j])}if(v.length===0&&i.clipboardData.files.length>0){const j=Array.from(i.clipboardData.files).filter(D=>D.type.startsWith("image/"));j.length>0&&Qt(j)}},[Qt]),{getRootProps:xa,getInputProps:ga,isDragActive:ha,open:fa}=Jr({accept:{"image/*":[".png",".jpg",".jpeg",".gif",".webp",".svg"]},maxSize:5*1024*1024,maxFiles:5,onDrop:Qt,noClick:!0,noKeyboard:!0}),jt=s.useCallback(async i=>{if(i.preventDefault(),!F.trim()||ae||!t)return;const v=F.trim();if(v.startsWith("/")){const ee=v.indexOf(" "),fe=ee!==-1?v.slice(0,ee):v,De=Qe.find(We=>We.name===fe&&We.namespace==="builtin");if(De){Gt(De);return}}if(U){const ee=await U();if(ee&&!ee.allowed){q?.(ee.reason);return}}let E=[];if(le.length>0){const ee=new FormData;le.forEach(fe=>{ee.append("images",fe)});try{const fe=await ie(`/api/projects/${t.name}/upload-images`,{method:"POST",headers:{},body:ee});if(!fe.ok)throw new Error("图片上传失败");E=(await fe.json()).images}catch(fe){console.error("Image upload failed:",fe),X(De=>[...De,{type:"error",content:`图片上传失败: ${fe.message}`,timestamp:new Date}]);return}}const j={type:"user",content:F,images:E,timestamp:new Date};X(ee=>[...ee,j]),W(!0),K(!0),yt({text:"处理中",tokens:0,can_interrupt:!0}),ke(!1),setTimeout(()=>ut(),100);const G=M||r?.id||`new-session-${Date.now()}`;u&&u(G);const H=(()=>{try{const ee=He.getItem("claude-settings");if(ee)return JSON.parse(ee)}catch(ee){console.error("Error loading tools settings:",ee)}return{allowedTools:[],disallowedTools:[],skipPermissions:!1}})();o({type:"claude-command",command:F,options:{projectPath:t.path,cwd:t.fullPath,sessionId:M,resume:!!M,toolsSettings:H,permissionMode:B,model:ht,images:E}}),Z(""),ue([]),Be(new Map),Ue(new Map),xt(!1),A.current&&(A.current.style.height="auto"),t&&He.removeItem(`draft_input_${t.name}`)},[F,ae,t,le,M,r,mt,B,u,ht,o,Z,ue,Be,Ue,xt,A,X,W,K,yt,ke,ut,U,q,Gt,Qe]),rs=s.useCallback(i=>!i||mt!=="claude"?{success:!1}:Fo(i.entry),[mt]),fr=s.useCallback((i,v)=>{const j=(Array.isArray(i)?i:[i]).filter(Boolean);j.length!==0&&(j.forEach(D=>{o({type:"claude-permission-response",requestId:D,allow:!!v?.allow,updatedInput:v?.updatedInput,message:v?.message,rememberEntry:v?.rememberEntry})}),_(D=>{const G=D.filter(de=>!j.includes(de.requestId));return G.length===0&&yt(null),G}))},[o]);s.useEffect(()=>{hr.current=jt},[jt]);const ss=i=>{if(!i)return;const v=F.slice(0,Yr),E=F.slice(Yr),j=E.indexOf(" "),D=j!==-1?E.slice(j):"",G=v+i.name+" "+D;Z(G),lt(!1),gt(-1),ct(""),dt(-1),_e.current&&clearTimeout(_e.current),Gt(i)},ba=i=>{if(Ye&&vt.length>0){if(i.key==="ArrowDown"){i.preventDefault(),dt(v=>v<vt.length-1?v+1:0);return}if(i.key==="ArrowUp"){i.preventDefault(),dt(v=>v>0?v-1:vt.length-1);return}if(i.key==="Tab"||i.key==="Enter"){i.preventDefault(),xr>=0?ss(vt[xr]):vt.length>0&&ss(vt[0]);return}if(i.key==="Escape"){i.preventDefault(),lt(!1),gt(-1),ct(""),dt(-1),_e.current&&clearTimeout(_e.current);return}}if(pt&&st.length>0){if(i.key==="ArrowDown"){i.preventDefault(),Ft(v=>v<st.length-1?v+1:0);return}if(i.key==="ArrowUp"){i.preventDefault(),Ft(v=>v>0?v-1:st.length-1);return}if(i.key==="Tab"||i.key==="Enter"){i.preventDefault(),St>=0?br(st[St]):st.length>0&&br(st[0]);return}if(i.key==="Escape"){i.preventDefault(),qe(!1);return}}if(i.key==="Tab"&&!pt&&!Ye){i.preventDefault();const v=["default","acceptEdits","bypassPermissions","plan"],j=(v.indexOf(B)+1)%v.length,D=v[j];f(D),r?.id&&localStorage.setItem(`permissionMode-${r.id}`,D);return}if(i.key==="Enter"){if(i.nativeEvent.isComposing)return;(i.ctrlKey||i.metaKey)&&!i.shiftKey?(i.preventDefault(),jt(i)):!i.shiftKey&&!i.ctrlKey&&!i.metaKey&&(p||(i.preventDefault(),jt(i)))}},br=i=>{const v=F.slice(0,Jt),E=F.slice(Jt),j=E.indexOf(" "),D=j!==-1?E.slice(j):"",G=v+"@"+i.path+" "+D,de=v.length+1+i.path.length+1;A.current&&!A.current.matches(":focus")&&A.current.focus(),Z(G),Ct(de),qe(!1),at(-1),A.current&&requestAnimationFrame(()=>{A.current&&(A.current.setSelectionRange(de,de),A.current.matches(":focus")||A.current.focus())})},va=i=>{const v=i.target.value,E=i.target.selectionStart;if(!M&&v.trim(),Z(v),Ct(E),!v.trim()){i.target.style.height="auto",xt(!1),lt(!1),gt(-1),ct("");return}const j=v.slice(0,E);if((j.match(/```/g)||[]).length%2===1){lt(!1),gt(-1),ct("");return}const de=/(^|\s)\/(\S*)$/,H=j.match(de);if(H){const ee=H.index+H[1].length,fe=H[2];gt(ee),lt(!0),dt(-1),_e.current&&clearTimeout(_e.current),_e.current=setTimeout(()=>{ct(fe)},150)}else lt(!1),gt(-1),ct(""),_e.current&&clearTimeout(_e.current)},ya=i=>{Ct(i.target.selectionStart)},ja=()=>{M&&T&&o({type:"abort-session",sessionId:M,provider:mt})},wa=()=>{const i=["default","acceptEdits","bypassPermissions","plan"],E=(i.indexOf(B)+1)%i.length,j=i[E];f(j),r?.id&&localStorage.setItem(`permissionMode-${r.id}`,j)};return t?e.jsxs(e.Fragment,{children:[e.jsx("style",{children:`
74
74
  details[open] .details-chevron {
package/dist/index.html CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Prevent zoom on iOS -->
27
27
  <meta name="format-detection" content="telephone=no" />
28
- <script type="module" crossorigin src="/assets/index-BJ5g44kL.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-BV3BueqH.js"></script>
29
29
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BeVl62c0.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-C_VWDoZS.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/vendor-utils-00TdZexr.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian2018cs/agenthub",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "A web-based UI for AI Agents",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -644,13 +644,22 @@ async function queryClaudeSDK(command, options = {}, ws) {
644
644
  const cacheReadTokens = modelData.cacheReadInputTokens || 0;
645
645
  const cacheCreationTokens = modelData.cacheCreationInputTokens || 0;
646
646
 
647
+ // Extract detailed cache creation tokens if available
648
+ const cacheCreation = modelData.cacheCreation || {};
649
+ const cacheCreation5mTokens = cacheCreation.ephemeral5mInputTokens || 0;
650
+ const cacheCreation1hTokens = cacheCreation.ephemeral1hInputTokens || 0;
651
+ const hasPreciseCacheData = cacheCreation5mTokens > 0 || cacheCreation1hTokens > 0;
652
+
647
653
  const normalizedModel = normalizeModelName(modelKey);
648
654
  const cost = calculateCost({
649
655
  model: normalizedModel,
650
656
  inputTokens,
651
657
  outputTokens,
652
658
  cacheReadTokens,
653
- cacheCreationTokens
659
+ // Use precise data if available, otherwise fallback to legacy field
660
+ cacheCreation5mTokens: hasPreciseCacheData ? cacheCreation5mTokens : undefined,
661
+ cacheCreation1hTokens: hasPreciseCacheData ? cacheCreation1hTokens : undefined,
662
+ cacheCreationTokens: hasPreciseCacheData ? undefined : cacheCreationTokens
654
663
  });
655
664
 
656
665
  // Insert usage record
@@ -15,21 +15,24 @@ const PRICING_PER_MILLION = {
15
15
  input: 5.00,
16
16
  output: 25.00,
17
17
  cacheRead: 0.50,
18
- cacheCreate: 6.25
18
+ cacheCreate5m: 6.25, // 1.25x base (5-minute ephemeral cache)
19
+ cacheCreate1h: 10.00 // 2x base (1-hour extended cache)
19
20
  },
20
21
  // Claude Sonnet 4.5
21
22
  'claude-sonnet-4-5-20250929': {
22
23
  input: 3.00,
23
24
  output: 15.00,
24
25
  cacheRead: 0.30,
25
- cacheCreate: 3.75
26
+ cacheCreate5m: 3.75, // 1.25x base
27
+ cacheCreate1h: 6.00 // 2x base
26
28
  },
27
29
  // Claude Haiku 4.5
28
30
  'claude-haiku-4-5-20251001': {
29
31
  input: 1.00,
30
32
  output: 5.00,
31
33
  cacheRead: 0.10,
32
- cacheCreate: 1.25
34
+ cacheCreate5m: 1.25, // 1.25x base
35
+ cacheCreate1h: 2.00 // 2x base
33
36
  },
34
37
  // ============ Legacy Models ============
35
38
  // Claude Opus 4.1
@@ -37,42 +40,48 @@ const PRICING_PER_MILLION = {
37
40
  input: 15.00,
38
41
  output: 75.00,
39
42
  cacheRead: 1.50,
40
- cacheCreate: 18.75
43
+ cacheCreate5m: 18.75, // 1.25x base
44
+ cacheCreate1h: 30.00 // 2x base
41
45
  },
42
46
  // Claude Opus 4
43
47
  'claude-opus-4-20250514': {
44
48
  input: 15.00,
45
49
  output: 75.00,
46
50
  cacheRead: 1.50,
47
- cacheCreate: 18.75
51
+ cacheCreate5m: 18.75, // 1.25x base
52
+ cacheCreate1h: 30.00 // 2x base
48
53
  },
49
54
  // Claude Sonnet 4
50
55
  'claude-sonnet-4-20250514': {
51
56
  input: 3.00,
52
57
  output: 15.00,
53
58
  cacheRead: 0.30,
54
- cacheCreate: 3.75
59
+ cacheCreate5m: 3.75, // 1.25x base
60
+ cacheCreate1h: 6.00 // 2x base
55
61
  },
56
62
  // Claude Sonnet 3.7
57
63
  'claude-3-7-sonnet-20250219': {
58
64
  input: 3.00,
59
65
  output: 15.00,
60
66
  cacheRead: 0.30,
61
- cacheCreate: 3.75
67
+ cacheCreate5m: 3.75, // 1.25x base
68
+ cacheCreate1h: 6.00 // 2x base
62
69
  },
63
70
  // Claude Haiku 3.5
64
71
  'claude-3-5-haiku-20241022': {
65
72
  input: 0.80,
66
73
  output: 4.00,
67
74
  cacheRead: 0.08,
68
- cacheCreate: 1.00
75
+ cacheCreate5m: 1.00, // 1.25x base
76
+ cacheCreate1h: 1.60 // 2x base
69
77
  },
70
78
  // Claude Haiku 3
71
79
  'claude-3-haiku-20240307': {
72
80
  input: 0.25,
73
81
  output: 1.25,
74
82
  cacheRead: 0.03,
75
- cacheCreate: 0.30
83
+ cacheCreate5m: 0.30, // Per pricing doc (not exactly 1.25x)
84
+ cacheCreate1h: 0.50 // 2x base
76
85
  },
77
86
  // ============ Aliases ============
78
87
  // Aliases for simplified model names (pointing to latest versions)
@@ -80,19 +89,22 @@ const PRICING_PER_MILLION = {
80
89
  input: 3.00,
81
90
  output: 15.00,
82
91
  cacheRead: 0.30,
83
- cacheCreate: 3.75
92
+ cacheCreate5m: 3.75, // 1.25x base
93
+ cacheCreate1h: 6.00 // 2x base
84
94
  },
85
95
  'opus': {
86
96
  input: 5.00,
87
97
  output: 25.00,
88
98
  cacheRead: 0.50,
89
- cacheCreate: 6.25
99
+ cacheCreate5m: 6.25, // 1.25x base
100
+ cacheCreate1h: 10.00 // 2x base
90
101
  },
91
102
  'haiku': {
92
103
  input: 1.00,
93
104
  output: 5.00,
94
105
  cacheRead: 0.10,
95
- cacheCreate: 1.25
106
+ cacheCreate5m: 1.25, // 1.25x base
107
+ cacheCreate1h: 2.00 // 2x base
96
108
  }
97
109
  };
98
110
 
@@ -103,7 +115,8 @@ for (const [model, prices] of Object.entries(PRICING_PER_MILLION)) {
103
115
  input: prices.input / 1_000_000,
104
116
  output: prices.output / 1_000_000,
105
117
  cacheRead: prices.cacheRead / 1_000_000,
106
- cacheCreate: prices.cacheCreate / 1_000_000
118
+ cacheCreate5m: prices.cacheCreate5m / 1_000_000,
119
+ cacheCreate1h: prices.cacheCreate1h / 1_000_000
107
120
  };
108
121
  }
109
122
 
@@ -144,7 +157,9 @@ function normalizeModelName(model) {
144
157
  * @param {number} usage.inputTokens - Input tokens
145
158
  * @param {number} usage.outputTokens - Output tokens
146
159
  * @param {number} usage.cacheReadTokens - Cache read tokens
147
- * @param {number} usage.cacheCreationTokens - Cache creation tokens
160
+ * @param {number} usage.cacheCreation5mTokens - 5-minute ephemeral cache creation tokens
161
+ * @param {number} usage.cacheCreation1hTokens - 1-hour extended cache creation tokens
162
+ * @param {number} [usage.cacheCreationTokens] - Legacy: total cache creation tokens (fallback for SDK that doesn't distinguish)
148
163
  * @returns {number} Cost in USD
149
164
  */
150
165
  function calculateCost(usage) {
@@ -159,7 +174,17 @@ function calculateCost(usage) {
159
174
  const inputCost = (usage.inputTokens || 0) * prices.input;
160
175
  const outputCost = (usage.outputTokens || 0) * prices.output;
161
176
  const cacheReadCost = (usage.cacheReadTokens || 0) * prices.cacheRead;
162
- const cacheCreateCost = (usage.cacheCreationTokens || 0) * prices.cacheCreate;
177
+
178
+ // Calculate cache creation cost with distinction between 5m and 1h
179
+ let cacheCreateCost = 0;
180
+ if (usage.cacheCreation5mTokens !== undefined || usage.cacheCreation1hTokens !== undefined) {
181
+ // Use precise calculation with separate 5m and 1h tokens
182
+ cacheCreateCost = (usage.cacheCreation5mTokens || 0) * prices.cacheCreate5m +
183
+ (usage.cacheCreation1hTokens || 0) * prices.cacheCreate1h;
184
+ } else {
185
+ // Fallback: use legacy cacheCreationTokens with 5m price (default assumption)
186
+ cacheCreateCost = (usage.cacheCreationTokens || 0) * prices.cacheCreate5m;
187
+ }
163
188
 
164
189
  return inputCost + outputCost + cacheReadCost + cacheCreateCost;
165
190
  }
@@ -209,14 +209,26 @@ async function scanSessionFile(userUuid, sessionId, filePath, startLine) {
209
209
  const inputTokens = usage.input_tokens || 0;
210
210
  const outputTokens = usage.output_tokens || 0;
211
211
  const cacheReadTokens = usage.cache_read_input_tokens || 0;
212
- const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
212
+
213
+ // Extract detailed cache creation tokens from nested cache_creation object
214
+ // Structure: usage.cache_creation.ephemeral_5m_input_tokens / ephemeral_1h_input_tokens
215
+ const cacheCreation = usage.cache_creation || {};
216
+ const cacheCreation5mTokens = cacheCreation.ephemeral_5m_input_tokens || 0;
217
+ const cacheCreation1hTokens = cacheCreation.ephemeral_1h_input_tokens || 0;
218
+
219
+ // Fallback to legacy field if nested object not present
220
+ const totalCacheCreationTokens = usage.cache_creation_input_tokens || 0;
221
+ const hasPreciseCacheData = cacheCreation5mTokens > 0 || cacheCreation1hTokens > 0;
213
222
 
214
223
  const cost = calculateCost({
215
224
  model,
216
225
  inputTokens,
217
226
  outputTokens,
218
227
  cacheReadTokens,
219
- cacheCreationTokens
228
+ // Use precise data if available, otherwise fallback to legacy field
229
+ cacheCreation5mTokens: hasPreciseCacheData ? cacheCreation5mTokens : undefined,
230
+ cacheCreation1hTokens: hasPreciseCacheData ? cacheCreation1hTokens : undefined,
231
+ cacheCreationTokens: hasPreciseCacheData ? undefined : totalCacheCreationTokens
220
232
  });
221
233
 
222
234
  // Determine the date from the entry timestamp or use current date
@@ -227,7 +239,7 @@ async function scanSessionFile(userUuid, sessionId, filePath, startLine) {
227
239
  // Uses session_id (or user_uuid) + model + all token counts + time window to match
228
240
  if (usageDb.checkRecordExists(
229
241
  userUuid, sessionId, model, inputTokens, outputTokens,
230
- cacheReadTokens, cacheCreationTokens, entryTimestamp
242
+ cacheReadTokens, totalCacheCreationTokens, entryTimestamp
231
243
  )) {
232
244
  // Record already exists (likely from SDK), skip to avoid duplicate counting
233
245
  continue;
@@ -241,7 +253,7 @@ async function scanSessionFile(userUuid, sessionId, filePath, startLine) {
241
253
  input_tokens: inputTokens,
242
254
  output_tokens: outputTokens,
243
255
  cache_read_tokens: cacheReadTokens,
244
- cache_creation_tokens: cacheCreationTokens,
256
+ cache_creation_tokens: totalCacheCreationTokens,
245
257
  cost_usd: cost,
246
258
  source: 'cli',
247
259
  created_at: entryTimestamp