@ai.weget.jp/bot 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,13 +37,16 @@ The host does not store or receive OpenAI keys from Platform.
37
37
 
38
38
  AI interaction is routed through local Codex only.
39
39
 
40
+ Codex MCP integration is moving toward a single `weget-gateway` entry instead of
41
+ direct per-tool MCP registration.
42
+
40
43
  Default built-in endpoints:
41
44
 
42
45
  - `loginApiUrl=https://api.weget.jp/login`
43
46
  - `botUploadApiUrl=https://api.weget.jp/bot/upload-url`
44
47
  - `gmoCryptoPrivateApiBaseUrl=https://api.coin.z.com/private`
45
48
  - `gmoFxPrivateApiBaseUrl=https://forex-api.coin.z.com/private`
46
- - `aiModel=gpt-4.1-mini`
49
+ - `aiModel=gpt-5.4`
47
50
 
48
51
  ## Local UI Structure
49
52
 
@@ -51,5 +54,13 @@ Default built-in endpoints:
51
54
  - `Codex Console`: local chat with Codex
52
55
  - `Installed Skills`: per-skill pages such as `skill-gmo-coin`, `skill-gmo-fx`, `skill-browser`
53
56
 
57
+ ## MCP Direction
58
+
59
+ - preferred Codex MCP entry: `weget-gateway`
60
+ - browser runtime prerequisite: `npx playwright install chromium`
61
+ - legacy direct `playwright` MCP registration is being phased out in favor of the gateway
62
+ - macro tools now prefer a local host bridge path:
63
+ `Codex -> weget-gateway -> bot-host bridge -> skill runtime -> protected API`
64
+
54
65
  The package is the public runtime host. Private control plane behavior remains
55
66
  in `platform/` and `infra/`.
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./skills.js";export*from"./skillHostState.js";export*from"./platformApi.js";export*from"./gmoFxRuntime.js";export*from"./gmoCoinRuntime.js";export const createBotHostManifest=()=>({name:"@ai.weget.jp/bot",version:"0.1.11",skills:[]});
1
+ export*from"./skills.js";export*from"./skillHostState.js";export*from"./platformApi.js";export const createBotHostManifest=()=>({name:"@ai.weget.jp/bot",version:"0.1.12",skills:[]});
@@ -1 +1 @@
1
- const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,getFxApiConfig:s,saveFxApiConfig:a,getCoinApiConfig:c,saveCoinApiConfig:g,getAiConfig:m,saveAiConfig:u,setCurrentSession:d,getCurrentSession:l,getActiveSkills:S,getCodexAuthStatus:k,startCodexLogin:y,getManagedSkills:f,emitLog:p,openGmoKlineWindow:h,getGmoMarketQuotes:b,getGmoSymbolRules:C,getGmoBuyingPower:w,getGmoAccountMetrics:L,getGmoPositionSummary:x,getGmoOpenPositions:E,placeGmoFxOrder:I,placeGmoCoinOrder:A,placeGmoFxCloseOrder:v,placeGmoCoinCloseOrder:U,getGmoApiState:G,testGmoApi:q,buildTradeStatusSnapshot:F,closeTradeWindows:P,startWsClient:z,stopWsClient:B,handleBotChatMessage:N,writeErrorLog:M,writeRuntimeLog:O})=>{e.handle("auth:login",async(e,t)=>{const n=r(t),s=String(n.botId||"").trim();if(!s)return{ok:!1,error:"Bot ID is required"};try{const r=await o(String(n.email||""),String(n.password||""));await i({botId:s,email:String(n.email||"").trim(),password:String(n.password||""),remember:Boolean(n.remember)});const e={...r,botId:s};return d(e),z({botId:s,userId:String(r.userId||""),accessToken:String(r.accessToken||"")}),p("[auth] login success",{userId:r.userId||"",email:r.email||"",botId:s}),{ok:!0,session:{userId:String(r.userId||""),email:String(r.email||""),botId:s}}}catch(r){const e=r instanceof Error?r.message:String(r);return p("[auth] login failed",{error:e}),{ok:!1,error:e}}}),e.handle("auth:logout",async()=>(B(),d(null),P(),p("[auth] logout"),{ok:!0})),e.handle("auth:getSavedProfile",async()=>{try{return{ok:!0,profile:await n()}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getFxApiConfig",async()=>({ok:!0,config:s()})),e.handle("trade:saveFxApiConfig",async(r,e)=>{try{return{ok:!0,config:await a(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getCoinApiConfig",async()=>({ok:!0,config:c()})),e.handle("trade:saveCoinApiConfig",async(r,e)=>{try{return{ok:!0,config:await g(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("ai:getConfig",async()=>({ok:!0,config:m()})),e.handle("ai:saveConfig",async(r,e)=>{try{return{ok:!0,config:await u(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("bot:getRuntimeInfo",async()=>{const e=l(),o=r(e),i=await k();return{ok:!0,status:e?"connected":"disconnected",session:e?{userId:String(o.userId||""),email:String(o.email||""),botId:String(o.botId||"")}:null,runtime:{loginApiUrl:t.loginApiUrl,capabilities:t.capabilities,gmoFxApiState:G("fx"),gmoCoinApiState:G("coin"),aiModel:m().aiModel,activeSkills:S(),codexAuthStatus:i.status,codexAuthDetail:i.detail}}}),e.handle("bot:getSkills",async()=>({ok:!0,skills:f()})),e.handle("codex:startLogin",async()=>y()),e.handle("trade:openGmoKlineWindow",async(e,t)=>{const o=r(t),i=String(o.symbol||"BTC_JPY").trim()||"BTC_JPY",n=String(o.interval||"1m").trim()||"1m",s="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await h({symbol:i,interval:n,market:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getMarketQuotes",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=Array.isArray(o.symbols)?o.symbols.map(r=>String(r||"").trim().toUpperCase()).filter(Boolean):void 0;try{return{ok:!0,...await b({market:i,symbols:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getSymbolRules",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await C({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getBuyingPower",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await w({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getAccountMetrics",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await L({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getPositionSummary",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=String(o.symbol||"").trim().toUpperCase();if("coin"===i&&!n)return{ok:!1,error:"symbol is required for coin positionSummary"};try{return{ok:!0,...await x({market:i,symbol:n||void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getOpenPositions",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=String(o.symbol||"").trim().toUpperCase();if("coin"===i&&!n)return{ok:!1,error:"symbol is required for coin openPositions"};const s=Number(o.page),a=Number(o.prevId),c=Number(o.count);try{return{ok:!0,...await E({market:i,symbol:n||void 0,page:Number.isFinite(s)?s:void 0,prevId:Number.isFinite(a)?a:void 0,count:Number.isFinite(c)?c:void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeFxOrder",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await I({symbol:i,side:n,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeCoinOrder",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await A({symbol:i,side:n,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeFxPosition",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=Number(o.positionId),a=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(s)||s<=0)return{ok:!1,error:"positionId is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await v({symbol:i,side:n,positionId:s,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeCoinPosition",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=Number(o.positionId),a=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(s)||s<=0)return{ok:!1,error:"positionId is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await U({symbol:i,side:n,positionId:s,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:testGmoApi",async(e,t)=>{const o=r(t),i="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";return q(i)}),e.handle("trade:getStatusSnapshot",async(e,t)=>{const o=r(t),i="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";try{return{ok:!0,snapshot:await F(i)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("chat:send",async(e,t)=>{const o=r(t),i=String(o.message||"").trim();if(!i)return{ok:!1,error:"message is required"};try{return{ok:!0,answer:(await N({text:i})).answer}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:writeError",async(e,t)=>{const o=r(t),i=String(o.source||"").trim()||"ui",n=String(o.message||"").trim()||"unknown error";try{return await M({source:i,message:n,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:write",async(e,t)=>{const o=r(t),i=String(o.level||"").trim().toLowerCase(),n="error"===i?"error":"debug"===i?"debug":"info",s=String(o.source||"").trim()||"ui",a=String(o.message||"").trim()||"log";try{return await O({level:n,source:s,message:a,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}})};
1
+ const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:n,readSavedLoginProfile:i,getSkillConfig:a,saveSkillConfig:s,getAiConfig:c,saveAiConfig:g,setCurrentSession:l,getCurrentSession:m,getActiveSkills:u,getCodexAuthStatus:d,getCodexMcpStatus:S,getPlaywrightBrowserStatus:y,startCodexLogin:k,installPlaywrightMcp:h,getGatewayContextFilePath:f,runGatewaySelfTest:w,getManagedSkills:p,getMacroSnapshot:b,getMacroCalendar:C,getMacroNews:E,emitLog:x,openGmoKlineWindow:L,getGmoMarketQuotes:I,getGmoSymbolRules:v,getGmoBuyingPower:P,getGmoAccountMetrics:A,getGmoPositionSummary:G,getGmoOpenPositions:U,placeGmoFxOrder:q,placeGmoCoinOrder:M,placeGmoFxCloseOrder:B,placeGmoCoinCloseOrder:N,getGmoApiState:z,testGmoApi:F,buildTradeStatusSnapshot:K,closeTradeWindows:O,startWsClient:T,stopWsClient:W,handleBotChatMessage:Y,writeErrorLog:R,writeRuntimeLog:_})=>{e.handle("auth:login",async(e,t)=>{const i=r(t),a=String(i.botId||"").trim();if(!a)return{ok:!1,error:"Bot ID is required"};try{const r=await o(String(i.email||""),String(i.password||""));await n({botId:a,email:String(i.email||"").trim(),password:String(i.password||""),remember:Boolean(i.remember)});const e={...r,botId:a};return l(e),T({botId:a,userId:String(r.userId||""),accessToken:String(r.accessToken||"")}),x("[auth] login success",{userId:r.userId||"",email:r.email||"",botId:a}),{ok:!0,session:{userId:String(r.userId||""),email:String(r.email||""),botId:a}}}catch(r){const e=r instanceof Error?r.message:String(r);return x("[auth] login failed",{error:e}),{ok:!1,error:e}}}),e.handle("auth:logout",async()=>(W(),l(null),O(),x("[auth] logout"),{ok:!0})),e.handle("auth:getSavedProfile",async()=>{try{return{ok:!0,profile:await i()}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("skill:getConfig",async(r,e)=>{try{const r=String(e||"").trim();return{ok:!0,...a(r)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("skill:saveConfig",async(e,t)=>{try{const e=r(t),o=String(e.skillName||"").trim();return{ok:!0,...await s(o,e.config)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("ai:getConfig",async()=>({ok:!0,config:c()})),e.handle("ai:saveConfig",async(r,e)=>{try{return{ok:!0,config:await g(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("bot:getRuntimeInfo",async()=>{const e=m(),o=r(e),n=await d();return{ok:!0,status:e?"connected":"disconnected",session:e?{userId:String(o.userId||""),email:String(o.email||""),botId:String(o.botId||"")}:null,runtime:{loginApiUrl:t.loginApiUrl,capabilities:t.capabilities,gmoFxApiState:z("fx"),gmoCoinApiState:z("coin"),aiModel:c().aiModel,activeSkills:u(),codexAuthStatus:n.status,codexAuthDetail:n.detail}}}),e.handle("bot:getSkills",async()=>({ok:!0,skills:p()})),e.handle("codex:startLogin",async()=>k()),e.handle("browser:getPlaywrightMcpStatus",async()=>{try{return{ok:!0,...await S("weget-gateway")}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("browser:getPlaywrightBrowserStatus",async()=>{try{return{ok:!0,...await y()}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("browser:installPlaywrightMcp",async()=>h()),e.handle("gateway:getStatus",async()=>{try{const[r,e,t]=await Promise.all([d(),S("weget-gateway"),y()]);return{ok:!0,codexAuth:r,gatewayStatus:e,browserStatus:t,contextFilePath:f(),skills:p().map(r=>({name:r.name,displayName:r.displayName,enabled:r.enabled,installStatus:r.installStatus,tools:r.tools}))}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("gateway:runSelfTest",async(e,t)=>{const o=r(t),n=String(o.target||"").trim().toLowerCase(),i="browser"===n||"macro"===n||"coin"===n||"fx"===n||"codex_macro"===n?n:"gateway";try{return await w(i)}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("macro:getSnapshot",async(e,t)=>{const o=r(t);try{return{ok:!0,snapshot:await b({force:Boolean(o.force)})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("macro:getCalendar",async(e,t)=>{const o=r(t),n=String(o.dateKey||"").trim();if(!n)return{ok:!1,error:"dateKey is required"};try{return{ok:!0,rows:await C({dateKey:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("macro:getNews",async(e,t)=>{const o=r(t),n=String(o.dateKey||"").trim();if(!n)return{ok:!1,error:"dateKey is required"};try{return{ok:!0,rows:await E({dateKey:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:openGmoKlineWindow",async(e,t)=>{const o=r(t),n=String(o.symbol||"BTC_JPY").trim()||"BTC_JPY",i=String(o.interval||"1m").trim()||"1m",a="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await L({symbol:n,interval:i,market:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getMarketQuotes",async(e,t)=>{const o=r(t),n="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",i=Array.isArray(o.symbols)?o.symbols.map(r=>String(r||"").trim().toUpperCase()).filter(Boolean):void 0;try{return{ok:!0,...await I({market:n,symbols:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getSymbolRules",async(e,t)=>{const o=r(t),n="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await v({market:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getBuyingPower",async(e,t)=>{const o=r(t),n="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await P({market:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getAccountMetrics",async(e,t)=>{const o=r(t),n="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await A({market:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getPositionSummary",async(e,t)=>{const o=r(t),n="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",i=String(o.symbol||"").trim().toUpperCase();if("coin"===n&&!i)return{ok:!1,error:"symbol is required for coin positionSummary"};try{return{ok:!0,...await G({market:n,symbol:i||void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getOpenPositions",async(e,t)=>{const o=r(t),n="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",i=String(o.symbol||"").trim().toUpperCase();if("coin"===n&&!i)return{ok:!1,error:"symbol is required for coin openPositions"};const a=Number(o.page),s=Number(o.prevId),c=Number(o.count);try{return{ok:!0,...await U({market:n,symbol:i||void 0,page:Number.isFinite(a)?a:void 0,prevId:Number.isFinite(s)?s:void 0,count:Number.isFinite(c)?c:void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeFxOrder",async(e,t)=>{const o=r(t),n=String(o.symbol||"").trim().toUpperCase(),i="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",a=String(o.size||"").trim();if(!n)return{ok:!1,error:"symbol is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await q({symbol:n,side:i,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeCoinOrder",async(e,t)=>{const o=r(t),n=String(o.symbol||"").trim().toUpperCase(),i="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",a=String(o.size||"").trim();if(!n)return{ok:!1,error:"symbol is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await M({symbol:n,side:i,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeFxPosition",async(e,t)=>{const o=r(t),n=String(o.symbol||"").trim().toUpperCase(),i="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",a=Number(o.positionId),s=String(o.size||"").trim();if(!n)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(a)||a<=0)return{ok:!1,error:"positionId is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await B({symbol:n,side:i,positionId:a,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeCoinPosition",async(e,t)=>{const o=r(t),n=String(o.symbol||"").trim().toUpperCase(),i="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",a=Number(o.positionId),s=String(o.size||"").trim();if(!n)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(a)||a<=0)return{ok:!1,error:"positionId is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await N({symbol:n,side:i,positionId:a,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:testGmoApi",async(e,t)=>{const o=r(t),n="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";return F(n)}),e.handle("trade:getStatusSnapshot",async(e,t)=>{const o=r(t),n="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";try{return{ok:!0,snapshot:await K(n)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("chat:send",async(e,t)=>{const o=r(t),n=String(o.message||"").trim();if(!n)return{ok:!1,error:"message is required"};try{return{ok:!0,answer:(await Y({text:n})).answer}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:writeError",async(e,t)=>{const o=r(t),n=String(o.source||"").trim()||"ui",i=String(o.message||"").trim()||"unknown error";try{return await R({source:n,message:i,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:write",async(e,t)=>{const o=r(t),n=String(o.level||"").trim().toLowerCase(),i="error"===n?"error":"debug"===n?"debug":"info",a=String(o.source||"").trim()||"ui",s=String(o.message||"").trim()||"log";try{return await _({level:i,source:a,message:s,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}})};
package/dist/src/main.js CHANGED
@@ -1 +1 @@
1
- import e from"node:path";import t from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as o}from"node:url";import{createRequire as a}from"node:module";import{SYSTEM_CONFIG as s}from"./config/systemConfig.js";import{BotWsClient as r}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as c}from"./services/codexService.js";import{createWindowManager as g}from"./services/windowManagerService.js";import{createGmoCoinRuntime as d}from"./gmoCoinRuntime.js";import{createGmoFxRuntime as m}from"./gmoFxRuntime.js";import{createSkillHostState as p}from"./skillHostState.js";const u=a(import.meta.url),{app:f,BrowserWindow:y,ipcMain:S,nativeImage:w}=u("electron"),h=o(import.meta.url),k=e.dirname(h),x=["trade","ai"],b=()=>e.join(f.getPath("userData"),"login-profile.json"),A=()=>e.join(f.getPath("userData"),"gmo-fx-config.json"),v=()=>e.join(f.getPath("userData"),"gmo-coin-config.json"),_=()=>e.join(f.getPath("userData"),"skill-states.json");let C=null,P=null;const D=new Map;let L=()=>e.join(f.getPath("documents"),"weget-bot-logs");const E=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},O=async({level:i,source:o,message:a,details:s,ts:r})=>{const n=L();await t.mkdir(n,{recursive:!0});const l=new Date,c=l.getFullYear(),g=String(l.getMonth()+1).padStart(2,"0"),d=String(l.getDate()).padStart(2,"0"),m=e.join(n,`${i}-${c}${g}${d}.log`),p=JSON.stringify({ts:r||l.toISOString(),level:i,source:o,message:a,details:s??null},null,0);await t.appendFile(m,`${p}\n`,"utf8")},j=g({BrowserWindow:y,preloadPath:e.join(k,"preload.cjs"),rendererDir:e.join(k,"renderer"),windowIconPath:e.join(k,"renderer","logo.png")}),I=(e,t)=>{const i=(new Date).toISOString();j.emitBotLog({message:e,data:t||null,ts:i}),O({level:E(e),source:"runtime",message:e,details:t,ts:i}).catch(()=>{})},M=e=>{j.emitChatEvent(e)},B=e=>e&&"object"==typeof e?e:{},T=m({configPath:A(),emitLog:I,getRuntimeConfig:()=>({cryptoApiBaseUrl:s.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:s.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:s.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:s.gmoFxPublicApiBaseUrl,assetsPath:s.gmoAssetsPath,fxAssetsPath:s.gmoFxAssetsPath,coinMarginPath:s.gmoCoinMarginPath,positionsPath:s.gmoPositionsPath,positionSummaryPath:s.gmoPositionSummaryPath,fxOrderPath:s.gmoFxOrderPath,coinCloseOrderPath:s.gmoCoinCloseOrderPath,fxCloseOrderPath:s.gmoFxCloseOrderPath}),emitTradeExecution:e=>{j.emitTradeExecution(e)},emitTradeHistory:e=>{const t=`trade-${Date.now()}`;P?.sendTaskEvent(t,"trade_history",e),I("[trade] history event sent (execution)",e)}}),U=d({configPath:v(),emitLog:I,getRuntimeConfig:()=>({cryptoApiBaseUrl:s.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:s.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:s.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:s.gmoFxPublicApiBaseUrl,assetsPath:s.gmoAssetsPath,fxAssetsPath:s.gmoFxAssetsPath,coinMarginPath:s.gmoCoinMarginPath,positionsPath:s.gmoPositionsPath,positionSummaryPath:s.gmoPositionSummaryPath,fxOrderPath:s.gmoFxOrderPath,coinCloseOrderPath:s.gmoCoinCloseOrderPath,fxCloseOrderPath:s.gmoFxCloseOrderPath}),emitTradeExecution:e=>{j.emitTradeExecution(e)},emitTradeHistory:e=>{const t=`trade-${Date.now()}`;P?.sendTaskEvent(t,"trade_history",e),I("[trade] history event sent (execution)",e)}}),F=p({gmoFxConfigPath:A(),gmoCoinConfigPath:v(),log:I});T.loadConfig(),U.loadConfig(),async function(){try{const e=await t.readFile(_(),"utf8"),i=JSON.parse(e);return(Array.isArray(i?.items)?i.items:[]).filter(e=>Boolean(e&&"object"==typeof e)).map(e=>({user_id:String(e.user_id||""),bot_id:String(e.bot_id||""),skill_id:String(e.skill_id||""),package_name:String(e.package_name||""),enabled:Boolean(e.enabled),install_status:String(e.install_status||""),version:e.version?String(e.version):null,config_json:B(e.config_json),created_at:e.created_at?String(e.created_at):void 0,updated_at:e.updated_at?String(e.updated_at):void 0}))}catch(e){if("ENOENT"===e?.code)return[];throw e}}().then(e=>{e.length>0&&(F.applySnapshots(e),I("[skills] restored local snapshot",{count:e.length}))}).catch(e=>{I("[skills] failed to restore local snapshot",{error:e instanceof Error?e.message:String(e)})}),L=()=>String(T.getConfig().errorLogDir||U.getConfig().errorLogDir||"").trim()||e.join(f.getPath("documents"),"weget-bot-logs");const G=c({getModel:()=>T.getConfig().aiModel||U.getConfig().aiModel,getWorkingDir:()=>f.getPath("userData"),emitLog:I}),J=l({getEnv:()=>({loginApiUrl:s.loginApiUrl,botUploadApiUrl:s.botUploadApiUrl}),emitLog:I,getCurrentSession:()=>C});const K=()=>{const e=F.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),t=F.isSkillEnabled("@ai.weget.jp/skill-gmo-coin");"connected"===P?.getStatus()&&e?T.startExecutionStreams():T.stopExecutionStreams(),"connected"===P?.getStatus()&&t?U.startExecutionStreams():U.stopExecutionStreams(),I("[skills] applied managed states",{active_skills:F.getActiveSkills().map(e=>e.name)})},R=async()=>{const e=J.getBotSkillsListApiUrl(),i=String(C?.botId||"").trim();if(!e||!i)return;const o=await J.callBotApi(e,{bot_id:i}),a=Array.isArray(o.states)?o.states.filter(e=>Boolean(e&&"object"==typeof e)).map(e=>({user_id:String(e.user_id||""),bot_id:String(e.bot_id||""),skill_id:String(e.skill_id||""),package_name:String(e.package_name||""),enabled:Boolean(e.enabled),install_status:String(e.install_status||""),version:e.version?String(e.version):null,config_json:B(e.config_json),created_at:e.created_at?String(e.created_at):void 0,updated_at:e.updated_at?String(e.updated_at):void 0})):[];F.applySnapshots(a),await(async e=>{await t.writeFile(_(),JSON.stringify({updatedAt:(new Date).toISOString(),items:e},null,2),"utf8")})(a),K(),I("[config] skill states synced",{count:a.length,bot_id:i,enabled:a.filter(e=>e.enabled).length})},N=e=>{if("fx"===e&&!F.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===e&&!F.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")};P=new r({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:x,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(e,t)=>I(e,t),onStatus:e=>j.emitBotStatus({status:e}),onLineMessage:async e=>{const t=String(e.line_user_id||""),i=String(e.text||"").trim();if(M({type:"line_user",lineUserId:t,text:i}),!i)return{answer:""};try{const e=await G.runPrompt({prompt:i,contextLabel:"line_message"});return M({type:"line_assistant",lineUserId:t,text:e}),{answer:e}}catch(e){const i=e instanceof Error?e.message:String(e);I("[ai] line reply failed",{error:i});const o=`AI error: ${i}`;return M({type:"line_assistant",lineUserId:t,text:o}),{answer:o}}},onRunTask:async e=>{const t=String(e.task_id||"").trim(),i=String(e.prompt||"").trim();if(t){if(D.set(t,{cancelled:!1}),P?.sendTaskEvent(t,"task_received",{prompt:i,conversation_id:String(e.conversation_id||""),continue_from_task_id:String(e.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return P?.sendTaskEvent(t,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void D.delete(t);try{P?.sendTaskEvent(t,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const e=await G.runPrompt({prompt:i,contextLabel:`task:${t}`}),o=D.get(t);if(o?.cancelled)return P?.sendTaskEvent(t,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void D.delete(t);P?.sendTaskEvent(t,"ai_output",{summary:e,ts:(new Date).toISOString()}),P?.sendTaskEvent(t,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),P?.sendTaskEvent(t,"task_completed",{summary:e.slice(0,500),ts:(new Date).toISOString()})}catch(e){P?.sendTaskEvent(t,"task_failed",{error:e instanceof Error?e.message:String(e),ts:(new Date).toISOString()})}finally{D.delete(t)}}else I("[task] run_task missing task_id")},onTaskControl:async e=>{const t=String(e.task_id||"").trim(),i=String(e.action||"").trim();if(t&&i&&"cancel_task"===i){const e=D.get(t);if(e)return e.cancelled=!0,void I("[task] cancel requested",{task_id:t});P?.sendTaskEvent(t,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()})}},onServerConfigUpdate:async e=>{const t=Array.isArray(e.scopes)?e.scopes.map(e=>String(e||"").trim()):[],i=t.length>0?t:["runtime","skills"];i.includes("runtime")&&await(async()=>{const e=J.getBotRuntimeConfigApiUrl(),t=String(C?.botId||"").trim();if(!e||!t)return;const i=await J.callBotApi(e,{bot_id:t}),o=String(i.chat_model||"").trim(),a={...o?{aiModel:o}:{}};0!==Object.keys(a).length&&(await T.saveConfig({...T.getConfig(),...a}),await U.saveConfig({...U.getConfig(),...a}),I("[config] runtime config synced",{chat_model:o||""}))})(),i.includes("skills")&&await R()}}),n({ipcMain:S,env:{capabilities:x,loginApiUrl:s.loginApiUrl},signInWithLoginApi:J.signInWithLoginApi,saveLoginProfile:async({botId:e,email:i,password:o,remember:a})=>{if(a)await t.writeFile(b(),JSON.stringify({botId:e,email:i,password:o,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await t.unlink(b())}catch(e){if("ENOENT"!==e?.code)throw e}},readSavedLoginProfile:async()=>{try{const e=await t.readFile(b(),"utf8"),i=JSON.parse(e);return{botId:String(i?.botId||""),email:String(i?.email||""),password:String(i?.password||""),remember:Boolean(i?.remember)}}catch(e){if("ENOENT"===e?.code)return{botId:"",email:"",password:"",remember:!1};throw e}},getFxApiConfig:()=>{const e=T.getConfig();return{riskDailyLossLimitJpy:e.riskDailyLossLimitJpy,fxApiKey:e.fxApiKey,fxApiSecret:e.fxApiSecret,errorLogDir:e.errorLogDir}},saveFxApiConfig:async e=>{const t=await T.saveConfig({...T.getConfig(),...e&&"object"==typeof e?e:{}});return"connected"===P?.getStatus()&&K(),{riskDailyLossLimitJpy:t.riskDailyLossLimitJpy,fxApiKey:t.fxApiKey,fxApiSecret:t.fxApiSecret,errorLogDir:t.errorLogDir}},getCoinApiConfig:()=>{const e=U.getConfig();return{riskDailyLossLimitJpy:e.riskDailyLossLimitJpy,cryptoApiKey:e.cryptoApiKey,cryptoApiSecret:e.cryptoApiSecret,errorLogDir:e.errorLogDir}},saveCoinApiConfig:async e=>{const t=await U.saveConfig({...U.getConfig(),...e&&"object"==typeof e?e:{}});return"connected"===P?.getStatus()&&K(),{riskDailyLossLimitJpy:t.riskDailyLossLimitJpy,cryptoApiKey:t.cryptoApiKey,cryptoApiSecret:t.cryptoApiSecret,errorLogDir:t.errorLogDir}},getAiConfig:()=>{const e=T.getConfig(),t=U.getConfig();return{aiModel:e.aiModel||t.aiModel}},saveAiConfig:async e=>{const t=e&&"object"==typeof e?e:{},i=await T.saveConfig({...T.getConfig(),...t});return await U.saveConfig({...U.getConfig(),...t}),{aiModel:i.aiModel}},setCurrentSession:e=>{C=e||null;const t=C?"connected":"disconnected";j.emitBotStatus({status:t})},getCurrentSession:()=>C,getActiveSkills:()=>F.getActiveSkills().map(e=>({name:e.name,displayName:e.displayName,version:e.version})),getCodexAuthStatus:()=>G.getAuthStatus(),startCodexLogin:()=>G.startLogin(),getManagedSkills:()=>F.getManagedSkills(),emitLog:I,openGmoKlineWindow:async({symbol:e,interval:t,market:i})=>(()=>{const o="fx"===i?"fx":"coin";return N(o),("fx"===o?T.marketData:U.marketData).fetchKline({symbol:e,interval:t,market:o})})(),getGmoMarketQuotes:async({market:e,symbols:t})=>(()=>{const i="fx"===e?"fx":"coin";return N(i),("fx"===i?T.marketData:U.marketData).fetchQuotes({market:i,symbols:t})})(),getGmoSymbolRules:async({market:e})=>(()=>{const t="fx"===e?"fx":"coin";return N(t),("fx"===t?T.marketData:U.marketData).fetchSymbolRules({market:t})})(),getGmoBuyingPower:async({market:e})=>({market:e,availableJpy:await(N(e),("fx"===e?T.gateway:U.gateway).getBuyingPower(e))}),getGmoAccountMetrics:async({market:e})=>(N(e),("fx"===e?T.gateway:U.gateway).getAccountMetrics(e)),getGmoPositionSummary:async({market:e,symbol:t})=>({market:e,symbol:t,items:await(N(e),("fx"===e?T.gateway:U.gateway).getPositionSummary({market:e,symbol:t}))}),getGmoOpenPositions:async({market:e,symbol:t,page:i,prevId:o,count:a})=>({market:e,symbol:t||"",items:await(N(e),("fx"===e?T.gateway:U.gateway).getOpenPositions({market:"fx"===e?"fx":"crypto",symbol:t||void 0,page:i,prevId:o,count:a}))}),placeGmoFxOrder:async({symbol:e,side:t,size:i})=>{N("fx");return await T.gateway.placeFxOrder({symbol:e,side:t,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:e,side:t,size:i})=>{N("coin");return await U.gateway.placeCoinOrder({symbol:e,side:t,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:e,side:t,positionId:i,size:o})=>{N("fx");return await T.gateway.placeFxCloseOrder({symbol:e,side:t,positionId:i,size:o,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:e,side:t,positionId:i,size:o})=>{N("coin");return await U.gateway.placeCoinCloseOrder({symbol:e,side:t,positionId:i,size:o,executionType:"MARKET"})},getGmoApiState:e=>"fx"===e?T.status.getApiState():U.status.getApiState(),testGmoApi:e=>(N(e),"fx"===e?T.status.testApi():U.status.testApi()),buildTradeStatusSnapshot:e=>(N(e),"fx"===e?T.status.buildSnapshot():U.status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:e=>{P?.start(e),K()},stopWsClient:()=>{T.stopExecutionStreams(),U.stopExecutionStreams(),P?.stop()},handleBotChatMessage:async({text:e})=>{M({type:"user",text:String(e||"")});const t=await G.runPrompt({prompt:e,contextLabel:"desktop_chat"});return M({type:"assistant",text:t}),{answer:t}},writeErrorLog:async({source:e,message:t,details:i})=>{await(async({source:e,message:t,details:i})=>{await O({level:"error",source:e,message:t,details:i})})({source:e,message:t,details:i})},writeRuntimeLog:async({level:e,source:t,message:i,details:o})=>{await O({level:e,source:t,message:i,details:o})}});const W=e.join(k,"renderer","logo.png");f.whenReady().then(()=>{(()=>{if("darwin"!==process.platform)return;if(!i(W))return;const e=w.createFromPath(W);e.isEmpty()||f.dock.setIcon(e)})(),j.createMainWindow(),f.on("activate",()=>{0===y.getAllWindows().length&&j.createMainWindow()})}),f.on("window-all-closed",()=>{T.stopExecutionStreams(),U.stopExecutionStreams(),P?.stop(),"darwin"!==process.platform&&f.quit()});
1
+ import t from"node:path";import e from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import{createRequire as s}from"node:module";import{SYSTEM_CONFIG as r}from"./config/systemConfig.js";import{BotWsClient as o}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as g}from"./services/codexService.js";import{createGatewayFileBridgeService as c}from"./services/gatewayFileBridgeService.js";import{createPlatformApiClient as d}from"./platformApi.js";import{createSkillRuntimeManager as m}from"./skillRuntimeManager.js";import{createWindowManager as p}from"./services/windowManagerService.js";import{createSkillHostState as u}from"./skillHostState.js";const f=s(import.meta.url),{app:y,BrowserWindow:S,ipcMain:w,nativeImage:k}=f("electron"),h=a(import.meta.url),b=t.dirname(h),v=["trade","ai"],_=()=>t.join(y.getPath("userData"),"login-profile.json"),x=()=>t.join(y.getPath("userData"),"host-config.json"),C=()=>t.join(y.getPath("userData"),"skill-states.json");let A=null,M=null,P={aiModel:"gpt-5.4",logOutputDir:""};const E=new Map;let O=()=>t.join(y.getPath("documents"),"weget-bot-logs");const D=t=>{const e=String(t||"").toLowerCase();return e.includes("debug")||e.includes("[debug]")?"debug":e.includes("error")||e.includes("failed")||e.includes("exception")?"error":"info"},j=async({level:i,source:a,message:s,details:r,ts:o})=>{const n=O();await e.mkdir(n,{recursive:!0});const l=new Date,g=l.getFullYear(),c=String(l.getMonth()+1).padStart(2,"0"),d=String(l.getDate()).padStart(2,"0"),m=t.join(n,`${i}-${g}${c}${d}.log`),p=JSON.stringify({ts:o||l.toISOString(),level:i,source:a,message:s,details:r??null},null,0);await e.appendFile(m,`${p}\n`,"utf8")},I=p({BrowserWindow:S,preloadPath:t.join(b,"preload.cjs"),rendererDir:t.join(b,"renderer"),windowIconPath:t.join(b,"renderer","logo.png")}),L=(t,e)=>{const i=(new Date).toISOString();I.emitBotLog({message:t,data:e||null,ts:i}),j({level:D(t),source:"runtime",message:t,details:e,ts:i}).catch(()=>{})},B=t=>{I.emitChatEvent(t)},T=t=>t&&"object"==typeof t?t:{},U=u({log:L}),N=l({getEnv:()=>({loginApiUrl:r.loginApiUrl,botUploadApiUrl:r.botUploadApiUrl}),emitLog:L,getCurrentSession:()=>A}),F=d({getBaseUrl:()=>String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),getSession:()=>A,log:L}),G=m({installRoot:t.join(y.getPath("userData"),"skills"),botId:()=>String(A?.botId||"").trim()||void 0,log:L,api:F,getRuntimeConfig:()=>({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:r.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:r.gmoFxPublicApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath}),emitSkillEvent:(t,e)=>{if("trade_execution"!==e.type){if("trade_history"===e.type){const i=`trade-${Date.now()}`;M?.sendTaskEvent(i,"trade_history",{package_name:t,...e.payload&&"object"==typeof e.payload?e.payload:{}}),L("[trade] history event sent (execution)",{package_name:t,payload:e.payload})}}else I.emitTradeExecution(e.payload)}}),K=()=>{const t=G.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime;if(!t)throw new Error("GMO FX skill runtime is not installed");return t},R=()=>{const t=G.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime;if(!t)throw new Error("GMO Coin skill runtime is not installed");return t},W=()=>G.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime,J=()=>G.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime,$=()=>{const t=G.getLoadedSkill("@ai.weget.jp/skill-macro-economy")?.runtime;if(!t)throw new Error("Macro Economy skill runtime is not installed");return t},z=()=>G.getLoadedSkill("@ai.weget.jp/skill-macro-economy")?.runtime,q=(t,e)=>{const i=t.gateway[e];if("function"!=typeof i)throw new Error(`skill runtime does not implement gateway.${e}`);return i},H=t=>{const e=T(t);return{riskDailyLossLimitJpy:Number(e.riskDailyLossLimitJpy||0),fxApiKey:String(e.fxApiKey||"").trim(),fxApiSecret:String(e.fxApiSecret||"").trim(),aiModel:String(e.aiModel||"").trim()||"gpt-5.4"}},Q=t=>{const e=T(t);return{riskDailyLossLimitJpy:Number(e.riskDailyLossLimitJpy||0),cryptoApiKey:String(e.cryptoApiKey||"").trim(),cryptoApiSecret:String(e.cryptoApiSecret||"").trim(),aiModel:String(e.aiModel||"").trim()||"gpt-5.4"}};O=()=>String(P.logOutputDir||"").trim()||t.join(y.getPath("documents"),"weget-bot-logs");const X=g({getModel:()=>{const t=W(),e=J();return P.aiModel||t?.getConfig?.().aiModel||e?.getConfig?.().aiModel||"gpt-5.4"},getWorkingDir:()=>y.getPath("userData"),getLogDir:()=>O(),emitLog:L,getGatewayContext:()=>({activeSkills:U.getManagedSkills().filter(t=>t.enabled).map(t=>({name:t.name,displayName:t.displayName,version:t.version,tools:t.tools})),hostMetadata:{aiModel:P.aiModel||"gpt-5.4",capabilities:v,userDataPath:y.getPath("userData"),platform:process.platform,gmoCoinPublicBaseUrl:r.gmoCryptoPublicApiBaseUrl,gmoFxPublicBaseUrl:r.gmoFxPublicApiBaseUrl},botId:String(A?.botId||"").trim(),platformApiBaseUrl:String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),accessToken:String(A?.accessToken||"").trim(),fileBridgeDir:String(rt.getContext()?.dir||"").trim(),fileBridgeToken:String(rt.getContext()?.token||"").trim()})}),Y=async t=>{const i=T(t);return P={aiModel:String(i.aiModel||P.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i.logOutputDir||"").trim()},await e.writeFile(x(),JSON.stringify({...P,updatedAt:(new Date).toISOString()},null,2),"utf8"),P};const V=async()=>{const t=await G.refreshLoadedSkills();return U.setAvailableSkills(t.map(t=>t.manifest)),t},Z=async t=>{const e=await V();L("[skills] reconciled bundled skills",{desired:t.map(t=>({package_name:t.package_name,install_status:t.install_status,enabled:t.enabled,version:t.version})),bundled:e.map(t=>({package_name:t.packageName,version:t.version}))})},tt=()=>{const t=U.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),e=U.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"),i=W(),a=J();i&&("connected"===M?.getStatus()&&t?i.startExecutionStreams():i.stopExecutionStreams()),a&&("connected"===M?.getStatus()&&e?a.startExecutionStreams():a.stopExecutionStreams()),L("[skills] applied managed states",{active_skills:U.getActiveSkills().map(t=>t.name)})},et=async()=>{const t=N.getBotRuntimeConfigApiUrl(),e=String(A?.botId||"").trim();if(!t||!e)return;const i=await N.callBotApi(t,{bot_id:e}),a=String(i.chat_model||"").trim(),s={...a?{aiModel:a}:{}};if(0===Object.keys(s).length)return;await Y({...P,...s});const r=W(),o=J();r&&await r.saveConfig({...r.getConfig(),...s}),o&&await o.saveConfig({...o.getConfig(),...s}),L("[config] runtime config synced",{chat_model:a||""})},it=async()=>{const t=N.getBotSkillsListApiUrl(),i=String(A?.botId||"").trim();if(!t||!i)return;const a=await N.callBotApi(t,{bot_id:i}),s=Array.isArray(a.states)?a.states.filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:T(t.config_json),created_at:t.created_at?String(t.created_at):void 0,updated_at:t.updated_at?String(t.updated_at):void 0})):[];U.applySnapshots(s),await(async t=>{await e.writeFile(C(),JSON.stringify({updatedAt:(new Date).toISOString(),items:t},null,2),"utf8")})(s),await Z(s),tt(),L("[config] skill states synced",{count:s.length,bot_id:i,enabled:s.filter(t=>t.enabled).length})},at=t=>{if("fx"===t&&!U.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===t&&!U.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")},st=()=>{if(!U.isSkillEnabled("@ai.weget.jp/skill-macro-economy"))throw new Error("Macro Economy skill is disabled")},rt=c({getMacroSnapshot:async({force:t}={})=>(st(),$().getSnapshot({force:t})),getMacroCalendar:async({dateKey:t})=>(st(),$().listCalendar({dateKey:t})),getMacroNews:async({dateKey:t})=>(st(),$().listNews({dateKey:t})),emitLog:L,getWorkingDir:()=>y.getPath("userData")}),ot={"@ai.weget.jp/skill-gmo-fx":{getConfig:()=>H(K().getConfig()),saveConfig:async t=>{const e=K(),i=await e.saveConfig({...e.getConfig(),...H(t)});return"connected"===M?.getStatus()&&tt(),H(i)}},"@ai.weget.jp/skill-gmo-coin":{getConfig:()=>Q(R().getConfig()),saveConfig:async t=>{const e=R(),i=await e.saveConfig({...e.getConfig(),...Q(t)});return"connected"===M?.getStatus()&&tt(),Q(i)}}},nt=t=>{const e=String(t||"").trim(),i=ot[e];if(!i)throw new Error(`skill config is not managed by bot host: ${t}`);return i},lt=async()=>{await G.ensureInstallRoot(),await V();const t=await async function(){try{const t=await e.readFile(C(),"utf8"),i=JSON.parse(t);return(Array.isArray(i?.items)?i.items:[]).filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:T(t.config_json),created_at:t.created_at?String(t.created_at):void 0,updated_at:t.updated_at?String(t.updated_at):void 0}))}catch(t){if("ENOENT"===t?.code)return[];throw t}}();t.length>0&&(U.applySnapshots(t),L("[skills] restored local snapshot",{count:t.length}),await Z(t)),tt()};M=new o({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:v,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(t,e)=>L(t,e),onStatus:t=>{I.emitBotStatus({status:t}),tt()},onLineMessage:async t=>{const e=String(t.line_user_id||""),i=String(t.text||"").trim();if(B({type:"line_user",lineUserId:e,text:i}),!i)return{answer:""};try{const t=await X.runPrompt({prompt:i,contextLabel:"line_message"});return B({type:"line_assistant",lineUserId:e,text:t}),{answer:t}}catch(t){const i=t instanceof Error?t.message:String(t);L("[ai] line reply failed",{error:i});const a=`AI error: ${i}`;return B({type:"line_assistant",lineUserId:e,text:a}),{answer:a}}},onRunTask:async t=>{const e=String(t.task_id||"").trim(),i=String(t.prompt||"").trim();if(e){if(E.set(e,{cancelled:!1}),M?.sendTaskEvent(e,"task_received",{prompt:i,conversation_id:String(t.conversation_id||""),continue_from_task_id:String(t.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return M?.sendTaskEvent(e,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void E.delete(e);try{M?.sendTaskEvent(e,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const t=await X.runPrompt({prompt:i,contextLabel:`task:${e}`}),a=E.get(e);if(a?.cancelled)return M?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void E.delete(e);M?.sendTaskEvent(e,"ai_output",{summary:t,ts:(new Date).toISOString()}),M?.sendTaskEvent(e,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),M?.sendTaskEvent(e,"task_completed",{summary:t.slice(0,500),ts:(new Date).toISOString()})}catch(t){M?.sendTaskEvent(e,"task_failed",{error:t instanceof Error?t.message:String(t),ts:(new Date).toISOString()})}finally{E.delete(e)}}else L("[task] run_task missing task_id")},onTaskControl:async t=>{const e=String(t.task_id||"").trim(),i=String(t.action||"").trim();if(e&&i&&"cancel_task"===i){const t=E.get(e);if(t)return t.cancelled=!0,void L("[task] cancel requested",{task_id:e});M?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()})}},onServerConfigUpdate:async t=>{const e=Array.isArray(t.scopes)?t.scopes.map(t=>String(t||"").trim()):[],i=e.length>0?e:["runtime","skills"];i.includes("runtime")&&await et(),i.includes("skills")&&await it()}}),n({ipcMain:w,env:{capabilities:v,loginApiUrl:r.loginApiUrl},signInWithLoginApi:N.signInWithLoginApi,saveLoginProfile:async({botId:t,email:i,password:a,remember:s})=>{if(s)await e.writeFile(_(),JSON.stringify({botId:t,email:i,password:a,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await e.unlink(_())}catch(t){if("ENOENT"!==t?.code)throw t}},readSavedLoginProfile:async()=>{try{const t=await e.readFile(_(),"utf8"),i=JSON.parse(t);return{botId:String(i?.botId||""),email:String(i?.email||""),password:String(i?.password||""),remember:Boolean(i?.remember)}}catch(t){if("ENOENT"===t?.code)return{botId:"",email:"",password:"",remember:!1};throw t}},getSkillConfig:t=>({config:nt(t).getConfig()}),saveSkillConfig:async(t,e)=>({config:await nt(t).saveConfig(e)}),getAiConfig:()=>({aiModel:String(P.aiModel||"gpt-5.4"),logOutputDir:String(P.logOutputDir||"")}),saveAiConfig:async t=>{const e=t&&"object"==typeof t?t:{},i=await Y({...P,...e}),a=W(),s=J(),r=a?await a.saveConfig({...a.getConfig(),...e}):null;return s&&await s.saveConfig({...s.getConfig(),...e}),{aiModel:String(i.aiModel||r?.aiModel||e.aiModel||"gpt-5.4"),logOutputDir:String(i.logOutputDir||"")}},setCurrentSession:t=>{A=t||null;const e=A?"connected":"disconnected";I.emitBotStatus({status:e})},getCurrentSession:()=>A,getActiveSkills:()=>U.getActiveSkills().map(t=>({name:t.name,displayName:t.displayName,version:t.version})),getCodexAuthStatus:()=>X.getAuthStatus(),getCodexMcpStatus:t=>X.getMcpServerStatus(t),getPlaywrightBrowserStatus:()=>X.getPlaywrightBrowserStatus(),startCodexLogin:()=>X.startLogin(),installPlaywrightMcp:()=>X.installWegetGatewayMcp(),getGatewayContextFilePath:()=>X.getGatewayContextFilePath(),runGatewaySelfTest:t=>X.runGatewaySelfTest(t),getManagedSkills:()=>U.getManagedSkills().map(t=>({...t,ui:G.getLoadedSkill(t.name)?.ui||void 0})),getMacroSnapshot:async({force:t}={})=>(st(),$().getSnapshot({force:t})),getMacroCalendar:async({dateKey:t})=>(st(),$().listCalendar({dateKey:t})),getMacroNews:async({dateKey:t})=>(st(),$().listNews({dateKey:t})),emitLog:L,openGmoKlineWindow:async({symbol:t,interval:e,market:i})=>(()=>{const a="fx"===i?"fx":"coin";return at(a),("fx"===a?K().marketData:R().marketData).fetchKline({symbol:t,interval:e,market:a})})(),getGmoMarketQuotes:async({market:t,symbols:e})=>(()=>{const i="fx"===t?"fx":"coin";return at(i),("fx"===i?K().marketData:R().marketData).fetchQuotes({market:i,symbols:e})})(),getGmoSymbolRules:async({market:t})=>(()=>{const e="fx"===t?"fx":"coin";return at(e),("fx"===e?K().marketData:R().marketData).fetchSymbolRules({market:e})})(),getGmoBuyingPower:async({market:t})=>({market:t,availableJpy:await(at(t),("fx"===t?K().gateway:R().gateway).getBuyingPower(t))}),getGmoAccountMetrics:async({market:t})=>(at(t),("fx"===t?K().gateway:R().gateway).getAccountMetrics(t)),getGmoPositionSummary:async({market:t,symbol:e})=>({market:t,symbol:e,items:await(at(t),("fx"===t?K().gateway:R().gateway).getPositionSummary({market:t,symbol:e}))}),getGmoOpenPositions:async({market:t,symbol:e,page:i,prevId:a,count:s})=>({market:t,symbol:e||"",items:await(at(t),("fx"===t?K().gateway:R().gateway).getOpenPositions({market:"fx"===t?"fx":"crypto",symbol:e||void 0,page:i,prevId:a,count:s}))}),placeGmoFxOrder:async({symbol:t,side:e,size:i})=>{at("fx");return await q(K(),"placeFxOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:t,side:e,size:i})=>{at("coin");return await q(R(),"placeCoinOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{at("fx");return await q(K(),"placeFxCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{at("coin");return await q(R(),"placeCoinCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},getGmoApiState:t=>{const e="fx"===t?W():J();return e?.status.getApiState()||"unknown"},testGmoApi:t=>(at(t),"fx"===t?K().status.testApi():R().status.testApi()),buildTradeStatusSnapshot:t=>(at(t),"fx"===t?K().status.buildSnapshot():R().status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:t=>{M?.start(t),et().catch(t=>{L("[config] runtime sync failed after login",{error:t instanceof Error?t.message:String(t)})}),it().catch(t=>{L("[config] skill sync failed after login",{error:t instanceof Error?t.message:String(t)})}),tt()},stopWsClient:()=>{W()?.stopExecutionStreams(),J()?.stopExecutionStreams(),z()?.stop?.(),M?.stop()},handleBotChatMessage:async({text:t})=>{B({type:"user",text:String(t||"")}),B({type:"assistant_status",status:"Preparing request"});const e=await X.runPrompt({prompt:t,contextLabel:"desktop_chat",onStatus:t=>B({type:"assistant_status",status:t})});return B({type:"assistant",text:e}),{answer:e}},writeErrorLog:async({source:t,message:e,details:i})=>{await(async({source:t,message:e,details:i})=>{await j({level:"error",source:t,message:e,details:i})})({source:t,message:e,details:i})},writeRuntimeLog:async({level:t,source:e,message:i,details:a})=>{await j({level:t,source:e,message:i,details:a})}});const gt=t.join(b,"renderer","logo.png");y.whenReady().then(async()=>{(()=>{if("darwin"!==process.platform)return;if(!i(gt))return;const t=k.createFromPath(gt);t.isEmpty()||y.dock.setIcon(t)})(),await(async()=>{try{const t=await e.readFile(x(),"utf8"),i=JSON.parse(t);P={aiModel:String(i?.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i?.logOutputDir||"").trim()}}catch(t){if("ENOENT"!==t?.code)throw t;P={aiModel:"gpt-5.4",logOutputDir:""}}return P})();try{await rt.ensureStarted()}catch(t){L("[gateway-file-bridge] startup failed",{error:t instanceof Error?t.message:String(t)})}try{await lt()}catch(t){L("[skills] local initialization failed",{error:t instanceof Error?t.message:String(t)})}I.createMainWindow(),y.on("activate",()=>{0===S.getAllWindows().length&&I.createMainWindow()})}),y.on("window-all-closed",()=>{W()?.stopExecutionStreams(),J()?.stopExecutionStreams(),z()?.stop?.(),rt.stop().catch(()=>{}),M?.stop(),"darwin"!==process.platform&&y.quit()});
@@ -4,15 +4,21 @@ contextBridge.exposeInMainWorld('botApi', {
4
4
  login: (email, password, botId, remember) => ipcRenderer.invoke('auth:login', { email, password, botId, remember }),
5
5
  logout: () => ipcRenderer.invoke('auth:logout'),
6
6
  getSavedProfile: () => ipcRenderer.invoke('auth:getSavedProfile'),
7
- getFxApiConfig: () => ipcRenderer.invoke('trade:getFxApiConfig'),
8
- saveFxApiConfig: (config) => ipcRenderer.invoke('trade:saveFxApiConfig', config),
9
- getCoinApiConfig: () => ipcRenderer.invoke('trade:getCoinApiConfig'),
10
- saveCoinApiConfig: (config) => ipcRenderer.invoke('trade:saveCoinApiConfig', config),
7
+ getSkillConfig: (skillName) => ipcRenderer.invoke('skill:getConfig', skillName),
8
+ saveSkillConfig: (skillName, config) => ipcRenderer.invoke('skill:saveConfig', { skillName, config }),
11
9
  getAiConfig: () => ipcRenderer.invoke('ai:getConfig'),
12
10
  saveAiConfig: (config) => ipcRenderer.invoke('ai:saveConfig', config),
13
11
  getRuntimeInfo: () => ipcRenderer.invoke('bot:getRuntimeInfo'),
14
12
  getSkills: () => ipcRenderer.invoke('bot:getSkills'),
15
13
  startCodexLogin: () => ipcRenderer.invoke('codex:startLogin'),
14
+ getPlaywrightMcpStatus: () => ipcRenderer.invoke('browser:getPlaywrightMcpStatus'),
15
+ getPlaywrightBrowserStatus: () => ipcRenderer.invoke('browser:getPlaywrightBrowserStatus'),
16
+ installPlaywrightMcp: () => ipcRenderer.invoke('browser:installPlaywrightMcp'),
17
+ getGatewayStatus: () => ipcRenderer.invoke('gateway:getStatus'),
18
+ runGatewaySelfTest: ({ target } = {}) => ipcRenderer.invoke('gateway:runSelfTest', { target }),
19
+ getMacroSnapshot: ({ force } = {}) => ipcRenderer.invoke('macro:getSnapshot', { force }),
20
+ getMacroCalendar: ({ dateKey }) => ipcRenderer.invoke('macro:getCalendar', { dateKey }),
21
+ getMacroNews: ({ dateKey }) => ipcRenderer.invoke('macro:getNews', { dateKey }),
16
22
  openGmoKlineWindow: ({ symbol, interval, market }) =>
17
23
  ipcRenderer.invoke('trade:openGmoKlineWindow', { symbol, interval, market }),
18
24
  getMarketQuotes: ({ market, symbols }) => ipcRenderer.invoke('trade:getMarketQuotes', { market, symbols }),
@@ -1 +1 @@
1
- const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("user-notice"),r=document.getElementById("bot-id"),a=document.getElementById("email"),s=document.getElementById("password"),c=document.getElementById("remember-me"),l=document.getElementById("status"),d=document.getElementById("session"),m=document.getElementById("runtime-info"),u=document.getElementById("skill-state-list"),g=document.getElementById("tab-btn-skill-gmo-coin"),f=document.getElementById("tab-btn-skill-gmo-fx"),p=document.getElementById("tab-btn-skill-browser"),y=document.getElementById("coin-skill-title"),b=document.getElementById("fx-skill-title"),w=document.getElementById("browser-skill-title"),x=document.getElementById("coin-skill-package"),k=document.getElementById("fx-skill-package"),h=document.getElementById("browser-skill-package"),S=document.getElementById("coin-skill-enabled"),v=document.getElementById("fx-skill-enabled"),E=document.getElementById("browser-skill-enabled"),C=document.getElementById("coin-skill-version"),I=document.getElementById("fx-skill-version"),A=document.getElementById("browser-skill-version"),B=document.getElementById("browser-skill-tools"),L=document.getElementById("logs"),N=document.getElementById("messages"),$=document.getElementById("chat-form"),U=document.getElementById("chat-input"),M=document.getElementById("send-btn"),P=document.getElementById("error-log-dir"),T=document.getElementById("risk-daily-loss-limit-jpy"),F=document.getElementById("crypto-api-key"),D=document.getElementById("crypto-api-secret"),z=document.getElementById("fx-api-key"),O=document.getElementById("fx-api-secret"),j=document.getElementById("toggle-crypto-secret-btn"),q=document.getElementById("toggle-fx-secret-btn"),J=document.getElementById("save-api-config-btn"),Y=document.getElementById("ai-model"),R=document.getElementById("save-ai-config-btn"),H=document.getElementById("codex-login-btn"),_=document.getElementById("codex-copy-login-btn"),K=document.getElementById("codex-refresh-auth-btn"),W=document.getElementById("codex-login-command"),G=document.getElementById("logout-btn"),V=document.getElementById("login-btn"),Q=document.getElementById("coin-symbol-select"),X=document.getElementById("fx-symbol-select"),Z=document.getElementById("coin-kline-intervals"),ee=document.getElementById("fx-kline-intervals"),te=document.getElementById("coin-kline-canvas"),ne=document.getElementById("fx-kline-canvas"),ie=document.getElementById("coin-market-icon"),oe=document.getElementById("fx-market-icon"),re=document.getElementById("coin-order-bid"),ae=document.getElementById("coin-order-ask"),se=document.getElementById("coin-order-spread"),ce=document.getElementById("fx-order-bid"),le=document.getElementById("fx-order-ask"),de=document.getElementById("fx-order-spread"),me=document.getElementById("coin-order-qty"),ue=document.getElementById("fx-order-qty"),ge=document.getElementById("coin-buy-btn"),fe=document.getElementById("coin-sell-btn"),pe=document.getElementById("fx-buy-btn"),ye=document.getElementById("fx-sell-btn"),be=document.getElementById("coin-required-amount"),we=document.getElementById("fx-required-amount"),xe=document.getElementById("coin-account-info-refresh-btn"),ke=document.getElementById("fx-account-info-refresh-btn"),he=document.getElementById("coin-account-pnl"),Se=document.getElementById("coin-account-margin"),ve=document.getElementById("coin-account-available"),Ee=document.getElementById("coin-account-margin-ratio"),Ce=document.getElementById("fx-account-pnl"),Ie=document.getElementById("fx-account-margin"),Ae=document.getElementById("fx-account-available"),Be=document.getElementById("fx-account-margin-ratio"),Le=document.getElementById("coin-position-summary-body"),Ne=document.getElementById("coin-position-list-body"),$e=document.getElementById("fx-position-summary-body"),Ue=document.getElementById("fx-position-list-body"),Me=document.getElementById("coin-position-summary-refresh-btn"),Pe=document.getElementById("coin-position-list-refresh-btn"),Te=document.getElementById("fx-position-summary-refresh-btn"),Fe=document.getElementById("fx-position-list-refresh-btn"),De=Array.from(document.querySelectorAll(".tab-btn")),ze=Array.from(document.querySelectorAll(".tab-panel"));let Oe="15m",je="15m",qe=String(Q?.value||"BTC_JPY").trim().toUpperCase(),Je=String(X?.value||"USD_JPY").trim().toUpperCase(),Ye=null,Re=null,He=null,_e=null,Ke=!1,We=!1,Ge=!1,Ve=!1,Qe=!1,Xe="",Ze="",et=0,tt=0,nt=null,it=null;const ot=new Map,rt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},at={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let st=[],ct=[];const lt=e=>e&&"object"==typeof e?e:{},dt=e=>{for(const t of De)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of ze)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of De)e.addEventListener("click",()=>dt(e.dataset.tab||"coin"));const mt=e=>{const n=Ke!==e;Ke=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&Wt(),e?n&&on():(Mt(),We=!1)},ut=e=>{const t=String(e||"disconnected").trim().toLowerCase();l.textContent=t,l.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?l.classList.add("status-disconnected"):l.classList.add("status-connecting"):l.classList.add("status-connected")},gt=(e,t=6)=>{if(null===e||!Number.isFinite(e))return"-";const n=Math.abs(e);return n>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:3}):n>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:Math.min(t,5)}):e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:t})},ft=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,pt=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,yt=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),bt=e=>{const t=String(e||"").trim().replace(/,/g,"");if(!t)return"";if(!/^\d+(\.\d+)?$/.test(t))return"";if(!t.includes("."))return t;return t.replace(/(\.\d*?[1-9])0+$/,"$1").replace(/\.0+$/,"").replace(/\.$/,"")},wt=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},xt=(e,t)=>{const n=String(e||"").trim(),i=n.startsWith("-"),o=i?n.slice(1):n,[r,a=""]=o.split("."),s=(a+"0".repeat(t)).slice(0,t),c=BigInt((r||"0")+s);return i?-c:c},kt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=gt(t.bid),a=gt(t.ask);n.textContent=r,i.textContent=a,o.textContent=gt(t.spread,8)},ht=e=>{if("coin"===e){const e=Number(String(me.value||"").trim()),t=nt;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(be.textContent="- 円"):void(be.textContent=ft(e*t/2))}const t=Number(String(ue.value||"").trim()),n=it;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?we.textContent="- 円":we.textContent=ft(t*n/20)},St=async(e,t)=>{if(!window.botApi?.getMarketQuotes)return null;const n=String(t||"").trim().toUpperCase(),i=await window.botApi.getMarketQuotes({market:e,symbols:[n]});if(!i?.ok)throw new Error(String(i?.error||"quote api failed"));const o=(i.quotes||[]).find(e=>String(e.symbol||"").toUpperCase()===n)||null;return"coin"===e?(kt({symbol:n,quote:o,orderBidNode:re,orderAskNode:ae,orderSpreadNode:se}),nt=o?.ask??null,ht("coin")):(kt({symbol:n,quote:o,orderBidNode:ce,orderAskNode:le,orderSpreadNode:de}),it=o?.ask??null,ht("fx")),o},vt=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(ie.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;oe.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},Et=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,Ct=e=>"coin"===e?rt:at,It=e=>"coin"===e?te:ne,At=e=>"coin"===e?qe:Je,Bt=e=>{const t=At(e),n=("coin"===e?st:ct).filter(e=>String(e.symbol||"").trim().toUpperCase()===t),i=[];for(const e of n){const t=String(e.side||"").trim().toUpperCase(),n=Number(e.averagePositionRate||0);if(!Number.isFinite(n)||n<=0)continue;const o="BUY"===t;i.push({price:n,label:o?"BUY Avg":"SELL Avg",color:o?"#1f9d55":"#e03131"})}return i},Lt=e=>{const t=Ct(e);t.candles.length&&Vt(It(e),t.candles,Bt(e))},Nt=(e,t)=>{if(!t)return;const n=Ct(e);if(!n.candles.length)return;if(n.symbol!==t.symbol)return;const i=n.candles[n.candles.length-1],o=null!==t.bid&&null!==t.ask?(t.bid+t.ask)/2:null!==t.ask?t.ask:t.bid;null!==o&&Number.isFinite(o)&&(i.close=o,i.high=Math.max(i.high,o),i.low=Math.min(i.low,o),Vt(It(e),n.candles,Bt(e)))},$t=async e=>{const t=Ct(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==At(e))return;if(!(Math.floor(Date.now()/Et(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await en():await tn()}finally{t.fetching=!1}}},Ut=async e=>{if("coin"!==e){if(!Qe){Qe=!0;try{const e=await St("fx",Je);Nt("fx",e),await $t("fx")}catch(e){const t=Yt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Ze||n-tt>15e3)&&(qt("[fx] quote poll failed",{error:t}),Ze=t,tt=n)}finally{Qe=!1}}}else{if(Ve)return;Ve=!0;try{const e=await St("coin",qe);Nt("coin",e),await $t("coin")}catch(e){const t=Yt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Xe||n-et>15e3)&&(qt("[coin] quote poll failed",{error:t}),Xe=t,et=n)}finally{Ve=!1}}},Mt=()=>{He&&(clearInterval(He),He=null),_e&&(clearInterval(_e),_e=null)},Pt=async e=>{if(window.botApi?.getAccountMetrics){if(!e||"coin"===e){const e=await window.botApi.getAccountMetrics({market:"coin"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);he.textContent=n>0?pt(i):ft(0),Se.textContent=ft(n),ve.textContent=ft(t),Ee.textContent=yt(Number(e.marginRatio||0))}else qt("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if(!e||"fx"===e){const e=await window.botApi.getAccountMetrics({market:"fx"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);Ce.textContent=n>0?pt(i):ft(0),Ie.textContent=ft(n),Ae.textContent=ft(t),Be.textContent=yt(Number(e.marginRatio||0))}else qt("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},Tt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").toUpperCase(),o=n.replace("_","/"),r=String(i.side||"-"),a=String(i.size||"").trim(),s=Number(a||0),c=Number(i.price||0),l=Number(i.lossGain||0),d=Number(i.totalSwap||0),m=String(i.timestamp||"-"),u=Number(i.positionId||0),g=Number.isFinite(u)&&u>0&&a?`data-market="${e}" data-symbol="${n}" data-side="${String(r||"").toUpperCase()}" data-position-id="${u}" data-size="${a}"`:"disabled",f=document.createElement("tr");f.innerHTML=`\n <td><button type="button" class="close-btn" ${g}>決済</button></td>\n <td>${o}<br />${r}</td>\n <td>${s.toLocaleString("ja-JP")}</td>\n <td>${c.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${pt(l)}<br />${pt(d)}</td>\n <td>${m.replace("T"," ").slice(0,19)}</td>\n `,t.appendChild(f)}}else t.innerHTML='<tr><td colspan="6" class="empty-cell">対象のお取引はございません。</td></tr>'},Ft=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").replace("_","/"),o=String(i.side||"-"),r=Number("fx"===e?i.sumPositionSize||0:i.sumPositionQuantity||0),a=Number(i.averagePositionRate||0),s=Number(i.positionLossGain||0),c=Number("fx"===e&&i.sumTotalSwap||0),l=document.createElement("tr");l.innerHTML=`\n <td><button type="button" class="close-btn">決済</button></td>\n <td>${n}<br />${o}</td>\n <td>${r.toLocaleString("ja-JP")}</td>\n <td>${a.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${pt(s)}<br />${pt(c)}</td>\n `,t.appendChild(l)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},Dt=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if(!e||"coin"===e){const e=await window.botApi.getPositionSummary({market:"coin",symbol:qe});e?.ok?(st=Array.isArray(e.items)?e.items:[],Ft("coin",Le,st),Lt("coin")):(qt("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),st=[],Ft("coin",Le,[]),Lt("coin"))}if(!e||"fx"===e){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(ct=Array.isArray(e.items)?e.items:[],Ft("fx",$e,ct),Lt("fx")):(qt("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),ct=[],Ft("fx",$e,[]),Lt("fx"))}if(!e||"coin"===e){const e=await window.botApi.getOpenPositions({market:"coin",symbol:qe,page:1,count:100});e?.ok?Tt("coin",Ne,Array.isArray(e.items)?e.items:[]):(qt("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),Tt("coin",Ne,[]))}if(!e||"fx"===e){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?Tt("fx",Ue,Array.isArray(e.items)?e.items:[]):(qt("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),Tt("fx",Ue,[]))}}},zt=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(ue.value||"").trim();i?await Gt(t,async()=>{const t=await n.placeFxOrder({symbol:Je,side:e,size:i});if(!t?.ok){const n=Yt(t?.error||"fx order failed");return qt("[fx] order failed",{symbol:Je,side:e,size:i,error:n}),Rt("error",n),void await Ht("fx.order",n,t)}qt("[fx] order placed",{symbol:Je,side:e,size:i,result:t.result||null}),Rt("success",`${e} 注文を送信しました。`),await Pt("fx"),await Dt("fx")}):Rt("error","数量を入力してください。")},Ot=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=bt(String(me.value||""));if(!i)return void Rt("error","数量の形式が不正です。");const o=((e,t)=>{const n=ot.get(e);if(!n)return{ok:!0};const i=bt(n.minOrderSize),o=bt(n.sizeStep),r=bt(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(wt(t),wt(i),wt(o),wt(r)),s=xt(t,a),c=xt(i,a),l=xt(o,a);if(s<c)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(l>0n&&s%l!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>xt(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(qe,i);o.ok?await Gt(t,async()=>{const t=await n.placeCoinOrder({symbol:qe,side:e,size:i});if(!t?.ok){const n=Yt(t?.error||"coin order failed");return qt("[coin] order failed",{symbol:qe,side:e,size:i,error:n}),Rt("error",n),void await Ht("coin.order",n,t)}qt("[coin] order placed",{symbol:qe,side:e,size:i,result:t.result||null}),Rt("success",`${e} 注文を送信しました。`),await Pt("coin"),await Dt("coin")}):Rt("error",o.reason||"数量が取引ルールに一致しません。")},jt=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},qt=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:jt(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!L)return;const i=document.createElement("div");i.className="log-entry";const o=document.createElement("div");o.className="log-line";const r=document.createElement("span");if(r.className="log-ts",r.textContent=n||(new Date).toISOString(),o.appendChild(r),o.append(document.createTextNode(e||"")),i.appendChild(o),null!=t&&""!==t){const e=document.createElement("pre");e.className="status-output",e.textContent="string"==typeof t?t:JSON.stringify(t,null,2),i.appendChild(e)}for(L.appendChild(i);L.children.length>300;)L.removeChild(L.firstChild);L.scrollTop=L.scrollHeight},Jt=(e,t,n="")=>{const i=document.createElement("div");i.className=`msg ${e}`;let o=n||("user"===e?"You":"Assistant");"line-user"===e&&(o=n||"LINE User"),"line-assistant"===e&&(o=n||"AI"),i.textContent=`${o}: ${t}`,N.appendChild(i),N.scrollTop=N.scrollHeight},Yt=e=>{const t=String(e||"").trim(),n=t.toLowerCase();return t?n.includes("api key")&&n.includes("missing")?"API Key 未设置,请先在 Config 页面保存。":n.includes("auth")||n.includes("401")||n.includes("403")?"认证失败,请检查 API Key 和 Secret。":n.includes("network")||n.includes("fetch failed")||n.includes("timeout")?"网络连接异常,请检查网络后重试。":n.includes("message is required")?"请输入内容后再发送。":t:"处理失败,请稍后重试。"},Rt=(e,t)=>{Ye&&(clearTimeout(Ye),Ye=null),o.classList.remove("hidden","notice-error","notice-success"),o.classList.add("error"===e?"notice-error":"notice-success"),o.textContent=t,Ye=setTimeout(()=>{_t()},"success"===e?2200:4200)},Ht=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},_t=()=>{Ye&&(clearTimeout(Ye),Ye=null),o.classList.add("hidden"),o.classList.remove("notice-error","notice-success"),o.textContent=""},Kt=(e,t)=>{Re&&(clearTimeout(Re),Re=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,Re=setTimeout(()=>{Wt()},"success"===e?2200:4200)},Wt=()=>{Re&&(clearTimeout(Re),Re=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},Gt=async(e,t)=>{if(e.disabled)return;e.disabled=!0,e.classList.add("is-loading");const n=e.textContent||"";try{await t()}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[ui] action failed",{error:t}),Rt("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},Vt=(e,t,n=[])=>{const i=e.getContext("2d");if(!i)return;const o=e.width,r=e.height;if(i.clearRect(0,0,o,r),!t.length)return;const a=Math.max(...t.map(e=>e.high)),s=Math.min(...t.map(e=>e.low)),c=n.map(e=>e.price).filter(e=>Number.isFinite(e)),l=c.length?Math.max(a,...c):a,d=c.length?Math.min(s,...c):s,m=Math.max(1e-8,l-d),u=10,g=10,f=o-10-62-8,p=r-10-24-8,y=Math.max(2,f/t.length),b=e=>g+(l-e)/m*p,w=e=>e>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:0}):e>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:3}):e.toLocaleString("en-US",{minimumFractionDigits:5,maximumFractionDigits:5});i.strokeStyle="#c9d4ea",i.strokeRect(.5,.5,o-1,r-1),i.strokeStyle="#e3eaf5",i.lineWidth=1;for(let e=0;e<=4;e+=1){const t=g+p/4*e;i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke()}t.forEach((e,t)=>{const n=u+t*y+.5*y,o=b(e.high),r=b(e.low),a=b(e.open),s=b(e.close),c=e.close>=e.open;i.strokeStyle=c?"#1f9d55":"#d64545",i.fillStyle=c?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,r),i.stroke();const l=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(l,m,d,g)});for(const e of n){const t=b(e.price);i.save(),i.setLineDash([4,4]),i.strokeStyle=e.color,i.lineWidth=1,i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke(),i.restore(),i.font="12px Segoe UI";const n=`${e.label} ${w(e.price)}`,o=Math.ceil(i.measureText(n).width)+10,r=14,a=Math.max(12,Math.min(g+p-18,t-9));i.fillStyle=e.color,i.fillRect(r,a,o,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(n,r+5,a+9)}const x=t[t.length-1],k=x?.close;if(Number.isFinite(k)){const e=b(Number(k));i.save(),i.setLineDash([5,4]),i.strokeStyle="#2b79ff",i.lineWidth=1,i.beginPath(),i.moveTo(u,e),i.lineTo(u+f,e),i.stroke(),i.restore();const t=w(Number(k));i.font="12px Segoe UI";const n=Math.ceil(i.measureText(t).width)+10,o=u+f+6,r=Math.max(12,Math.min(g+p-18,e-9));i.fillStyle="#2b79ff",i.fillRect(o,r,n,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(t,o+5,r+9)}i.fillStyle="#66758d",i.font="12px Segoe UI",i.textBaseline="middle",i.textAlign="left";for(let e=0;e<=4;e+=1){const t=l-m/4*e,n=g+p/4*e;i.fillText(w(t),u+f+8,n)}const h=e=>{const t=new Date(e>1e12?e:1e3*e);if(Number.isNaN(t.getTime()))return"-";const n=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0");return`${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${n}:${i}`};i.textAlign="center",i.textBaseline="top";for(let e=0;e<=4;e+=1){const n=Math.min(t.length-1,Math.floor((t.length-1)*(e/4))),o=u+f*e/4;i.fillText(h(t[n]?.time||0),o,g+p+6)}},Qt=(e,t)=>{const{tabBtn:n,titleNode:i,packageNode:o,enabledNode:r,versionNode:a,fallbackTitle:s,fallbackPackage:c}=t,l=e?.displayName||s,d=e?.name||c,m=!e||Boolean(e.enabled),u=String(e?.installStatus||"builtin").trim()||"builtin",g=String(e?.configuredVersion||e?.version||"builtin").trim()||"builtin";n&&(n.textContent=d.replace("@ai.weget.jp/",""),n.classList.toggle("is-disabled",!m)),i&&(i.textContent=l),o&&(o.textContent=d),r&&(r.textContent=m?"enabled":"disabled",r.classList.toggle("is-disabled",!m)),a&&(a.textContent=`${u} · ${g}`)},Xt=async()=>{if(!window.botApi?.getRuntimeInfo)return;const[e,t]=await Promise.all([window.botApi.getRuntimeInfo(),window.botApi.getSkills?window.botApi.getSkills():(async()=>({ok:!1,skills:[]}))()]);if(!e?.ok)return;ut(e.status||"disconnected"),e.session?(mt(!0),d.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(mt(!1),d.textContent="local mode (not logged in)"),(e=>{m.innerHTML="";const t=lt(e),n=lt(t.runtime),i=t.session?lt(t.session):null,o=[["Bot ID",String(i?.botId||"-")],["User ID",String(i?.userId||"-")],["Email",String(i?.email||"-")],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["Active Skills",Array.isArray(n.activeSkills)&&n.activeSkills.map(e=>{const t=lt(e);return String(t.displayName||t.name||"").trim()}).filter(Boolean).join(", ")||"-"],["GMO FX API State",String(n.gmoFxApiState||"unknown")],["GMO Coin API State",String(n.gmoCoinApiState||"unknown")],["Codex Auth",String(n.codexAuthStatus||"unknown")],["Codex Auth Detail",String(n.codexAuthDetail||"-")],["Default Model",String(n.aiModel||"-")],["Login API",String(n.loginApiUrl||"-")]];for(const[e,t]of o){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const i=document.createElement("div");i.className="runtime-value",i.textContent=t,m.appendChild(n),m.appendChild(i)}})(e);const n=t?.ok&&t.skills||[];(e=>{if(!u)return;u.innerHTML="";const t=Array.isArray(e)?e:[];if(0===t.length){const e=document.createElement("div");return e.className="skill-empty",e.textContent="No managed skills are available in this bot host.",void u.appendChild(e)}for(const e of t){const t=document.createElement("section");t.className="skill-card"+(e.enabled?"":" is-disabled");const n=document.createElement("div");n.className="skill-card-head";const i=document.createElement("div");i.className="skill-card-title";const o=document.createElement("strong");o.textContent=e.displayName||e.name;const r=document.createElement("code");if(r.textContent=e.name,i.appendChild(o),i.appendChild(r),e.description){const t=document.createElement("div");t.className="muted",t.textContent=e.description,i.appendChild(t)}const a=document.createElement("div");a.className="skill-badges";const s=document.createElement("span");s.className="skill-badge "+(e.enabled?"enabled":"disabled"),s.textContent=e.enabled?"enabled":"disabled",a.appendChild(s);const c=document.createElement("span");c.className="skill-badge status",c.textContent=e.installStatus||"unknown",a.appendChild(c),n.appendChild(i),n.appendChild(a),t.appendChild(n);const l=e=>{const t=document.createElement("div");t.className="skill-card-section";const n=document.createElement("div");return n.className="skill-section-label",n.textContent=e,t.appendChild(n),t},d=l("Package"),m=document.createElement("code");m.className="skill-package-code",m.textContent=e.name,d.appendChild(m),t.appendChild(d);const g=l("Tools"),f=document.createElement("div");f.className="skill-tags";const p=Array.isArray(e.tools)?e.tools:[];if(p.length>0)for(const e of p){const t=document.createElement("span");t.className="skill-tag",t.textContent=e,f.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No tools declared",f.appendChild(e)}g.appendChild(f),t.appendChild(g);const y=l("Permissions");if(Array.isArray(e.permissions)&&e.permissions.length>0){const t=document.createElement("div");t.className="skill-tags";for(const n of e.permissions){const e=document.createElement("span");e.className="skill-tag permissions",e.textContent=n,t.appendChild(e)}y.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No permissions declared",y.appendChild(e)}t.appendChild(y);const b=l("State"),w=document.createElement("div");w.className="skill-meta";const x=[["Bundled Version",e.version||"-"],["Configured Version",e.configuredVersion||"-"],["UI",e.hasUi?"has ui":"no ui"],["Config Keys",String(Object.keys(e.configJson||{}).length)]];for(const[e,t]of x){const n=document.createElement("div");n.className="skill-meta-row";const i=document.createElement("span");i.textContent=e;const o=document.createElement("strong");o.textContent=t,n.appendChild(i),n.appendChild(o),w.appendChild(n)}b.appendChild(w),t.appendChild(b),u.appendChild(t)}})(n),Qt(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{tabBtn:g,titleNode:y,packageNode:x,enabledNode:S,versionNode:C,fallbackTitle:"GMO Coin Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-coin"}),Qt(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{tabBtn:f,titleNode:b,packageNode:k,enabledNode:v,versionNode:I,fallbackTitle:"GMO FX Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-fx"});const i=n.find(e=>"@ai.weget.jp/skill-browser"===e.name);Qt(i,{tabBtn:p,titleNode:w,packageNode:h,enabledNode:E,versionNode:A,fallbackTitle:"Browser Skill",fallbackPackage:"@ai.weget.jp/skill-browser"}),((e,t,n)=>{if(e){if(e.innerHTML="",0===t.length){const t=document.createElement("span");return t.className="muted",t.textContent=n,void e.appendChild(t)}for(const n of t){const t=document.createElement("span");t.className="skill-tag",t.textContent=n,e.appendChild(t)}}})(B,Array.isArray(i?.tools)?i.tools:[],"No browser tools declared.")};J.addEventListener("click",async()=>{await Gt(J,async()=>{if(!window.botApi?.saveFxApiConfig||!window.botApi.saveCoinApiConfig)return;const e={riskDailyLossLimitJpy:Number(T.value||"0"),errorLogDir:String(P.value||"").trim()},t={...e,fxApiKey:String(z.value||"").trim(),fxApiSecret:String(O.value||"").trim()},n={...e,cryptoApiKey:String(F.value||"").trim(),cryptoApiSecret:String(D.value||"").trim()},[i,o]=await Promise.all([window.botApi.saveFxApiConfig(t),window.botApi.saveCoinApiConfig(n)]);return i.ok?o.ok?(qt("[trade-config] saved"),Rt("success","配置已保存到本机。"),void await Xt()):(qt(`[trade-config] coin save failed: ${o.error||"unknown"}`),Rt("error",Yt(o.error)),void await Ht("trade-config.coin.save",Yt(o.error),o)):(qt(`[trade-config] fx save failed: ${i.error||"unknown"}`),Rt("error",Yt(i.error)),void await Ht("trade-config.fx.save",Yt(i.error),i))})}),R.addEventListener("click",async()=>{await Gt(R,async()=>{if(!window.botApi?.saveAiConfig)return;const e={aiModel:String(Y.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini"},t=await window.botApi.saveAiConfig(e);if(!t.ok)return qt(`[ai-config] save failed: ${t.error||"unknown"}`),Rt("error",Yt(t.error)),void await Ht("ai-config.save",Yt(t.error),t);qt("[ai-config] saved"),Rt("success","AI 设置已保存。"),await Xt()})}),H.addEventListener("click",async()=>{await Gt(H,async()=>{if(!window.botApi?.startCodexLogin)return;const e=await window.botApi.startCodexLogin();if(!e.ok)return qt(`[codex] login launch failed: ${e.detail||"unknown"}`),Rt("error",Yt(e.detail)),void await Ht("codex.login.launch",Yt(e.detail),e);qt("[codex] login launched",{detail:e.detail}),Rt("success",e.detail||"Codex login started."),setTimeout(()=>{Xt()},1500)})}),_.addEventListener("click",async()=>{const e=String(W.textContent||"codex login --device-auth").trim();try{if(!await(async e=>{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0;const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","true"),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select();const n=document.execCommand("copy");return document.body.removeChild(t),n})(e))return void Rt("error","无法复制命令,请手动执行。");qt("[codex] login command copied"),Rt("success","Codex 登录命令已复制。")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[codex] copy login command failed",{error:t}),Rt("error",t)}}),K.addEventListener("click",async()=>{await Gt(K,async()=>{await Xt(),qt("[codex] auth status refreshed"),Rt("success","Codex 状态已刷新。")})}),j.addEventListener("click",()=>{const e="password"===D.type?"text":"password";D.type=e,j.textContent="password"===e?"显示":"隐藏"}),q.addEventListener("click",()=>{const e="password"===O.type?"text":"password";O.type=e,q.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;Wt();const t=a.value.trim(),n=r.value.trim(),i=s.value;if(t&&n&&i){V.disabled=!0,V.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,c.checked);if(!e.ok)return qt(`[ui] login failed: ${e.error||"unknown"}`),void Kt("error",Yt(e.error));qt("[ui] login success"),Kt("success","登录成功。"),Rt("success","登录成功。"),await Xt()}finally{V.disabled=!1,V.textContent="Login"}}}),G.addEventListener("click",async()=>{await Gt(G,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return qt(`[ui] logout failed: ${e.error||"unknown"}`),Rt("error",Yt(e.error)),void await Ht("auth.logout",Yt(e.error),e);qt("[ui] logout"),Rt("success","已登出。"),mt(!1),d.textContent="local mode (not logged in)",await Xt()})});const Zt=async({market:e,symbol:t,interval:n,canvas:i})=>{if(!window.botApi)return;const o=await window.botApi.openGmoKlineWindow({symbol:t,interval:n,market:e});if(!o.ok)return qt(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),Rt("error",Yt(o.error)),void await Ht(`${e}.kline`,Yt(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=Ct(e);a.symbol=String(o.symbol||t||"").toUpperCase(),a.interval=n,a.candles=r.map(e=>({time:Number(e.time||0),open:Number(e.open||0),high:Number(e.high||0),low:Number(e.low||0),close:Number(e.close||0)})),a.lastFetchBucket=Math.floor(Date.now()/Et(n)),Vt(i,a.candles,Bt(e)),qt("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),_t()},en=async()=>{await Zt({market:"coin",symbol:qe,interval:Oe,canvas:te})},tn=async()=>{await Zt({market:"fx",symbol:Je,interval:je,canvas:ne})},nn=({container:e,getCurrent:t,setCurrent:n,onChange:i})=>{const o=Array.from(e.querySelectorAll(".interval-btn"));for(const e of o)e.addEventListener("click",async()=>{const r=String(e.dataset.interval||"").trim();if(r&&r!==t()){n(r);for(const t of o)t.classList.toggle("is-active",t===e);await i()}})};Q.addEventListener("change",async()=>{qe=String(Q.value||"BTC_JPY").trim().toUpperCase(),vt("coin",qe);try{await St("coin",qe),await en(),await Dt("coin")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Rt("error",t)}}),X.addEventListener("change",async()=>{Je=String(X.value||"USD_JPY").trim().toUpperCase(),vt("fx",Je);try{await St("fx",Je),await tn(),await Dt("fx")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Rt("error",t)}}),nn({container:Z,getCurrent:()=>Oe,setCurrent:e=>{Oe=e},onChange:en}),nn({container:ee,getCurrent:()=>je,setCurrent:e=>{je=e},onChange:tn}),xe.addEventListener("click",async()=>{await Gt(xe,async()=>{await Pt("coin")})}),ke.addEventListener("click",async()=>{await Gt(ke,async()=>{await Pt("fx")})}),me.addEventListener("input",()=>{ht("coin")}),ue.addEventListener("input",()=>{ht("fx")}),Ne.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeCoinPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=bt(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&c?await Gt(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=Yt(e?.error||"coin close order failed");return qt("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),Rt("error",t),void await Ht("coin.closeOrder",t,e)}qt("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),Rt("success","決済注文を送信しました。"),await Pt("coin"),await Dt("coin")}):Rt("error","決済対象データが不正です。")}),Ue.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeFxPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&c?await Gt(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=Yt(e?.error||"fx close order failed");return qt("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),Rt("error",t),void await Ht("fx.closeOrder",t,e)}qt("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),Rt("success","決済注文を送信しました。"),await Pt("fx"),await Dt("fx")}):Rt("error","決済対象データが不正です。")}),ge.addEventListener("click",async()=>{await Ot("BUY",ge)}),fe.addEventListener("click",async()=>{await Ot("SELL",fe)}),pe.addEventListener("click",async()=>{await zt("BUY",pe)}),ye.addEventListener("click",async()=>{await zt("SELL",ye)}),Me.addEventListener("click",async()=>{await Gt(Me,async()=>{await Dt("coin")})}),Pe.addEventListener("click",async()=>{await Gt(Pe,async()=>{await Dt("coin")})}),Te.addEventListener("click",async()=>{await Gt(Te,async()=>{await Dt("fx")})}),Fe.addEventListener("click",async()=>{await Gt(Fe,async()=>{await Dt("fx")})}),$.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(U.value||"").trim();if(t){Jt("user",t),U.value="",M.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return Jt("assistant",`Error: ${e.error||"unknown"}`),Rt("error",Yt(e.error)),void await Ht("ai.chat",Yt(e.error),e);Jt("assistant",String(e.answer||"")),_t()}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Jt("assistant",`Error: ${t}`),Rt("error",t),await Ht("ai.chat.exception",t,e)}finally{M.disabled=!1}}}),window.addEventListener("error",e=>{const t=Yt(e.error?.message||e.message||"Unknown UI error");qt("[ui] uncaught error",{error:t}),Rt("error",t),Ht("ui.error",t,{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason.message:String(e.reason||"Unhandled promise rejection"),n=Yt(t);qt("[ui] unhandled rejection",{error:n}),Rt("error",n),Ht("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{ut(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=lt(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void Jt("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";Jt("line-assistant",String(t.text||""),e)}}),window.botApi.onTradeExecution(e=>{if(!e)return;const t=lt(e),n="coin"===String(t.market||"").trim().toLowerCase()?"coin":"fx",i=String(t.symbol||"").trim().toUpperCase(),o=String(t.side||"").trim().toUpperCase()||"-",r=String(t.settleType||"").trim().toUpperCase()||"OPEN",a=String(t.executionSize??"").trim(),s=String(t.executionPrice??"").trim(),c="CLOSE"===r?"決済":"新規",l=`${i} ${o} ${a}${s?` @ ${s}`:""}`;qt(`[${n}] execution filled`,{symbol:i,side:o,settleType:r,size:a,price:s,raw:t}),Rt("success",`約定成功 (${c}) ${l}`.trim()),Pt(n).catch(()=>{}),Dt(n).catch(()=>{})}));const on=async()=>{if(Ke&&!We&&!Ge){Ge=!0;try{await async function(){vt("coin",qe),vt("fx",Je);try{await St("coin",qe)}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[coin] quote fetch failed",{error:t}),Rt("error",t)}try{await St("fx",Je)}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[fx] quote fetch failed",{error:t}),Rt("error",t)}if(await en(),await tn(),window.botApi?.getSymbolRules)try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){ot.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&ot.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){qt("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}}(),await Pt(),await Dt(),He||(He=setInterval(()=>{Ut("coin")},1e3)),_e||(_e=setInterval(()=>{Ut("fx")},1e3)),We=!0}finally{Ge=!1}}};mt(!1),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(r.value=e.profile.botId||"",a.value=e.profile.email||"",s.value=e.profile.password||"",c.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getFxApiConfig||!window.botApi.getCoinApiConfig||!window.botApi.getAiConfig)return;const[e,t,n]=await Promise.all([window.botApi.getFxApiConfig(),window.botApi.getCoinApiConfig(),window.botApi.getAiConfig()]);e?.ok&&e.config&&(P.value=e.config.errorLogDir||"",T.value=String(e.config.riskDailyLossLimitJpy||5e4),z.value=e.config.fxApiKey||"",O.value=e.config.fxApiSecret||""),t?.ok&&t.config&&(P.value||(P.value=t.config.errorLogDir||""),T.value||(T.value=String(t.config.riskDailyLossLimitJpy||5e4)),F.value=t.config.cryptoApiKey||"",D.value=t.config.cryptoApiSecret||""),n?.ok&&n.config&&(Y.value=n.config.aiModel||"gpt-4.1-mini")})(),Xt();export{};
1
+ const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("sidebar-nav"),a=document.getElementById("sidebar-toggle-btn"),r=document.getElementById("user-notice"),s=document.getElementById("bot-id"),l=document.getElementById("email"),c=document.getElementById("password"),d=document.getElementById("remember-me"),m=document.getElementById("status"),u=document.getElementById("session"),g=document.getElementById("runtime-info"),p=document.getElementById("skill-state-list"),f=document.getElementById("tab-btn-skill-gmo-coin"),y=document.getElementById("tab-btn-skill-gmo-fx"),w=document.getElementById("tab-btn-skill-browser"),b=document.getElementById("tab-btn-skill-macro-economy"),h=document.getElementById("coin-skill-title"),k=document.getElementById("fx-skill-title"),x=document.getElementById("browser-skill-title"),S=document.getElementById("macro-skill-title"),E=document.getElementById("coin-skill-package"),v=document.getElementById("fx-skill-package"),C=document.getElementById("browser-skill-package"),N=document.getElementById("macro-skill-package"),I=document.getElementById("coin-skill-enabled"),A=document.getElementById("fx-skill-enabled"),L=document.getElementById("browser-skill-enabled"),B=document.getElementById("macro-skill-enabled"),$=document.getElementById("coin-skill-version"),M=document.getElementById("fx-skill-version"),T=document.getElementById("browser-skill-version"),P=document.getElementById("macro-skill-version"),U=document.getElementById("browser-skill-cards"),D=document.getElementById("browser-skill-tools"),F=document.getElementById("macro-snapshot-as-of"),j=document.getElementById("macro-fear-greed-score"),_=document.getElementById("macro-snapshot-hint"),z=document.getElementById("macro-coin-cards"),O=document.getElementById("macro-fx-cards"),H=document.getElementById("macro-coin-heatmap-treemap"),J=document.getElementById("macro-fx-heatmap-treemap"),q=document.getElementById("macro-calendar-body"),G=document.getElementById("macro-news-body"),K=document.getElementById("macro-date-key"),Y=document.getElementById("macro-refresh-snapshot-btn"),R=document.getElementById("macro-refresh-day-btn"),W=document.getElementById("browser-chromium-status"),X=document.getElementById("browser-chromium-detail"),V=document.getElementById("browser-prereq-hint"),Q=document.getElementById("browser-refresh-playwright-mcp-btn"),Z=document.getElementById("logs"),ee=document.getElementById("gateway-codex-auth"),te=document.getElementById("gateway-mcp-status"),ne=document.getElementById("gateway-browser-status"),ie=document.getElementById("gateway-context-file"),oe=document.getElementById("gateway-detail"),ae=document.getElementById("gateway-install-btn"),re=document.getElementById("gateway-refresh-btn"),se=document.getElementById("gateway-skill-grid"),le=document.getElementById("messages"),ce=document.getElementById("chat-form"),de=document.getElementById("chat-input"),me=document.getElementById("send-btn"),ue=document.getElementById("fx-risk-daily-loss-limit-jpy"),ge=document.getElementById("coin-risk-daily-loss-limit-jpy"),pe=document.getElementById("crypto-api-key"),fe=document.getElementById("crypto-api-secret"),ye=document.getElementById("fx-api-key"),we=document.getElementById("fx-api-secret"),be=document.getElementById("toggle-crypto-secret-btn"),he=document.getElementById("toggle-fx-secret-btn"),ke=document.getElementById("save-fx-config-btn"),xe=document.getElementById("save-coin-config-btn"),Se=document.getElementById("fx-config-title"),Ee=document.getElementById("coin-config-title"),ve=document.getElementById("fx-config-label-riskDailyLossLimitJpy"),Ce=document.getElementById("fx-config-label-fxApiKey"),Ne=document.getElementById("fx-config-label-fxApiSecret"),Ie=document.getElementById("coin-config-label-riskDailyLossLimitJpy"),Ae=document.getElementById("coin-config-label-cryptoApiKey"),Le=document.getElementById("coin-config-label-cryptoApiSecret"),Be=document.getElementById("ai-model"),$e=document.getElementById("host-log-output-dir"),Me=document.getElementById("save-ai-config-btn"),Te=document.getElementById("codex-login-btn"),Pe=document.getElementById("codex-copy-login-btn"),Ue=document.getElementById("codex-refresh-auth-btn"),De=document.getElementById("codex-login-command"),Fe=document.getElementById("logout-btn"),je=document.getElementById("login-btn"),_e=document.getElementById("coin-symbol-select"),ze=document.getElementById("fx-symbol-select"),Oe=document.getElementById("coin-kline-intervals"),He=document.getElementById("fx-kline-intervals"),Je=document.getElementById("coin-kline-canvas"),qe=document.getElementById("fx-kline-canvas"),Ge=document.getElementById("coin-market-icon"),Ke=document.getElementById("fx-market-icon"),Ye=document.getElementById("coin-order-bid"),Re=document.getElementById("coin-order-ask"),We=document.getElementById("coin-order-spread"),Xe=document.getElementById("fx-order-bid"),Ve=document.getElementById("fx-order-ask"),Qe=document.getElementById("fx-order-spread"),Ze=document.getElementById("coin-order-qty"),et=document.getElementById("fx-order-qty"),tt=document.getElementById("coin-buy-btn"),nt=document.getElementById("coin-sell-btn"),it=document.getElementById("fx-buy-btn"),ot=document.getElementById("fx-sell-btn"),at=document.getElementById("coin-required-amount"),rt=document.getElementById("fx-required-amount"),st=document.getElementById("coin-account-info-refresh-btn"),lt=document.getElementById("fx-account-info-refresh-btn"),ct=document.getElementById("coin-account-pnl"),dt=document.getElementById("coin-account-margin"),mt=document.getElementById("coin-account-available"),ut=document.getElementById("coin-account-margin-ratio"),gt=document.getElementById("fx-account-pnl"),pt=document.getElementById("fx-account-margin"),ft=document.getElementById("fx-account-available"),yt=document.getElementById("fx-account-margin-ratio"),wt=document.getElementById("coin-position-summary-body"),bt=document.getElementById("coin-position-list-body"),ht=document.getElementById("fx-position-summary-body"),kt=document.getElementById("fx-position-list-body"),xt=document.getElementById("coin-position-summary-refresh-btn"),St=document.getElementById("coin-position-list-refresh-btn"),Et=document.getElementById("fx-position-summary-refresh-btn"),vt=document.getElementById("fx-position-list-refresh-btn"),Ct=Array.from(document.querySelectorAll(".tab-btn")),Nt=Array.from(document.querySelectorAll(".tab-panel"));let It="15m",At="15m",Lt=String(_e?.value||"BTC_JPY").trim().toUpperCase(),Bt=String(ze?.value||"USD_JPY").trim().toUpperCase(),$t=null,Mt=null,Tt=null,Pt=null,Ut=!1,Dt=!1,Ft=!1,jt=!1,_t=!1,zt="",Ot="",Ht=0,Jt=0,qt=null,Gt=null,Kt=[],Yt=!1;const Rt=new Map,Wt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},Xt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let Vt=[],Qt=[];const Zt=e=>e&&"object"==typeof e?e:{},en="weget.bot.sidebar.collapsed",tn=e=>{i.classList.toggle("sidebar-collapsed",e),a&&(a.textContent=e?"▶":"◀",a.setAttribute("aria-label",e?"Expand menu":"Collapse menu")),o&&o.setAttribute("data-collapsed",e?"true":"false")},nn=e=>{for(const t of Ct)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of Nt)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of Ct)e.addEventListener("click",()=>nn(e.dataset.tab||"coin"));a?.addEventListener("click",()=>{const e=!i.classList.contains("sidebar-collapsed");tn(e);try{window.localStorage.setItem(en,String(e))}catch{}});const on=e=>{const n=Ut!==e;Ut=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&Zn(),e?n&&ki():(Fn(),Dt=!1)},an=e=>{const t=String(e||"disconnected").trim().toLowerCase();m.textContent=t,m.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?m.classList.add("status-disconnected"):m.classList.add("status-connecting"):m.classList.add("status-connected")},rn=(e,t=6)=>{if(null===e||!Number.isFinite(e))return"-";const n=Math.abs(e);return n>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:3}):n>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:Math.min(t,5)}):e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:t})},sn=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,ln=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,cn=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),dn=e=>{const t=e instanceof Date?e:new Date(e);if(Number.isNaN(t.getTime()))return"";return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`},mn=e=>{const t=String(e||"").trim();if(!t)return"-";const n=new Date(t);return Number.isNaN(n.getTime())?t:n.toLocaleString("ja-JP",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})},un=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"),gn=e=>{const t=String(e||"").trim();if(!t)return{cleaned:"",impact:"",direction:""};const n=t.indexOf("\n"),i=n>=0?t.slice(0,n).trim():t,o=n>=0?t.slice(n+1).trim():"";if(i.startsWith("{")&&i.endsWith("}"))try{const e=JSON.parse(i);return{cleaned:o,impact:String(e.impact||"").trim().toLowerCase(),direction:String(e.usd_jpy_direction||"").trim().toLowerCase()}}catch{}return{cleaned:t,impact:"",direction:""}},pn=e=>{const t=String(e||"").trim().toLowerCase();if(!t)return"-";const n="high"===t?"High":"medium"===t?"Medium":"low"===t?"Low":t;return`<span class="macro-impact-badge ${un(t)}">${un(n)}</span>`},fn=e=>{const t=Math.max(0,Math.min(3,Math.round(Number(e||0))));return t<=0?"-":`<span class="macro-importance-stars">${"★".repeat(t)}</span>`},yn=e=>{const t=String(e||"").trim().toLowerCase();return t?"up"===t?'<span class="macro-direction up">↑↑</span>':"down"===t?'<span class="macro-direction down">↓↓</span>':"sideways"===t?'<span class="macro-direction flat">→</span>':un(t):"-"},wn=e=>{const t=String(e||"").trim().replace(/,/g,"");if(!t)return"";if(!/^\d+(\.\d+)?$/.test(t))return"";if(!t.includes("."))return t;return t.replace(/(\.\d*?[1-9])0+$/,"$1").replace(/\.0+$/,"").replace(/\.$/,"")},bn=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},hn=(e,t)=>{const n=String(e||"").trim(),i=n.startsWith("-"),o=i?n.slice(1):n,[a,r=""]=o.split("."),s=(r+"0".repeat(t)).slice(0,t),l=BigInt((a||"0")+s);return i?-l:l},kn=e=>{const t=En("fx"===e?"@ai.weget.jp/skill-gmo-fx":"@ai.weget.jp/skill-gmo-coin"),n=Zt(t?.configJson);return"fx"===e?Boolean(String(n.fxApiKey||ye.value||"").trim()&&String(n.fxApiSecret||we.value||"").trim()):Boolean(String(n.cryptoApiKey||pe.value||"").trim()&&String(n.cryptoApiSecret||fe.value||"").trim())},xn=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const a=rn(t.bid),r=rn(t.ask);n.textContent=a,i.textContent=r,o.textContent=rn(t.spread,8)},Sn=e=>{if("coin"===e){const e=Number(String(Ze.value||"").trim()),t=qt;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(at.textContent="- 円"):void(at.textContent=sn(e*t/2))}const t=Number(String(et.value||"").trim()),n=Gt;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?rt.textContent="- 円":rt.textContent=sn(t*n/20)},En=e=>Kt.find(t=>t.name===e),vn=e=>{const t=En(e);if(!t)return!1;const n=String(t.installStatus||"").trim().toLowerCase();return t.enabled&&"active"===n},Cn=e=>vn("fx"===e?"@ai.weget.jp/skill-gmo-fx":"@ai.weget.jp/skill-gmo-coin"),Nn=async(e,t)=>{if(!window.botApi?.getMarketQuotes)return null;const n=String(t||"").trim().toUpperCase(),i=await window.botApi.getMarketQuotes({market:e,symbols:[n]});if(!i?.ok)throw new Error(String(i?.error||"quote api failed"));const o=(i.quotes||[]).find(e=>String(e.symbol||"").toUpperCase()===n)||null;return"coin"===e?(xn({symbol:n,quote:o,orderBidNode:Ye,orderAskNode:Re,orderSpreadNode:We}),qt=o?.ask??null,Sn("coin")):(xn({symbol:n,quote:o,orderBidNode:Xe,orderAskNode:Ve,orderSpreadNode:Qe}),Gt=o?.ask??null,Sn("fx")),o},In=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(Ge.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),a="JPY"===o?i:`${i}_${o}`;Ke.src=`https://coin.z.com/jp/member/imgs/fx/icon_${a}.svg`},An=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,Ln=e=>"coin"===e?Wt:Xt,Bn=e=>"coin"===e?Je:qe,$n=e=>"coin"===e?Lt:Bt,Mn=e=>{const t=$n(e),n=("coin"===e?Vt:Qt).filter(e=>String(e.symbol||"").trim().toUpperCase()===t),i=[];for(const e of n){const t=String(e.side||"").trim().toUpperCase(),n=Number(e.averagePositionRate||0);if(!Number.isFinite(n)||n<=0)continue;const o="BUY"===t;i.push({price:n,label:o?"BUY Avg":"SELL Avg",color:o?"#1f9d55":"#e03131"})}return i},Tn=e=>{const t=Ln(e);t.candles.length&&ti(Bn(e),t.candles,Mn(e))},Pn=(e,t)=>{if(!t)return;const n=Ln(e);if(!n.candles.length)return;if(n.symbol!==t.symbol)return;const i=n.candles[n.candles.length-1],o=null!==t.bid&&null!==t.ask?(t.bid+t.ask)/2:null!==t.ask?t.ask:t.bid;null!==o&&Number.isFinite(o)&&(i.close=o,i.high=Math.max(i.high,o),i.low=Math.min(i.low,o),ti(Bn(e),n.candles,Mn(e)))},Un=async e=>{const t=Ln(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==$n(e))return;if(!(Math.floor(Date.now()/An(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await wi():await bi()}finally{t.fetching=!1}}},Dn=async e=>{if(Cn(e))if("coin"!==e){if(!_t){_t=!0;try{const e=await Nn("fx",Bt);Pn("fx",e),await Un("fx")}catch(e){const t=Rn(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Ot||n-Jt>15e3)&&(Gn("[fx] quote poll failed",{error:t}),Ot=t,Jt=n)}finally{_t=!1}}}else{if(jt)return;jt=!0;try{const e=await Nn("coin",Lt);Pn("coin",e),await Un("coin")}catch(e){const t=Rn(e instanceof Error?e.message:String(e)),n=Date.now();(t!==zt||n-Ht>15e3)&&(Gn("[coin] quote poll failed",{error:t}),zt=t,Ht=n)}finally{jt=!1}}},Fn=()=>{Tt&&(clearInterval(Tt),Tt=null),Pt&&(clearInterval(Pt),Pt=null)},jn=async e=>{if(window.botApi?.getAccountMetrics){if((!e||"coin"===e)&&Cn("coin"))if(kn("coin")){const e=await window.botApi.getAccountMetrics({market:"coin"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);ct.textContent=n>0?ln(i):sn(0),dt.textContent=sn(n),mt.textContent=sn(t),ut.textContent=cn(Number(e.marginRatio||0))}else Gn("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}else ct.textContent=sn(0),dt.textContent="- 円",mt.textContent="- 円",ut.textContent="- %";if((!e||"fx"===e)&&Cn("fx"))if(kn("fx")){const e=await window.botApi.getAccountMetrics({market:"fx"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);gt.textContent=n>0?ln(i):sn(0),pt.textContent=sn(n),ft.textContent=sn(t),yt.textContent=cn(Number(e.marginRatio||0))}else Gn("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}else gt.textContent=sn(0),pt.textContent="- 円",ft.textContent="- 円",yt.textContent="- %"}},_n=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").toUpperCase(),o=n.replace("_","/"),a=String(i.side||"-"),r=String(i.size||"").trim(),s=Number(r||0),l=Number(i.price||0),c=Number(i.lossGain||0),d=Number(i.totalSwap||0),m=String(i.timestamp||"-"),u=Number(i.positionId||0),g=Number.isFinite(u)&&u>0&&r?`data-market="${e}" data-symbol="${n}" data-side="${String(a||"").toUpperCase()}" data-position-id="${u}" data-size="${r}"`:"disabled",p=document.createElement("tr");p.innerHTML=`\n <td><button type="button" class="close-btn" ${g}>決済</button></td>\n <td>${o}<br />${a}</td>\n <td>${s.toLocaleString("ja-JP")}</td>\n <td>${l.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${ln(c)}<br />${ln(d)}</td>\n <td>${m.replace("T"," ").slice(0,19)}</td>\n `,t.appendChild(p)}}else t.innerHTML='<tr><td colspan="6" class="empty-cell">対象のお取引はございません。</td></tr>'},zn=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").replace("_","/"),o=String(i.side||"-"),a=Number("fx"===e?i.sumPositionSize||0:i.sumPositionQuantity||0),r=Number(i.averagePositionRate||0),s=Number(i.positionLossGain||0),l=Number("fx"===e&&i.sumTotalSwap||0),c=document.createElement("tr");c.innerHTML=`\n <td><button type="button" class="close-btn">決済</button></td>\n <td>${n}<br />${o}</td>\n <td>${a.toLocaleString("ja-JP")}</td>\n <td>${r.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${ln(s)}<br />${ln(l)}</td>\n `,t.appendChild(c)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},On=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if((!e||"coin"===e)&&Cn("coin"))if(kn("coin")){const e=await window.botApi.getPositionSummary({market:"coin",symbol:Lt});e?.ok?(Vt=Array.isArray(e.items)?e.items:[],zn("coin",wt,Vt),Tn("coin")):(Gn("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),Vt=[],zn("coin",wt,[]),Tn("coin"))}else Vt=[],zn("coin",wt,[]),Tn("coin");if((!e||"fx"===e)&&Cn("fx"))if(kn("fx")){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(Qt=Array.isArray(e.items)?e.items:[],zn("fx",ht,Qt),Tn("fx")):(Gn("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),Qt=[],zn("fx",ht,[]),Tn("fx"))}else Qt=[],zn("fx",ht,[]),Tn("fx");if((!e||"coin"===e)&&Cn("coin"))if(kn("coin")){const e=await window.botApi.getOpenPositions({market:"coin",symbol:Lt,page:1,count:100});e?.ok?_n("coin",bt,Array.isArray(e.items)?e.items:[]):(Gn("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),_n("coin",bt,[]))}else _n("coin",bt,[]);if((!e||"fx"===e)&&Cn("fx"))if(kn("fx")){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?_n("fx",kt,Array.isArray(e.items)?e.items:[]):(Gn("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),_n("fx",kt,[]))}else _n("fx",kt,[])}},Hn=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(et.value||"").trim();i?await ei(t,async()=>{const t=await n.placeFxOrder({symbol:Bt,side:e,size:i});if(!t?.ok){const n=Rn(t?.error||"fx order failed");return Gn("[fx] order failed",{symbol:Bt,side:e,size:i,error:n}),Wn("error",n),void await Xn("fx.order",n,t)}Gn("[fx] order placed",{symbol:Bt,side:e,size:i,result:t.result||null}),Wn("success",`${e} 注文を送信しました。`),await jn("fx"),await On("fx")}):Wn("error","数量を入力してください。")},Jn=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=wn(String(Ze.value||""));if(!i)return void Wn("error","数量の形式が不正です。");const o=((e,t)=>{const n=Rt.get(e);if(!n)return{ok:!0};const i=wn(n.minOrderSize),o=wn(n.sizeStep),a=wn(n.maxOrderSize);if(!i||!o)return{ok:!0};const r=Math.max(bn(t),bn(i),bn(o),bn(a)),s=hn(t,r),l=hn(i,r),c=hn(o,r);if(s<l)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(c>0n&&s%c!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(a&&s>hn(a,r))return{ok:!1,reason:`${e} の最大数量は ${a} です。`};return{ok:!0}})(Lt,i);o.ok?await ei(t,async()=>{const t=await n.placeCoinOrder({symbol:Lt,side:e,size:i});if(!t?.ok){const n=Rn(t?.error||"coin order failed");return Gn("[coin] order failed",{symbol:Lt,side:e,size:i,error:n}),Wn("error",n),void await Xn("coin.order",n,t)}Gn("[coin] order placed",{symbol:Lt,side:e,size:i,result:t.result||null}),Wn("success",`${e} 注文を送信しました。`),await jn("coin"),await On("coin")}):Wn("error",o.reason||"数量が取引ルールに一致しません。")},qn=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},Gn=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:qn(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!Z)return;const i=document.createElement("div");i.className="log-entry";const o=document.createElement("div");o.className="log-line";const a=document.createElement("span");if(a.className="log-ts",a.textContent=n||(new Date).toISOString(),o.appendChild(a),o.append(document.createTextNode(e||"")),i.appendChild(o),null!=t&&""!==t){const e=document.createElement("pre");e.className="status-output",e.textContent="string"==typeof t?t:JSON.stringify(t,null,2),i.appendChild(e)}for(Z.appendChild(i);Z.children.length>300;)Z.removeChild(Z.firstChild);Z.scrollTop=Z.scrollHeight},Kn=(e,t,n="")=>{const i=document.createElement("div");i.className=`msg ${e}`;let o=n||("user"===e?"You":"Assistant");"line-user"===e&&(o=n||"LINE User"),"line-assistant"===e&&(o=n||"AI"),i.textContent=`${o}: ${t}`,le.appendChild(i),le.scrollTop=le.scrollHeight},Yn=()=>{const e=le.querySelector(".msg.assistant-thinking");e&&e.remove()},Rn=e=>{const t=String(e||"").trim(),n=t.toLowerCase();return t?n.includes("api key")&&n.includes("missing")?"API Key 未设置,请先在 Config 页面保存。":n.includes("auth")||n.includes("401")||n.includes("403")?"认证失败,请检查 API Key 和 Secret。":n.includes("network")||n.includes("fetch failed")||n.includes("timeout")?"网络连接异常,请检查网络后重试。":n.includes("message is required")?"请输入内容后再发送。":t:"处理失败,请稍后重试。"},Wn=(e,t)=>{$t&&(clearTimeout($t),$t=null),r.classList.remove("hidden","notice-error","notice-success"),r.classList.add("error"===e?"notice-error":"notice-success"),r.textContent=t,$t=setTimeout(()=>{Vn()},"success"===e?2200:4200)},Xn=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},Vn=()=>{$t&&(clearTimeout($t),$t=null),r.classList.add("hidden"),r.classList.remove("notice-error","notice-success"),r.textContent=""},Qn=(e,t)=>{Mt&&(clearTimeout(Mt),Mt=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,Mt=setTimeout(()=>{Zn()},"success"===e?2200:4200)},Zn=()=>{Mt&&(clearTimeout(Mt),Mt=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},ei=async(e,t)=>{if(e.disabled)return;e.disabled=!0,e.classList.add("is-loading");const n=e.textContent||"";try{await t()}catch(e){const t=Rn(e instanceof Error?e.message:String(e));Gn("[ui] action failed",{error:t}),Wn("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},ti=(e,t,n=[])=>{const i=e.getContext("2d");if(!i)return;const o=e.width,a=e.height;if(i.clearRect(0,0,o,a),!t.length)return;const r=Math.max(...t.map(e=>e.high)),s=Math.min(...t.map(e=>e.low)),l=n.map(e=>e.price).filter(e=>Number.isFinite(e)),c=l.length?Math.max(r,...l):r,d=l.length?Math.min(s,...l):s,m=Math.max(1e-8,c-d),u=10,g=10,p=o-10-62-8,f=a-10-24-8,y=Math.max(2,p/t.length),w=e=>g+(c-e)/m*f,b=e=>e>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:0}):e>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:3}):e.toLocaleString("en-US",{minimumFractionDigits:5,maximumFractionDigits:5});i.strokeStyle="#c9d4ea",i.strokeRect(.5,.5,o-1,a-1),i.strokeStyle="#e3eaf5",i.lineWidth=1;for(let e=0;e<=4;e+=1){const t=g+f/4*e;i.beginPath(),i.moveTo(u,t),i.lineTo(u+p,t),i.stroke()}t.forEach((e,t)=>{const n=u+t*y+.5*y,o=w(e.high),a=w(e.low),r=w(e.open),s=w(e.close),l=e.close>=e.open;i.strokeStyle=l?"#1f9d55":"#d64545",i.fillStyle=l?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,a),i.stroke();const c=n-.3*y,d=Math.max(1,.6*y),m=Math.min(r,s),g=Math.max(1,Math.abs(s-r));i.fillRect(c,m,d,g)});for(const e of n){const t=w(e.price);i.save(),i.setLineDash([4,4]),i.strokeStyle=e.color,i.lineWidth=1,i.beginPath(),i.moveTo(u,t),i.lineTo(u+p,t),i.stroke(),i.restore(),i.font="12px Segoe UI";const n=`${e.label} ${b(e.price)}`,o=Math.ceil(i.measureText(n).width)+10,a=14,r=Math.max(12,Math.min(g+f-18,t-9));i.fillStyle=e.color,i.fillRect(a,r,o,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(n,a+5,r+9)}const h=t[t.length-1],k=h?.close;if(Number.isFinite(k)){const e=w(Number(k));i.save(),i.setLineDash([5,4]),i.strokeStyle="#2b79ff",i.lineWidth=1,i.beginPath(),i.moveTo(u,e),i.lineTo(u+p,e),i.stroke(),i.restore();const t=b(Number(k));i.font="12px Segoe UI";const n=Math.ceil(i.measureText(t).width)+10,o=u+p+6,a=Math.max(12,Math.min(g+f-18,e-9));i.fillStyle="#2b79ff",i.fillRect(o,a,n,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(t,o+5,a+9)}i.fillStyle="#66758d",i.font="12px Segoe UI",i.textBaseline="middle",i.textAlign="left";for(let e=0;e<=4;e+=1){const t=c-m/4*e,n=g+f/4*e;i.fillText(b(t),u+p+8,n)}const x=e=>{const t=new Date(e>1e12?e:1e3*e);if(Number.isNaN(t.getTime()))return"-";const n=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0");return`${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${n}:${i}`};i.textAlign="center",i.textBaseline="top";for(let e=0;e<=4;e+=1){const n=Math.min(t.length-1,Math.floor((t.length-1)*(e/4))),o=u+p*e/4;i.fillText(x(t[n]?.time||0),o,g+f+6)}},ni=(e,t)=>{const{tabBtn:n,titleNode:i,packageNode:o,enabledNode:a,versionNode:r,fallbackTitle:s,fallbackPackage:l}=t,c=String(e?.ui?.surfaceTitle||e?.displayName||s).trim()||s,d=e?.name||l,m=String(e?.ui?.tabLabel||d.replace("@ai.weget.jp/","")).trim()||d.replace("@ai.weget.jp/",""),u=!!e&&Boolean(e.enabled),g=String(e?.installStatus||"uninstalled").trim()||"uninstalled",p=String(e?.configuredVersion||e?.version||"-").trim()||"-";if(n){const e=n.querySelector(".tab-btn-label");e&&(e.textContent=m),n.classList.toggle("is-disabled",!u)}i&&(i.textContent=c),o&&(o.textContent=d),a&&(a.textContent=u?"enabled":"disabled",a.classList.toggle("is-disabled",!u)),r&&(r.textContent=`${g} · ${p}`)},ii=(e,t)=>{t.titleNode&&(t.titleNode.textContent=String(e?.ui?.configTitle||t.fallbackTitle).trim()||t.fallbackTitle);const n=Array.isArray(e?.ui?.configFields)&&e.ui?.configFields||[];for(const[e,i]of Object.entries(t.fields)){const t=n.find(t=>t.key===e);t?.label&&i.labelNode&&(i.labelNode.textContent=t.label),void 0!==t?.placeholder&&(i.inputNode.placeholder=t.placeholder||"")}},oi=(e,t)=>{if(e){if(e.innerHTML="",!t.length){const t=document.createElement("span");return t.className="muted",t.textContent="No macro snapshot cards available.",void e.appendChild(t)}for(const n of t){const t=document.createElement("div");t.className="macro-chip"+("down"===n.trend?" is-negative":"");const i=document.createElement("div");i.className="macro-chip-code",i.textContent=n.code||"-";const o=document.createElement("div");o.className="macro-chip-value",o.textContent=n.value||"-";const a=document.createElement("div");a.className="macro-chip-delta"+("down"===n.trend?" is-negative":""),a.textContent=n.delta||"-",t.append(i,o,a),e.appendChild(t)}}},ai=(e,t,n)=>{if(!e)return;if(e.innerHTML="",!t.length){const t=document.createElement("div");return t.className="macro-treemap-empty",t.textContent=n,void e.appendChild(t)}const i=[...t].map(e=>({symbol:String(e.symbol||"").trim(),change:Number(e.change||0),weight:Math.max(1,Math.abs(Number(e.change||0)))})).filter(e=>e.symbol).sort((e,t)=>Math.abs(t.change)-Math.abs(e.change)).slice(0,12),o=[],a=i.reduce((e,t)=>e+t.weight,0)||1,r=(e,t,n,i,a,s)=>{if(!e.length)return;if(1===e.length)return void o.push({item:e[0],x:t,y:n,width:i,height:a});const l=e.reduce((e,t)=>e+t.weight,0);let c=1,d=e[0].weight;for(;c<e.length-1&&d<l/2;)c+=1,d+=e[c-1].weight;const m=e.slice(0,c),u=e.slice(c),g=d/l;if(s){const e=i*g;return r(m,t,n,e,a,!s),void r(u,t+e,n,i-e,a,!s)}const p=a*g;r(m,t,n,i,p,!s),r(u,t,n+p,i,a-p,!s)};r(i,0,0,100,100,!0);for(const{item:t,x:n,y:i,width:r,height:s}of o){const o=document.createElement("div"),l=Math.min(.88,.22+t.weight/Math.max(.18*a,1));o.className="macro-treemap-tile "+(t.change>=0?"up":"down"),o.style.setProperty("--tile-intensity",String(l)),o.style.left=`${n}%`,o.style.top=`${i}%`,o.style.width=`${r}%`,o.style.height=`${s}%`,o.innerHTML=`\n <div class="macro-treemap-symbol">${un(t.symbol.replace(/_JPY$/i,"").replace(/_USD$/i," $"))}</div>\n <div class="macro-treemap-change">${t.change>=0?"+":""}${t.change.toFixed(1)}%</div>\n `,e.appendChild(o)}},ri=e=>{if(q)if(q.innerHTML="",e.length)for(const t of e){const e=gn(t.ai_analyzer||""),n=document.createElement("tr");n.className="macro-data-row",n.innerHTML=`\n <td>${un(mn(t.date))}</td>\n <td>${un(t.country||"-")}</td>\n <td class="macro-title-cell"><strong>${un(t.title||"-")}</strong></td>\n <td>${fn(Number(t.importance||0))}</td>\n <td>${un(t.actual||"-")}</td>\n <td>${un(t.forecast||"-")}</td>\n <td>${un(t.previous||"-")}</td>\n <td>${yn(e.direction)}</td>\n `;const i=document.createElement("tr");i.className="macro-ai-row",i.innerHTML=`\n <td colspan="8" class="macro-analyzer">${un(e.cleaned||"-")}</td>\n `,q.append(n,i)}else q.innerHTML='<tr><td colspan="8" class="empty-cell">No calendar rows for the selected date.</td></tr>'},si=e=>{if(G)if(G.innerHTML="",e.length)for(const t of e){const e=gn(t.ai_analyzer||""),n=String(t.url||"").trim(),i=document.createElement("tr");i.className="macro-data-row",i.innerHTML=`\n <td>${un(mn(t.time))}</td>\n <td class="macro-title-cell"><strong>${un(t.content||"-")}</strong></td>\n <td>${pn(e.impact)}</td>\n <td>${yn(e.direction)}</td>\n <td>${n?`<a class="macro-link" href="${un(n)}" target="_blank" rel="noreferrer noopener">open</a>`:"-"}</td>\n `;const o=document.createElement("tr");o.className="macro-ai-row",o.innerHTML=`\n <td colspan="5" class="macro-analyzer">${un(e.cleaned||"-")}</td>\n `,G.append(i,o)}else G.innerHTML='<tr><td colspan="4" class="empty-cell">No news rows for the selected date.</td></tr>'},li=e=>{F&&(F.textContent="-"),j&&(j.textContent="-"),_&&(_.textContent=e),oi(z,[]),oi(O,[]),ai(H,[],"No coin heatmap data."),ai(J,[],"No FX heatmap data."),ri([]),si([])},ci=async({forceSnapshot:e=!1}={})=>{if(Yt)return;if(!window.botApi?.getMacroSnapshot||!window.botApi?.getMacroCalendar||!window.botApi?.getMacroNews)return void li("Macro IPC bridge is not available in this host build.");const t=En("@ai.weget.jp/skill-macro-economy");if(!t||!t.enabled||"active"!==String(t.installStatus||"").trim().toLowerCase())return void li("Macro Economy skill is disabled or not active.");if(!Ut)return void li("Login is required to load macro snapshot, calendar, and news.");Yt=!0;const n=String(K?.value||"").trim()||dn(new Date);K&&!K.value&&(K.value=n);try{const[t,i,o]=await Promise.all([window.botApi.getMacroSnapshot({force:e}),window.botApi.getMacroCalendar({dateKey:n}),window.botApi.getMacroNews({dateKey:n})]);if(!t.ok)throw new Error(t.error||"macro snapshot failed");if(!i.ok)throw new Error(i.error||"macro calendar failed");if(!o.ok)throw new Error(o.error||"macro news failed");const a=t.snapshot;F&&(F.textContent=mn(a?.as_of)),j&&(j.textContent=null==a?.fear_greed_score?"-":`${Number(a.fear_greed_score).toLocaleString("ja-JP",{maximumFractionDigits:0})}`),_&&(_.textContent=`Loaded macro data for ${n} from the local macro economy skill runtime.`);const r=Array.isArray(a?.coin_cards)?[...a.coin_cards]:[];null!=a?.fear_greed_score&&r.unshift({code:"FGI",value:`${Number(a.fear_greed_score).toLocaleString("ja-JP",{maximumFractionDigits:0})}`,delta:"Fear & Greed",trend:Number(a.fear_greed_score)>=50?"up":"down",points:[],usePill:!0}),oi(z,r),oi(O,Array.isArray(a?.fx_cards)?a?.fx_cards:[]),ai(H,Array.isArray(a?.coin_heatmap)?a?.coin_heatmap:[],"No coin heatmap data."),ai(J,Array.isArray(a?.fx_heatmap)?a?.fx_heatmap:[],"No FX heatmap data."),ri(Array.isArray(i.rows)?i.rows:[]),si(Array.isArray(o.rows)?o.rows:[])}catch(e){li(Rn(e instanceof Error?e.message:String(e)))}finally{Yt=!1}},di=(e,t)=>{W&&(W.textContent=e,W.classList.toggle("is-disabled","installed"!==e),W.classList.toggle("is-muted","unknown"===e)),X&&(X.textContent=t||"No Playwright browser detail available.")},mi=({browserStatus:e})=>{V&&(V.innerHTML="installed"!==e?"Run <code>npx playwright install chromium</code> on this machine.":"Chromium is ready for Playwright-backed browser tasks.")},ui=async()=>{if(!window.botApi?.getPlaywrightBrowserStatus)return di("unknown","Playwright browser check is not available in this host build."),void mi({browserStatus:"unknown"});const e=await window.botApi.getPlaywrightBrowserStatus(),t=e.ok&&e.status||"unknown";e.ok?di(t,String(e.detail||"").trim()):di("unknown",Rn(e.error)),mi({browserStatus:t})},gi=(e,t,n,i=!1)=>{e&&(e.textContent=t||"-",e.classList.toggle("is-disabled",!n&&!i),e.classList.toggle("is-muted",i))},pi=async()=>{if(!window.botApi?.getGatewayStatus)return;const e=await window.botApi.getGatewayStatus();if(!e?.ok)return gi(ee,"error",!1),gi(te,"error",!1),gi(ne,"error",!1),void(oe&&(oe.textContent=Rn(e?.error||"gateway status failed")));gi(ee,String(e.codexAuth?.status||"unknown"),"logged_in"===String(e.codexAuth?.status||""),"unknown"===String(e.codexAuth?.status||"")),gi(te,String(e.gatewayStatus?.status||"unknown"),"configured"===String(e.gatewayStatus?.status||""),"unknown"===String(e.gatewayStatus?.status||"")),gi(ne,String(e.browserStatus?.status||"unknown"),"installed"===String(e.browserStatus?.status||""),"unknown"===String(e.browserStatus?.status||"")),ie&&(ie.textContent=`Context file: ${String(e.contextFilePath||"-")}`),oe&&(oe.textContent=JSON.stringify({codexAuth:e.codexAuth||null,gatewayStatus:e.gatewayStatus||null,browserStatus:e.browserStatus||null},null,2)),(e=>{if(!se)return;se.innerHTML="";const t=[{target:"gateway",title:"Gateway Core"},{target:"codex_macro",title:"Codex Macro Chain",packageName:"@ai.weget.jp/skill-macro-economy"},{target:"browser",title:"Browser Skill",packageName:"@ai.weget.jp/skill-browser"},{target:"macro",title:"Macro Economy Skill",packageName:"@ai.weget.jp/skill-macro-economy"},{target:"coin",title:"GMO Coin Skill",packageName:"@ai.weget.jp/skill-gmo-coin"},{target:"fx",title:"GMO FX Skill",packageName:"@ai.weget.jp/skill-gmo-fx"}];for(const n of t){const t=n.packageName?e.find(e=>e.name===n.packageName):null,i=document.createElement("section");i.className="browser-skill-card gateway-test-card",i.innerHTML=`\n <div class="gateway-test-head">\n <h3>${n.title}</h3>\n <span class="skill-surface-badge is-muted">${t?t.enabled?"enabled":"disabled":"system"}</span>\n </div>\n <div class="muted">${t?`${t.name} · ${t.installStatus}`:"Gateway MCP self-test"}</div>\n <div class="skill-tags">${t&&Array.isArray(t.tools)&&t.tools.length?t.tools.map(e=>`<span class="skill-tag">${un(e)}</span>`).join(""):'<span class="muted">No declared tools</span>'}</div>\n <div class="row config-actions">\n <button type="button" class="secondary gateway-test-btn" data-target="${n.target}">Run Test</button>\n </div>\n <div class="gateway-test-result">\n <span class="skill-surface-badge is-muted gateway-test-badge" data-target="${n.target}">not run</span>\n <div class="muted gateway-test-summary" data-target="${n.target}">-</div>\n </div>\n `,se.appendChild(i)}})(Array.isArray(e.skills)?e.skills:[])},fi=async()=>{if(!window.botApi?.getRuntimeInfo)return;const[e,t]=await Promise.all([window.botApi.getRuntimeInfo(),window.botApi.getSkills?window.botApi.getSkills():(async()=>({ok:!1,skills:[]}))()]);if(!e?.ok)return;const n=t?.ok&&t.skills||[];Kt=n,an(e.status||"disconnected"),e.session?(on(!0),u.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(on(!1),u.textContent="local mode (not logged in)"),(e=>{g.innerHTML="";const t=Zt(e),n=Zt(t.runtime),i=t.session?Zt(t.session):null,o=[["Bot ID",String(i?.botId||"-")],["User ID",String(i?.userId||"-")],["Email",String(i?.email||"-")],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["Active Skills",Array.isArray(n.activeSkills)&&n.activeSkills.map(e=>{const t=Zt(e);return String(t.displayName||t.name||"").trim()}).filter(Boolean).join(", ")||"-"],["GMO FX API State",String(n.gmoFxApiState||"unknown")],["GMO Coin API State",String(n.gmoCoinApiState||"unknown")],["Codex Auth",String(n.codexAuthStatus||"unknown")],["Codex Auth Detail",String(n.codexAuthDetail||"-")],["Default Model",String(n.aiModel||"-")],["Login API",String(n.loginApiUrl||"-")]];for(const[e,t]of o){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const i=document.createElement("div");i.className="runtime-value",i.textContent=t,g.appendChild(n),g.appendChild(i)}})(e),(e=>{if(!p)return;p.innerHTML="";const t=Array.isArray(e)?e:[];if(0===t.length){const e=document.createElement("div");return e.className="skill-empty",e.textContent="No managed skills are available in this bot host.",void p.appendChild(e)}for(const e of t){const t=document.createElement("section");t.className="skill-card"+(e.enabled?"":" is-disabled");const n=document.createElement("div");n.className="skill-card-head";const i=document.createElement("div");i.className="skill-card-title";const o=document.createElement("strong");o.textContent=e.displayName||e.name;const a=document.createElement("code");if(a.textContent=e.name,i.appendChild(o),i.appendChild(a),e.description){const t=document.createElement("div");t.className="muted",t.textContent=e.description,i.appendChild(t)}const r=document.createElement("div");r.className="skill-badges";const s=document.createElement("span");s.className="skill-badge "+(e.enabled?"enabled":"disabled"),s.textContent=e.enabled?"enabled":"disabled",r.appendChild(s);const l=document.createElement("span");l.className="skill-badge status",l.textContent=e.installStatus||"unknown",r.appendChild(l),n.appendChild(i),n.appendChild(r),t.appendChild(n);const c=e=>{const t=document.createElement("div");t.className="skill-card-section";const n=document.createElement("div");return n.className="skill-section-label",n.textContent=e,t.appendChild(n),t},d=c("Package"),m=document.createElement("code");m.className="skill-package-code",m.textContent=e.name,d.appendChild(m),t.appendChild(d);const u=c("Tools"),g=document.createElement("div");g.className="skill-tags";const f=Array.isArray(e.tools)?e.tools:[];if(f.length>0)for(const e of f){const t=document.createElement("span");t.className="skill-tag",t.textContent=e,g.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No tools declared",g.appendChild(e)}u.appendChild(g),t.appendChild(u);const y=c("Permissions");if(Array.isArray(e.permissions)&&e.permissions.length>0){const t=document.createElement("div");t.className="skill-tags";for(const n of e.permissions){const e=document.createElement("span");e.className="skill-tag permissions",e.textContent=n,t.appendChild(e)}y.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No permissions declared",y.appendChild(e)}t.appendChild(y);const w=c("State"),b=document.createElement("div");b.className="skill-meta";const h=[["Bundled Version",e.version||"-"],["Configured Version",e.configuredVersion||"-"],["UI",e.hasUi?"has ui":"no ui"],["Config Keys",String(Object.keys(e.configJson||{}).length)]];for(const[e,t]of h){const n=document.createElement("div");n.className="skill-meta-row";const i=document.createElement("span");i.textContent=e;const o=document.createElement("strong");o.textContent=t,n.appendChild(i),n.appendChild(o),b.appendChild(n)}w.appendChild(b),t.appendChild(w),p.appendChild(t)}})(n),ni(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{tabBtn:f,titleNode:h,packageNode:E,enabledNode:I,versionNode:$,fallbackTitle:"GMO Coin Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-coin"}),ni(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{tabBtn:y,titleNode:k,packageNode:v,enabledNode:A,versionNode:M,fallbackTitle:"GMO FX Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-fx"});const i=n.find(e=>"@ai.weget.jp/skill-browser"===e.name);ni(i,{tabBtn:w,titleNode:x,packageNode:C,enabledNode:L,versionNode:T,fallbackTitle:"Browser Skill",fallbackPackage:"@ai.weget.jp/skill-browser"}),((e,t,n)=>{if(e){if(e.innerHTML="",0===t.length){const t=document.createElement("span");return t.className="muted",t.textContent=n,void e.appendChild(t)}for(const n of t){const t=document.createElement("span");t.className="skill-tag",t.textContent=n,e.appendChild(t)}}})(D,Array.isArray(i?.tools)?i.tools:[],"No browser tools declared."),U&&(U.innerHTML="");const o=n.find(e=>"@ai.weget.jp/skill-macro-economy"===e.name);ni(o,{tabBtn:b,titleNode:S,packageNode:N,enabledNode:B,versionNode:P,fallbackTitle:"Macro Economy Skill",fallbackPackage:"@ai.weget.jp/skill-macro-economy"}),await pi(),await ui(),ii(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{titleNode:Se,fallbackTitle:"FX Skill Config",fields:{riskDailyLossLimitJpy:{labelNode:ve,inputNode:ue},fxApiKey:{labelNode:Ce,inputNode:ye},fxApiSecret:{labelNode:Ne,inputNode:we}}}),ii(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{titleNode:Ee,fallbackTitle:"Coin Skill Config",fields:{riskDailyLossLimitJpy:{labelNode:Ie,inputNode:ge},cryptoApiKey:{labelNode:Ae,inputNode:pe},cryptoApiSecret:{labelNode:Le,inputNode:fe}}}),e.session?(ci(),ki()):li("Login is required to load macro snapshot, calendar, and news.")};ke.addEventListener("click",async()=>{await ei(ke,async()=>{if(!window.botApi?.saveSkillConfig)return;const e={riskDailyLossLimitJpy:Number(ue.value||"0"),fxApiKey:String(ye.value||"").trim(),fxApiSecret:String(we.value||"").trim()},t=await window.botApi.saveSkillConfig("@ai.weget.jp/skill-gmo-fx",e);if(!t.ok)return Gn(`[trade-config] fx save failed: ${t.error||"unknown"}`),Wn("error",Rn(t.error)),void await Xn("trade-config.fx.save",Rn(t.error),t);Gn("[trade-config] fx saved"),Wn("success","FX skill 配置已保存到本机。"),await fi()})}),xe.addEventListener("click",async()=>{await ei(xe,async()=>{if(!window.botApi?.saveSkillConfig)return;const e={riskDailyLossLimitJpy:Number(ge.value||"0"),cryptoApiKey:String(pe.value||"").trim(),cryptoApiSecret:String(fe.value||"").trim()},t=await window.botApi.saveSkillConfig("@ai.weget.jp/skill-gmo-coin",e);if(!t.ok)return Gn(`[trade-config] coin save failed: ${t.error||"unknown"}`),Wn("error",Rn(t.error)),void await Xn("trade-config.coin.save",Rn(t.error),t);Gn("[trade-config] coin saved"),Wn("success","Coin skill 配置已保存到本机。"),await fi()})}),Me.addEventListener("click",async()=>{await ei(Me,async()=>{if(!window.botApi?.saveAiConfig)return;const e={aiModel:String(Be.value||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String($e.value||"").trim()},t=await window.botApi.saveAiConfig(e);if(!t.ok)return Gn(`[ai-config] save failed: ${t.error||"unknown"}`),Wn("error",Rn(t.error)),void await Xn("ai-config.save",Rn(t.error),t);Gn("[ai-config] saved"),Wn("success","AI 设置已保存。"),await fi()})}),Te.addEventListener("click",async()=>{await ei(Te,async()=>{if(!window.botApi?.startCodexLogin)return;const e=await window.botApi.startCodexLogin();if(!e.ok)return Gn(`[codex] login launch failed: ${e.detail||"unknown"}`),Wn("error",Rn(e.detail)),void await Xn("codex.login.launch",Rn(e.detail),e);Gn("[codex] login launched",{detail:e.detail}),Wn("success",e.detail||"Codex login started."),setTimeout(()=>{fi()},1500)})}),Pe.addEventListener("click",async()=>{const e=String(De.textContent||"codex login --device-auth").trim();try{if(!await(async e=>{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0;const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","true"),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select();const n=document.execCommand("copy");return document.body.removeChild(t),n})(e))return void Wn("error","无法复制命令,请手动执行。");Gn("[codex] login command copied"),Wn("success","Codex 登录命令已复制。")}catch(e){const t=Rn(e instanceof Error?e.message:String(e));Gn("[codex] copy login command failed",{error:t}),Wn("error",t)}}),Ue.addEventListener("click",async()=>{await ei(Ue,async()=>{await fi(),Gn("[codex] auth status refreshed"),Wn("success","Codex 状态已刷新。")})}),Q?.addEventListener("click",async()=>{await ei(Q,async()=>{await ui(),Gn("[browser] chromium status refreshed"),Wn("success","Browser status 已刷新。")})}),ae?.addEventListener("click",async()=>{await ei(ae,async()=>{if(!window.botApi?.installPlaywrightMcp)return;const e=await window.botApi.installPlaywrightMcp();if(!e.ok)return Gn("[gateway] mcp install failed",{error:e.detail||e.error||"unknown"}),Wn("error",Rn(e.detail||e.error)),await Xn("gateway.install",Rn(e.detail||e.error),e),void await pi();Gn("[gateway] mcp configured",{detail:e.detail}),Wn("success",e.detail||"WeGet Gateway MCP configured."),await pi()})}),re?.addEventListener("click",async()=>{await ei(re,async()=>{await pi(),Wn("success","Gateway status 已刷新。")})}),se?.addEventListener("click",async e=>{const t=e.target,n=t?.closest(".gateway-test-btn");if(!n)return;const i=String(n.dataset.target||"").trim().toLowerCase();"gateway"!==i&&"codex_macro"!==i&&"browser"!==i&&"macro"!==i&&"coin"!==i&&"fx"!==i||await(async(e,t)=>{const n=window.botApi;n?.runGatewaySelfTest&&await ei(t,async()=>{const t=await n.runGatewaySelfTest({target:e}),i=se?.querySelector(`.gateway-test-badge[data-target="${e}"]`),o=se?.querySelector(`.gateway-test-summary[data-target="${e}"]`);if(i){const e=Boolean(t?.ok);i.textContent=e?"OK":"NG",i.classList.toggle("is-muted",!1),i.classList.toggle("is-disabled",!e)}if(o){const e=String(t?.summary||t?.error||t?.detail||"-").trim()||"-",n=String(t?.logPath||"").trim();o.textContent=n?`${e} [log] ${n}`:e}if(!t?.ok){const n=Rn(t?.summary||t?.error||t?.detail||`${e} test failed`);return Gn("[gateway] self-test failed",{target:e,error:n,logPath:t?.logPath||""}),void Wn("error",n)}Gn("[gateway] self-test ok",{target:e,summary:t?.summary||"",logPath:t?.logPath||""}),Wn("success",`${e} test completed.`)})})(i,n)}),Y?.addEventListener("click",async()=>{await ei(Y,async()=>{await ci({forceSnapshot:!0}),Gn("[macro] snapshot refreshed"),Wn("success","Macro snapshot 已刷新。")})}),R?.addEventListener("click",async()=>{await ei(R,async()=>{await ci(),Gn("[macro] calendar and news refreshed",{dateKey:String(K?.value||"").trim()}),Wn("success","Macro calendar / news 已刷新。")})}),K?.addEventListener("change",()=>{ci()}),be.addEventListener("click",()=>{const e="password"===fe.type?"text":"password";fe.type=e,be.textContent="password"===e?"显示":"隐藏"}),he.addEventListener("click",()=>{const e="password"===we.type?"text":"password";we.type=e,he.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;Zn();const t=l.value.trim(),n=s.value.trim(),i=c.value;if(t&&n&&i){je.disabled=!0,je.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,d.checked);if(!e.ok)return Gn(`[ui] login failed: ${e.error||"unknown"}`),void Qn("error",Rn(e.error));Gn("[ui] login success"),Qn("success","登录成功。"),Wn("success","登录成功。"),await fi()}finally{je.disabled=!1,je.textContent="Login"}}}),Fe.addEventListener("click",async()=>{await ei(Fe,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return Gn(`[ui] logout failed: ${e.error||"unknown"}`),Wn("error",Rn(e.error)),void await Xn("auth.logout",Rn(e.error),e);Gn("[ui] logout"),Wn("success","已登出。"),on(!1),u.textContent="local mode (not logged in)",await fi()})});const yi=async({market:e,symbol:t,interval:n,canvas:i})=>{if(!window.botApi)return;const o=await window.botApi.openGmoKlineWindow({symbol:t,interval:n,market:e});if(!o.ok)return Gn(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),Wn("error",Rn(o.error)),void await Xn(`${e}.kline`,Rn(o.error),o);const a=Array.isArray(o.candles)?o.candles:[],r=Ln(e);r.symbol=String(o.symbol||t||"").toUpperCase(),r.interval=n,r.candles=a.map(e=>({time:Number(e.time||0),open:Number(e.open||0),high:Number(e.high||0),low:Number(e.low||0),close:Number(e.close||0)})),r.lastFetchBucket=Math.floor(Date.now()/An(n)),ti(i,r.candles,Mn(e)),Gn("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:a.length}),Vn()},wi=async()=>{await yi({market:"coin",symbol:Lt,interval:It,canvas:Je})},bi=async()=>{await yi({market:"fx",symbol:Bt,interval:At,canvas:qe})},hi=({container:e,getCurrent:t,setCurrent:n,onChange:i})=>{const o=Array.from(e.querySelectorAll(".interval-btn"));for(const e of o)e.addEventListener("click",async()=>{const a=String(e.dataset.interval||"").trim();if(a&&a!==t()){n(a);for(const t of o)t.classList.toggle("is-active",t===e);await i()}})};_e.addEventListener("change",async()=>{Lt=String(_e.value||"BTC_JPY").trim().toUpperCase(),In("coin",Lt);try{await Nn("coin",Lt),await wi(),await On("coin")}catch(e){const t=Rn(e instanceof Error?e.message:String(e));Wn("error",t)}}),ze.addEventListener("change",async()=>{Bt=String(ze.value||"USD_JPY").trim().toUpperCase(),In("fx",Bt);try{await Nn("fx",Bt),await bi(),await On("fx")}catch(e){const t=Rn(e instanceof Error?e.message:String(e));Wn("error",t)}}),hi({container:Oe,getCurrent:()=>It,setCurrent:e=>{It=e},onChange:wi}),hi({container:He,getCurrent:()=>At,setCurrent:e=>{At=e},onChange:bi}),st.addEventListener("click",async()=>{await ei(st,async()=>{await jn("coin")})}),lt.addEventListener("click",async()=>{await ei(lt,async()=>{await jn("fx")})}),Ze.addEventListener("input",()=>{Sn("coin")}),et.addEventListener("input",()=>{Sn("fx")}),bt.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeCoinPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),a="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",r="BUY"===a?"SELL":"BUY",s=Number(n.dataset.positionId||0),l=wn(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&l?await ei(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:r,positionId:s,size:l});if(!e?.ok){const t=Rn(e?.error||"coin close order failed");return Gn("[coin] close order failed",{symbol:o,positionSide:a,closeSide:r,positionId:s,size:l,error:t}),Wn("error",t),void await Xn("coin.closeOrder",t,e)}Gn("[coin] close order placed",{symbol:o,positionSide:a,closeSide:r,positionId:s,size:l,result:e.result||null}),Wn("success","決済注文を送信しました。"),await jn("coin"),await On("coin")}):Wn("error","決済対象データが不正です。")}),kt.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeFxPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),a="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",r="BUY"===a?"SELL":"BUY",s=Number(n.dataset.positionId||0),l=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&l?await ei(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:r,positionId:s,size:l});if(!e?.ok){const t=Rn(e?.error||"fx close order failed");return Gn("[fx] close order failed",{symbol:o,positionSide:a,closeSide:r,positionId:s,size:l,error:t}),Wn("error",t),void await Xn("fx.closeOrder",t,e)}Gn("[fx] close order placed",{symbol:o,positionSide:a,closeSide:r,positionId:s,size:l,result:e.result||null}),Wn("success","決済注文を送信しました。"),await jn("fx"),await On("fx")}):Wn("error","決済対象データが不正です。")}),tt.addEventListener("click",async()=>{await Jn("BUY",tt)}),nt.addEventListener("click",async()=>{await Jn("SELL",nt)}),it.addEventListener("click",async()=>{await Hn("BUY",it)}),ot.addEventListener("click",async()=>{await Hn("SELL",ot)}),xt.addEventListener("click",async()=>{await ei(xt,async()=>{await On("coin")})}),St.addEventListener("click",async()=>{await ei(St,async()=>{await On("coin")})}),Et.addEventListener("click",async()=>{await ei(Et,async()=>{await On("fx")})}),vt.addEventListener("click",async()=>{await ei(vt,async()=>{await On("fx")})}),ce.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(de.value||"").trim();if(t){Kn("user",t),de.value="",me.disabled=!0,((e="AI")=>{Yn();const t=document.createElement("div");t.className="msg assistant assistant-thinking";const n=document.createElement("div");n.className="msg-title",n.textContent=`${e}:`;const i=document.createElement("div");i.className="thinking-body";const o=document.createElement("span");o.className="thinking-text",o.textContent="Thinking";const a=document.createElement("span");a.className="thinking-dots",a.innerHTML="<span></span><span></span><span></span>",i.append(o,a),t.append(n,i),le.appendChild(t),le.scrollTop=le.scrollHeight})("AI");try{const e=await window.botApi.sendChat(t);if(!e.ok)return Yn(),Kn("assistant",`Error: ${e.error||"unknown"}`),Wn("error",Rn(e.error)),void await Xn("ai.chat",Rn(e.error),e);Vn()}catch(e){Yn();const t=Rn(e instanceof Error?e.message:String(e));Kn("assistant",`Error: ${t}`),Wn("error",t),await Xn("ai.chat.exception",t,e)}finally{me.disabled=!1}}}),window.addEventListener("error",e=>{const t=Rn(e.error?.message||e.message||"Unknown UI error");Gn("[ui] uncaught error",{error:t}),Wn("error",t),Xn("ui.error",t,{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason.message:String(e.reason||"Unhandled promise rejection"),n=Rn(t);Gn("[ui] unhandled rejection",{error:n}),Wn("error",n),Xn("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{an(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=Zt(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void Kn("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";return void Kn("line-assistant",String(t.text||""),e)}"assistant_status"!==t.type?"assistant"===t.type&&(Yn(),Kn("assistant",String(t.text||""))):(e=>{const t=String(e||"").trim().replace(/\s+/g," ");if(!t)return;if(/^[{}[\],:]+$/.test(t))return;if(/^["']?[a-zA-Z0-9_-]+["']?\s*:\s*$/.test(t))return;const n=le.querySelector(".msg.assistant-thinking .thinking-text");n&&(n.textContent=t)})(String(t.status||"Thinking"))}),window.botApi.onTradeExecution(e=>{if(!e)return;const t=Zt(e),n="coin"===String(t.market||"").trim().toLowerCase()?"coin":"fx",i=String(t.symbol||"").trim().toUpperCase(),o=String(t.side||"").trim().toUpperCase()||"-",a=String(t.settleType||"").trim().toUpperCase()||"OPEN",r=String(t.executionSize??"").trim(),s=String(t.executionPrice??"").trim(),l="CLOSE"===a?"決済":"新規",c=`${i} ${o} ${r}${s?` @ ${s}`:""}`;Gn(`[${n}] execution filled`,{symbol:i,side:o,settleType:a,size:r,price:s,raw:t}),Wn("success",`約定成功 (${l}) ${c}`.trim()),jn(n).catch(()=>{}),On(n).catch(()=>{})}));const ki=async()=>{if(Ut&&!Dt&&!Ft){Ft=!0;try{if(!await async function(){let e=0;if(In("coin",Lt),In("fx",Bt),Cn("coin")){e+=1;try{await Nn("coin",Lt)}catch(e){const t=Rn(e instanceof Error?e.message:String(e));Gn("[coin] quote fetch failed",{error:t}),Wn("error",t)}await wi()}if(Cn("fx")){e+=1;try{await Nn("fx",Bt)}catch(e){const t=Rn(e instanceof Error?e.message:String(e));Gn("[fx] quote fetch failed",{error:t}),Wn("error",t)}await bi()}if(window.botApi?.getSymbolRules&&Cn("coin"))try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){Rt.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&Rt.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){Gn("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}return e>0}())return;await jn(),await On(),Cn("coin")&&!Tt&&(Tt=setInterval(()=>{Dn("coin")},1e3)),Cn("fx")&&!Pt&&(Pt=setInterval(()=>{Dn("fx")},1e3)),Dt=!0}finally{Ft=!1}}};on(!1),(()=>{try{tn("true"===window.localStorage.getItem(en))}catch{tn(!1)}})(),K&&!K.value&&(K.value=dn(new Date)),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(s.value=e.profile.botId||"",l.value=e.profile.email||"",c.value=e.profile.password||"",d.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getSkillConfig||!window.botApi.getAiConfig)return;const[e,t,n]=await Promise.all([window.botApi.getSkillConfig("@ai.weget.jp/skill-gmo-fx"),window.botApi.getSkillConfig("@ai.weget.jp/skill-gmo-coin"),window.botApi.getAiConfig()]);e?.ok&&e.config&&(ue.value=String(e.config.riskDailyLossLimitJpy||5e4),ye.value=String(e.config.fxApiKey||""),we.value=String(e.config.fxApiSecret||"")),t?.ok&&t.config&&(ge.value=String(t.config.riskDailyLossLimitJpy||5e4),pe.value=String(t.config.cryptoApiKey||""),fe.value=String(t.config.cryptoApiSecret||"")),n?.ok&&n.config&&(Be.value=n.config.aiModel||"gpt-5.4",$e.value=n.config.logOutputDir||"")})(),fi();export{};