@ai.weget.jp/bot 0.1.12 → 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,6 +37,9 @@ 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`
@@ -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/`.
@@ -1 +1 @@
1
- const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,getSkillConfig:s,saveSkillConfig:a,getAiConfig:c,saveAiConfig:g,setCurrentSession:m,getCurrentSession:l,getActiveSkills:u,getCodexAuthStatus:d,startCodexLogin:S,getManagedSkills:k,emitLog:y,openGmoKlineWindow:f,getGmoMarketQuotes:p,getGmoSymbolRules:h,getGmoBuyingPower:b,getGmoAccountMetrics:w,getGmoPositionSummary:C,getGmoOpenPositions:L,placeGmoFxOrder:E,placeGmoCoinOrder:x,placeGmoFxCloseOrder:I,placeGmoCoinCloseOrder:v,getGmoApiState:A,testGmoApi:U,buildTradeStatusSnapshot:G,closeTradeWindows:q,startWsClient:P,stopWsClient:z,handleBotChatMessage:B,writeErrorLog:N,writeRuntimeLog:F})=>{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 m(e),P({botId:s,userId:String(r.userId||""),accessToken:String(r.accessToken||"")}),y("[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 y("[auth] login failed",{error:e}),{ok:!1,error:e}}}),e.handle("auth:logout",async()=>(z(),m(null),q(),y("[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("skill:getConfig",async(r,e)=>{try{const r=String(e||"").trim();return{ok:!0,...s(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 a(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=l(),o=r(e),i=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:A("fx"),gmoCoinApiState:A("coin"),aiModel:c().aiModel,activeSkills:u(),codexAuthStatus:i.status,codexAuthDetail:i.detail}}}),e.handle("bot:getSkills",async()=>({ok:!0,skills:k()})),e.handle("codex:startLogin",async()=>S()),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 f({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 p({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 h({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 b({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 w({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 C({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 L({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 E({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 x({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 I({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 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:testGmoApi",async(e,t)=>{const o=r(t),i="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";return U(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 G(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 B({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 N({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 F({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 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 n}from"node:module";import{SYSTEM_CONFIG as s}from"./config/systemConfig.js";import{BotWsClient as o}from"./wsClient.js";import{registerIpcHandlers as r}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as g}from"./services/codexService.js";import{createPlatformApiClient as c}from"./platformApi.js";import{createSkillRuntimeManager as d}from"./skillRuntimeManager.js";import{createWindowManager as m}from"./services/windowManagerService.js";import{createSkillHostState as p}from"./skillHostState.js";const u=n(import.meta.url),{app:f,BrowserWindow:S,ipcMain:y,nativeImage:w}=u("electron"),k=a(import.meta.url),b=t.dirname(k),h=["trade","ai"],_=()=>t.join(f.getPath("userData"),"login-profile.json"),v=()=>t.join(f.getPath("userData"),"host-config.json"),x=()=>t.join(f.getPath("userData"),"skill-states.json");let A=null,C=null,O={aiModel:"gpt-5.4",logOutputDir:""};const E=new Map;let M=()=>t.join(f.getPath("documents"),"weget-bot-logs");const P=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:n,details:s,ts:o})=>{const r=M();await e.mkdir(r,{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(r,`${i}-${g}${c}${d}.log`),p=JSON.stringify({ts:o||l.toISOString(),level:i,source:a,message:n,details:s??null},null,0);await e.appendFile(m,`${p}\n`,"utf8")},D=m({BrowserWindow:S,preloadPath:t.join(b,"preload.cjs"),rendererDir:t.join(b,"renderer"),windowIconPath:t.join(b,"renderer","logo.png")}),I=(t,e)=>{const i=(new Date).toISOString();D.emitBotLog({message:t,data:e||null,ts:i}),j({level:P(t),source:"runtime",message:t,details:e,ts:i}).catch(()=>{})},L=t=>{D.emitChatEvent(t)},T=t=>t&&"object"==typeof t?t:{},B=p({log:I}),U=l({getEnv:()=>({loginApiUrl:s.loginApiUrl,botUploadApiUrl:s.botUploadApiUrl}),emitLog:I,getCurrentSession:()=>A}),F=c({getBaseUrl:()=>String(s.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),getSession:()=>A,log:I}),N=d({installRoot:t.join(f.getPath("userData"),"skills"),botId:()=>String(A?.botId||"").trim()||void 0,log:I,api:F,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}),emitSkillEvent:(t,e)=>{if("trade_execution"!==e.type){if("trade_history"===e.type){const i=`trade-${Date.now()}`;C?.sendTaskEvent(i,"trade_history",{package_name:t,...e.payload&&"object"==typeof e.payload?e.payload:{}}),I("[trade] history event sent (execution)",{package_name:t,payload:e.payload})}}else D.emitTradeExecution(e.payload)}}),G=()=>{const t=N.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=N.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime;if(!t)throw new Error("GMO Coin skill runtime is not installed");return t},J=()=>N.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime,W=()=>N.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime,$=(t,e)=>{const i=t.gateway[e];if("function"!=typeof i)throw new Error(`skill runtime does not implement gateway.${e}`);return i},K=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"}},z=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"}};M=()=>String(O.logOutputDir||"").trim()||t.join(f.getPath("documents"),"weget-bot-logs");const q=g({getModel:()=>{const t=J(),e=W();return O.aiModel||t?.getConfig?.().aiModel||e?.getConfig?.().aiModel||"gpt-5.4"},getWorkingDir:()=>f.getPath("userData"),emitLog:I}),H=async t=>{const i=T(t);return O={aiModel:String(i.aiModel||O.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i.logOutputDir||"").trim()},await e.writeFile(v(),JSON.stringify({...O,updatedAt:(new Date).toISOString()},null,2),"utf8"),O};const Q=async()=>{const t=await N.refreshLoadedSkills();return B.setAvailableSkills(t.map(t=>t.manifest)),t},X=async t=>{const e=await Q();I("[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}))})},Y=()=>{const t=B.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),e=B.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"),i=J(),a=W();i&&("connected"===C?.getStatus()&&t?i.startExecutionStreams():i.stopExecutionStreams()),a&&("connected"===C?.getStatus()&&e?a.startExecutionStreams():a.stopExecutionStreams()),I("[skills] applied managed states",{active_skills:B.getActiveSkills().map(t=>t.name)})},V=async()=>{const t=U.getBotRuntimeConfigApiUrl(),e=String(A?.botId||"").trim();if(!t||!e)return;const i=await U.callBotApi(t,{bot_id:e}),a=String(i.chat_model||"").trim(),n={...a?{aiModel:a}:{}};if(0===Object.keys(n).length)return;await H({...O,...n});const s=J(),o=W();s&&await s.saveConfig({...s.getConfig(),...n}),o&&await o.saveConfig({...o.getConfig(),...n}),I("[config] runtime config synced",{chat_model:a||""})},Z=async()=>{const t=U.getBotSkillsListApiUrl(),i=String(A?.botId||"").trim();if(!t||!i)return;const a=await U.callBotApi(t,{bot_id:i}),n=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})):[];B.applySnapshots(n),await(async t=>{await e.writeFile(x(),JSON.stringify({updatedAt:(new Date).toISOString(),items:t},null,2),"utf8")})(n),await X(n),Y(),I("[config] skill states synced",{count:n.length,bot_id:i,enabled:n.filter(t=>t.enabled).length})},tt=t=>{if("fx"===t&&!B.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===t&&!B.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")},et={"@ai.weget.jp/skill-gmo-fx":{getConfig:()=>K(G().getConfig()),saveConfig:async t=>{const e=G(),i=await e.saveConfig({...e.getConfig(),...K(t)});return"connected"===C?.getStatus()&&Y(),K(i)}},"@ai.weget.jp/skill-gmo-coin":{getConfig:()=>z(R().getConfig()),saveConfig:async t=>{const e=R(),i=await e.saveConfig({...e.getConfig(),...z(t)});return"connected"===C?.getStatus()&&Y(),z(i)}}},it=t=>{const e=String(t||"").trim(),i=et[e];if(!i)throw new Error(`skill config is not managed by bot host: ${t}`);return i},at=async()=>{await N.ensureInstallRoot(),await Q();const t=await async function(){try{const t=await e.readFile(x(),"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&&(B.applySnapshots(t),I("[skills] restored local snapshot",{count:t.length}),await X(t)),Y()};C=new o({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:h,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(t,e)=>I(t,e),onStatus:t=>{D.emitBotStatus({status:t}),Y()},onLineMessage:async t=>{const e=String(t.line_user_id||""),i=String(t.text||"").trim();if(L({type:"line_user",lineUserId:e,text:i}),!i)return{answer:""};try{const t=await q.runPrompt({prompt:i,contextLabel:"line_message"});return L({type:"line_assistant",lineUserId:e,text:t}),{answer:t}}catch(t){const i=t instanceof Error?t.message:String(t);I("[ai] line reply failed",{error:i});const a=`AI error: ${i}`;return L({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}),C?.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 C?.sendTaskEvent(e,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void E.delete(e);try{C?.sendTaskEvent(e,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const t=await q.runPrompt({prompt:i,contextLabel:`task:${e}`}),a=E.get(e);if(a?.cancelled)return C?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void E.delete(e);C?.sendTaskEvent(e,"ai_output",{summary:t,ts:(new Date).toISOString()}),C?.sendTaskEvent(e,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),C?.sendTaskEvent(e,"task_completed",{summary:t.slice(0,500),ts:(new Date).toISOString()})}catch(t){C?.sendTaskEvent(e,"task_failed",{error:t instanceof Error?t.message:String(t),ts:(new Date).toISOString()})}finally{E.delete(e)}}else I("[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 I("[task] cancel requested",{task_id:e});C?.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 V(),i.includes("skills")&&await Z()}}),r({ipcMain:y,env:{capabilities:h,loginApiUrl:s.loginApiUrl},signInWithLoginApi:U.signInWithLoginApi,saveLoginProfile:async({botId:t,email:i,password:a,remember:n})=>{if(n)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:it(t).getConfig()}),saveSkillConfig:async(t,e)=>({config:await it(t).saveConfig(e)}),getAiConfig:()=>({aiModel:String(O.aiModel||"gpt-5.4"),logOutputDir:String(O.logOutputDir||"")}),saveAiConfig:async t=>{const e=t&&"object"==typeof t?t:{},i=await H({...O,...e}),a=J(),n=W(),s=a?await a.saveConfig({...a.getConfig(),...e}):null;return n&&await n.saveConfig({...n.getConfig(),...e}),{aiModel:String(i.aiModel||s?.aiModel||e.aiModel||"gpt-5.4"),logOutputDir:String(i.logOutputDir||"")}},setCurrentSession:t=>{A=t||null;const e=A?"connected":"disconnected";D.emitBotStatus({status:e})},getCurrentSession:()=>A,getActiveSkills:()=>B.getActiveSkills().map(t=>({name:t.name,displayName:t.displayName,version:t.version})),getCodexAuthStatus:()=>q.getAuthStatus(),startCodexLogin:()=>q.startLogin(),getManagedSkills:()=>B.getManagedSkills().map(t=>({...t,ui:N.getLoadedSkill(t.name)?.ui||void 0})),emitLog:I,openGmoKlineWindow:async({symbol:t,interval:e,market:i})=>(()=>{const a="fx"===i?"fx":"coin";return tt(a),("fx"===a?G().marketData:R().marketData).fetchKline({symbol:t,interval:e,market:a})})(),getGmoMarketQuotes:async({market:t,symbols:e})=>(()=>{const i="fx"===t?"fx":"coin";return tt(i),("fx"===i?G().marketData:R().marketData).fetchQuotes({market:i,symbols:e})})(),getGmoSymbolRules:async({market:t})=>(()=>{const e="fx"===t?"fx":"coin";return tt(e),("fx"===e?G().marketData:R().marketData).fetchSymbolRules({market:e})})(),getGmoBuyingPower:async({market:t})=>({market:t,availableJpy:await(tt(t),("fx"===t?G().gateway:R().gateway).getBuyingPower(t))}),getGmoAccountMetrics:async({market:t})=>(tt(t),("fx"===t?G().gateway:R().gateway).getAccountMetrics(t)),getGmoPositionSummary:async({market:t,symbol:e})=>({market:t,symbol:e,items:await(tt(t),("fx"===t?G().gateway:R().gateway).getPositionSummary({market:t,symbol:e}))}),getGmoOpenPositions:async({market:t,symbol:e,page:i,prevId:a,count:n})=>({market:t,symbol:e||"",items:await(tt(t),("fx"===t?G().gateway:R().gateway).getOpenPositions({market:"fx"===t?"fx":"crypto",symbol:e||void 0,page:i,prevId:a,count:n}))}),placeGmoFxOrder:async({symbol:t,side:e,size:i})=>{tt("fx");return await $(G(),"placeFxOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:t,side:e,size:i})=>{tt("coin");return await $(R(),"placeCoinOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{tt("fx");return await $(G(),"placeFxCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{tt("coin");return await $(R(),"placeCoinCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},getGmoApiState:t=>{const e="fx"===t?J():W();return e?.status.getApiState()||"unknown"},testGmoApi:t=>(tt(t),"fx"===t?G().status.testApi():R().status.testApi()),buildTradeStatusSnapshot:t=>(tt(t),"fx"===t?G().status.buildSnapshot():R().status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:t=>{C?.start(t),V().catch(t=>{I("[config] runtime sync failed after login",{error:t instanceof Error?t.message:String(t)})}),Z().catch(t=>{I("[config] skill sync failed after login",{error:t instanceof Error?t.message:String(t)})}),Y()},stopWsClient:()=>{J()?.stopExecutionStreams(),W()?.stopExecutionStreams(),C?.stop()},handleBotChatMessage:async({text:t})=>{L({type:"user",text:String(t||"")});const e=await q.runPrompt({prompt:t,contextLabel:"desktop_chat"});return L({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 nt=t.join(b,"renderer","logo.png");f.whenReady().then(async()=>{(()=>{if("darwin"!==process.platform)return;if(!i(nt))return;const t=w.createFromPath(nt);t.isEmpty()||f.dock.setIcon(t)})(),await(async()=>{try{const t=await e.readFile(v(),"utf8"),i=JSON.parse(t);O={aiModel:String(i?.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i?.logOutputDir||"").trim()}}catch(t){if("ENOENT"!==t?.code)throw t;O={aiModel:"gpt-5.4",logOutputDir:""}}return O})();try{await at()}catch(t){I("[skills] local initialization failed",{error:t instanceof Error?t.message:String(t)})}D.createMainWindow(),f.on("activate",()=>{0===S.getAllWindows().length&&D.createMainWindow()})}),f.on("window-all-closed",()=>{J()?.stopExecutionStreams(),W()?.stopExecutionStreams(),C?.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()});
@@ -11,6 +11,14 @@ contextBridge.exposeInMainWorld('botApi', {
11
11
  getRuntimeInfo: () => ipcRenderer.invoke('bot:getRuntimeInfo'),
12
12
  getSkills: () => ipcRenderer.invoke('bot:getSkills'),
13
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 }),
14
22
  openGmoKlineWindow: ({ symbol, interval, market }) =>
15
23
  ipcRenderer.invoke('trade:openGmoKlineWindow', { symbol, interval, market }),
16
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("sidebar-nav"),r=document.getElementById("sidebar-toggle-btn"),a=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"),f=document.getElementById("skill-state-list"),p=document.getElementById("tab-btn-skill-gmo-coin"),y=document.getElementById("tab-btn-skill-gmo-fx"),b=document.getElementById("tab-btn-skill-browser"),w=document.getElementById("coin-skill-title"),x=document.getElementById("fx-skill-title"),k=document.getElementById("browser-skill-title"),S=document.getElementById("coin-skill-package"),h=document.getElementById("fx-skill-package"),E=document.getElementById("browser-skill-package"),v=document.getElementById("coin-skill-enabled"),C=document.getElementById("fx-skill-enabled"),I=document.getElementById("browser-skill-enabled"),A=document.getElementById("coin-skill-version"),B=document.getElementById("fx-skill-version"),L=document.getElementById("browser-skill-version"),N=document.getElementById("browser-skill-cards"),$=document.getElementById("browser-skill-tools"),T=document.getElementById("logs"),U=document.getElementById("messages"),M=document.getElementById("chat-form"),P=document.getElementById("chat-input"),D=document.getElementById("send-btn"),F=document.getElementById("fx-risk-daily-loss-limit-jpy"),j=document.getElementById("coin-risk-daily-loss-limit-jpy"),z=document.getElementById("crypto-api-key"),O=document.getElementById("crypto-api-secret"),J=document.getElementById("fx-api-key"),q=document.getElementById("fx-api-secret"),Y=document.getElementById("toggle-crypto-secret-btn"),H=document.getElementById("toggle-fx-secret-btn"),R=document.getElementById("save-fx-config-btn"),K=document.getElementById("save-coin-config-btn"),_=document.getElementById("fx-config-title"),W=document.getElementById("coin-config-title"),G=document.getElementById("fx-config-label-riskDailyLossLimitJpy"),V=document.getElementById("fx-config-label-fxApiKey"),X=document.getElementById("fx-config-label-fxApiSecret"),Q=document.getElementById("coin-config-label-riskDailyLossLimitJpy"),Z=document.getElementById("coin-config-label-cryptoApiKey"),ee=document.getElementById("coin-config-label-cryptoApiSecret"),te=document.getElementById("ai-model"),ne=document.getElementById("host-log-output-dir"),ie=document.getElementById("save-ai-config-btn"),oe=document.getElementById("codex-login-btn"),re=document.getElementById("codex-copy-login-btn"),ae=document.getElementById("codex-refresh-auth-btn"),se=document.getElementById("codex-login-command"),le=document.getElementById("logout-btn"),ce=document.getElementById("login-btn"),de=document.getElementById("coin-symbol-select"),me=document.getElementById("fx-symbol-select"),ue=document.getElementById("coin-kline-intervals"),ge=document.getElementById("fx-kline-intervals"),fe=document.getElementById("coin-kline-canvas"),pe=document.getElementById("fx-kline-canvas"),ye=document.getElementById("coin-market-icon"),be=document.getElementById("fx-market-icon"),we=document.getElementById("coin-order-bid"),xe=document.getElementById("coin-order-ask"),ke=document.getElementById("coin-order-spread"),Se=document.getElementById("fx-order-bid"),he=document.getElementById("fx-order-ask"),Ee=document.getElementById("fx-order-spread"),ve=document.getElementById("coin-order-qty"),Ce=document.getElementById("fx-order-qty"),Ie=document.getElementById("coin-buy-btn"),Ae=document.getElementById("coin-sell-btn"),Be=document.getElementById("fx-buy-btn"),Le=document.getElementById("fx-sell-btn"),Ne=document.getElementById("coin-required-amount"),$e=document.getElementById("fx-required-amount"),Te=document.getElementById("coin-account-info-refresh-btn"),Ue=document.getElementById("fx-account-info-refresh-btn"),Me=document.getElementById("coin-account-pnl"),Pe=document.getElementById("coin-account-margin"),De=document.getElementById("coin-account-available"),Fe=document.getElementById("coin-account-margin-ratio"),je=document.getElementById("fx-account-pnl"),ze=document.getElementById("fx-account-margin"),Oe=document.getElementById("fx-account-available"),Je=document.getElementById("fx-account-margin-ratio"),qe=document.getElementById("coin-position-summary-body"),Ye=document.getElementById("coin-position-list-body"),He=document.getElementById("fx-position-summary-body"),Re=document.getElementById("fx-position-list-body"),Ke=document.getElementById("coin-position-summary-refresh-btn"),_e=document.getElementById("coin-position-list-refresh-btn"),We=document.getElementById("fx-position-summary-refresh-btn"),Ge=document.getElementById("fx-position-list-refresh-btn"),Ve=Array.from(document.querySelectorAll(".tab-btn")),Xe=Array.from(document.querySelectorAll(".tab-panel"));let Qe="15m",Ze="15m",et=String(de?.value||"BTC_JPY").trim().toUpperCase(),tt=String(me?.value||"USD_JPY").trim().toUpperCase(),nt=null,it=null,ot=null,rt=null,at=!1,st=!1,lt=!1,ct=!1,dt=!1,mt="",ut="",gt=0,ft=0,pt=null,yt=null,bt=[];const wt=new Map,xt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},kt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let St=[],ht=[];const Et=e=>e&&"object"==typeof e?e:{},vt="weget.bot.sidebar.collapsed",Ct=e=>{i.classList.toggle("sidebar-collapsed",e),r&&(r.textContent=e?"▶":"◀",r.setAttribute("aria-label",e?"Expand menu":"Collapse menu")),o&&o.setAttribute("data-collapsed",e?"true":"false")},It=e=>{for(const t of Ve)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of Xe)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of Ve)e.addEventListener("click",()=>It(e.dataset.tab||"coin"));r?.addEventListener("click",()=>{const e=!i.classList.contains("sidebar-collapsed");Ct(e);try{window.localStorage.setItem(vt,String(e))}catch{}});const At=e=>{const n=at!==e;at=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&gn(),e?n&&En():(Xt(),st=!1)},Bt=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")},Lt=(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})},Nt=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,$t=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,Tt=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),Ut=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(/\.$/,"")},Mt=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},Pt=(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),l=BigInt((r||"0")+s);return i?-l:l},Dt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=Lt(t.bid),a=Lt(t.ask);n.textContent=r,i.textContent=a,o.textContent=Lt(t.spread,8)},Ft=e=>{if("coin"===e){const e=Number(String(ve.value||"").trim()),t=pt;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(Ne.textContent="- 円"):void(Ne.textContent=Nt(e*t/2))}const t=Number(String(Ce.value||"").trim()),n=yt;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?$e.textContent="- 円":$e.textContent=Nt(t*n/20)},jt=e=>{const t=(e=>bt.find(t=>t.name===e))(e);if(!t)return!1;const n=String(t.installStatus||"").trim().toLowerCase();return t.enabled&&"active"===n},zt=e=>jt("fx"===e?"@ai.weget.jp/skill-gmo-fx":"@ai.weget.jp/skill-gmo-coin"),Ot=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?(Dt({symbol:n,quote:o,orderBidNode:we,orderAskNode:xe,orderSpreadNode:ke}),pt=o?.ask??null,Ft("coin")):(Dt({symbol:n,quote:o,orderBidNode:Se,orderAskNode:he,orderSpreadNode:Ee}),yt=o?.ask??null,Ft("fx")),o},Jt=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(ye.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;be.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},qt=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,Yt=e=>"coin"===e?xt:kt,Ht=e=>"coin"===e?fe:pe,Rt=e=>"coin"===e?et:tt,Kt=e=>{const t=Rt(e),n=("coin"===e?St:ht).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},_t=e=>{const t=Yt(e);t.candles.length&&pn(Ht(e),t.candles,Kt(e))},Wt=(e,t)=>{if(!t)return;const n=Yt(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),pn(Ht(e),n.candles,Kt(e)))},Gt=async e=>{const t=Yt(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==Rt(e))return;if(!(Math.floor(Date.now()/qt(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await kn():await Sn()}finally{t.fetching=!1}}},Vt=async e=>{if(zt(e))if("coin"!==e){if(!dt){dt=!0;try{const e=await Ot("fx",tt);Wt("fx",e),await Gt("fx")}catch(e){const t=ln(e instanceof Error?e.message:String(e)),n=Date.now();(t!==ut||n-ft>15e3)&&(an("[fx] quote poll failed",{error:t}),ut=t,ft=n)}finally{dt=!1}}}else{if(ct)return;ct=!0;try{const e=await Ot("coin",et);Wt("coin",e),await Gt("coin")}catch(e){const t=ln(e instanceof Error?e.message:String(e)),n=Date.now();(t!==mt||n-gt>15e3)&&(an("[coin] quote poll failed",{error:t}),mt=t,gt=n)}finally{ct=!1}}},Xt=()=>{ot&&(clearInterval(ot),ot=null),rt&&(clearInterval(rt),rt=null)},Qt=async e=>{if(window.botApi?.getAccountMetrics){if((!e||"coin"===e)&&zt("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);Me.textContent=n>0?$t(i):Nt(0),Pe.textContent=Nt(n),De.textContent=Nt(t),Fe.textContent=Tt(Number(e.marginRatio||0))}else an("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if((!e||"fx"===e)&&zt("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);je.textContent=n>0?$t(i):Nt(0),ze.textContent=Nt(n),Oe.textContent=Nt(t),Je.textContent=Tt(Number(e.marginRatio||0))}else an("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},Zt=(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),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&&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>${l.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${$t(c)}<br />${$t(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>'},en=(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),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>${r.toLocaleString("ja-JP")}</td>\n <td>${a.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${$t(s)}<br />${$t(l)}</td>\n `,t.appendChild(c)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},tn=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if((!e||"coin"===e)&&zt("coin")){const e=await window.botApi.getPositionSummary({market:"coin",symbol:et});e?.ok?(St=Array.isArray(e.items)?e.items:[],en("coin",qe,St),_t("coin")):(an("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),St=[],en("coin",qe,[]),_t("coin"))}if((!e||"fx"===e)&&zt("fx")){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(ht=Array.isArray(e.items)?e.items:[],en("fx",He,ht),_t("fx")):(an("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),ht=[],en("fx",He,[]),_t("fx"))}if((!e||"coin"===e)&&zt("coin")){const e=await window.botApi.getOpenPositions({market:"coin",symbol:et,page:1,count:100});e?.ok?Zt("coin",Ye,Array.isArray(e.items)?e.items:[]):(an("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),Zt("coin",Ye,[]))}if((!e||"fx"===e)&&zt("fx")){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?Zt("fx",Re,Array.isArray(e.items)?e.items:[]):(an("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),Zt("fx",Re,[]))}}},nn=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(Ce.value||"").trim();i?await fn(t,async()=>{const t=await n.placeFxOrder({symbol:tt,side:e,size:i});if(!t?.ok){const n=ln(t?.error||"fx order failed");return an("[fx] order failed",{symbol:tt,side:e,size:i,error:n}),cn("error",n),void await dn("fx.order",n,t)}an("[fx] order placed",{symbol:tt,side:e,size:i,result:t.result||null}),cn("success",`${e} 注文を送信しました。`),await Qt("fx"),await tn("fx")}):cn("error","数量を入力してください。")},on=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=Ut(String(ve.value||""));if(!i)return void cn("error","数量の形式が不正です。");const o=((e,t)=>{const n=wt.get(e);if(!n)return{ok:!0};const i=Ut(n.minOrderSize),o=Ut(n.sizeStep),r=Ut(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(Mt(t),Mt(i),Mt(o),Mt(r)),s=Pt(t,a),l=Pt(i,a),c=Pt(o,a);if(s<l)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(c>0n&&s%c!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>Pt(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(et,i);o.ok?await fn(t,async()=>{const t=await n.placeCoinOrder({symbol:et,side:e,size:i});if(!t?.ok){const n=ln(t?.error||"coin order failed");return an("[coin] order failed",{symbol:et,side:e,size:i,error:n}),cn("error",n),void await dn("coin.order",n,t)}an("[coin] order placed",{symbol:et,side:e,size:i,result:t.result||null}),cn("success",`${e} 注文を送信しました。`),await Qt("coin"),await tn("coin")}):cn("error",o.reason||"数量が取引ルールに一致しません。")},rn=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"},an=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:rn(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!T)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(T.appendChild(i);T.children.length>300;)T.removeChild(T.firstChild);T.scrollTop=T.scrollHeight},sn=(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}`,U.appendChild(i),U.scrollTop=U.scrollHeight},ln=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:"处理失败,请稍后重试。"},cn=(e,t)=>{nt&&(clearTimeout(nt),nt=null),a.classList.remove("hidden","notice-error","notice-success"),a.classList.add("error"===e?"notice-error":"notice-success"),a.textContent=t,nt=setTimeout(()=>{mn()},"success"===e?2200:4200)},dn=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},mn=()=>{nt&&(clearTimeout(nt),nt=null),a.classList.add("hidden"),a.classList.remove("notice-error","notice-success"),a.textContent=""},un=(e,t)=>{it&&(clearTimeout(it),it=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,it=setTimeout(()=>{gn()},"success"===e?2200:4200)},gn=()=>{it&&(clearTimeout(it),it=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},fn=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=ln(e instanceof Error?e.message:String(e));an("[ui] action failed",{error:t}),cn("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},pn=(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)),l=n.map(e=>e.price).filter(e=>Number.isFinite(e)),c=l.length?Math.max(a,...l):a,d=l.length?Math.min(s,...l):s,m=Math.max(1e-8,c-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+(c-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),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,r),i.stroke();const c=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(c,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=c-m/4*e,n=g+p/4*e;i.fillText(w(t),u+f+8,n)}const S=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(S(t[n]?.time||0),o,g+p+6)}},yn=(e,t)=>{const{tabBtn:n,titleNode:i,packageNode:o,enabledNode:r,versionNode:a,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",f=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),r&&(r.textContent=u?"enabled":"disabled",r.classList.toggle("is-disabled",!u)),a&&(a.textContent=`${g} · ${f}`)},bn=(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||"")}},wn=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||[];bt=n,Bt(e.status||"disconnected"),e.session?(At(!0),u.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(At(!1),u.textContent="local mode (not logged in)"),(e=>{g.innerHTML="";const t=Et(e),n=Et(t.runtime),i=t.session?Et(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=Et(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(!f)return;f.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 f.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 l=document.createElement("span");l.className="skill-badge status",l.textContent=e.installStatus||"unknown",a.appendChild(l),n.appendChild(i),n.appendChild(a),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 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,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 b=c("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),f.appendChild(t)}})(n),yn(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{tabBtn:p,titleNode:w,packageNode:S,enabledNode:v,versionNode:A,fallbackTitle:"GMO Coin Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-coin"}),yn(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{tabBtn:y,titleNode:x,packageNode:h,enabledNode:C,versionNode:B,fallbackTitle:"GMO FX Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-fx"});const i=n.find(e=>"@ai.weget.jp/skill-browser"===e.name);yn(i,{tabBtn:b,titleNode:k,packageNode:E,enabledNode:I,versionNode:L,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)}}})($,Array.isArray(i?.tools)?i.tools:[],"No browser tools declared."),(e=>{if(!N)return;N.innerHTML="";const t=Array.isArray(e?.ui?.cards)&&e.ui?.cards||[];if(0===t.length){const e=document.createElement("div");return e.className="browser-skill-card",e.innerHTML='<h3>Skill UI</h3><p class="muted">No browser UI cards declared by the installed skill.</p>',void N.appendChild(e)}for(const e of t){const t=document.createElement("div");t.className="browser-skill-card";const n=document.createElement("h3");n.textContent=e.title;const i=document.createElement("p");i.className="muted",i.textContent=e.body,t.append(n,i),N.appendChild(t)}})(i),bn(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{titleNode:_,fallbackTitle:"FX Skill Config",fields:{riskDailyLossLimitJpy:{labelNode:G,inputNode:F},fxApiKey:{labelNode:V,inputNode:J},fxApiSecret:{labelNode:X,inputNode:q}}}),bn(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{titleNode:W,fallbackTitle:"Coin Skill Config",fields:{riskDailyLossLimitJpy:{labelNode:Q,inputNode:j},cryptoApiKey:{labelNode:Z,inputNode:z},cryptoApiSecret:{labelNode:ee,inputNode:O}}}),e.session&&En()};R.addEventListener("click",async()=>{await fn(R,async()=>{if(!window.botApi?.saveSkillConfig)return;const e={riskDailyLossLimitJpy:Number(F.value||"0"),fxApiKey:String(J.value||"").trim(),fxApiSecret:String(q.value||"").trim()},t=await window.botApi.saveSkillConfig("@ai.weget.jp/skill-gmo-fx",e);if(!t.ok)return an(`[trade-config] fx save failed: ${t.error||"unknown"}`),cn("error",ln(t.error)),void await dn("trade-config.fx.save",ln(t.error),t);an("[trade-config] fx saved"),cn("success","FX skill 配置已保存到本机。"),await wn()})}),K.addEventListener("click",async()=>{await fn(K,async()=>{if(!window.botApi?.saveSkillConfig)return;const e={riskDailyLossLimitJpy:Number(j.value||"0"),cryptoApiKey:String(z.value||"").trim(),cryptoApiSecret:String(O.value||"").trim()},t=await window.botApi.saveSkillConfig("@ai.weget.jp/skill-gmo-coin",e);if(!t.ok)return an(`[trade-config] coin save failed: ${t.error||"unknown"}`),cn("error",ln(t.error)),void await dn("trade-config.coin.save",ln(t.error),t);an("[trade-config] coin saved"),cn("success","Coin skill 配置已保存到本机。"),await wn()})}),ie.addEventListener("click",async()=>{await fn(ie,async()=>{if(!window.botApi?.saveAiConfig)return;const e={aiModel:String(te.value||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(ne.value||"").trim()},t=await window.botApi.saveAiConfig(e);if(!t.ok)return an(`[ai-config] save failed: ${t.error||"unknown"}`),cn("error",ln(t.error)),void await dn("ai-config.save",ln(t.error),t);an("[ai-config] saved"),cn("success","AI 设置已保存。"),await wn()})}),oe.addEventListener("click",async()=>{await fn(oe,async()=>{if(!window.botApi?.startCodexLogin)return;const e=await window.botApi.startCodexLogin();if(!e.ok)return an(`[codex] login launch failed: ${e.detail||"unknown"}`),cn("error",ln(e.detail)),void await dn("codex.login.launch",ln(e.detail),e);an("[codex] login launched",{detail:e.detail}),cn("success",e.detail||"Codex login started."),setTimeout(()=>{wn()},1500)})}),re.addEventListener("click",async()=>{const e=String(se.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 cn("error","无法复制命令,请手动执行。");an("[codex] login command copied"),cn("success","Codex 登录命令已复制。")}catch(e){const t=ln(e instanceof Error?e.message:String(e));an("[codex] copy login command failed",{error:t}),cn("error",t)}}),ae.addEventListener("click",async()=>{await fn(ae,async()=>{await wn(),an("[codex] auth status refreshed"),cn("success","Codex 状态已刷新。")})}),Y.addEventListener("click",()=>{const e="password"===O.type?"text":"password";O.type=e,Y.textContent="password"===e?"显示":"隐藏"}),H.addEventListener("click",()=>{const e="password"===q.type?"text":"password";q.type=e,H.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;gn();const t=l.value.trim(),n=s.value.trim(),i=c.value;if(t&&n&&i){ce.disabled=!0,ce.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,d.checked);if(!e.ok)return an(`[ui] login failed: ${e.error||"unknown"}`),void un("error",ln(e.error));an("[ui] login success"),un("success","登录成功。"),cn("success","登录成功。"),await wn()}finally{ce.disabled=!1,ce.textContent="Login"}}}),le.addEventListener("click",async()=>{await fn(le,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return an(`[ui] logout failed: ${e.error||"unknown"}`),cn("error",ln(e.error)),void await dn("auth.logout",ln(e.error),e);an("[ui] logout"),cn("success","已登出。"),At(!1),u.textContent="local mode (not logged in)",await wn()})});const xn=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 an(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),cn("error",ln(o.error)),void await dn(`${e}.kline`,ln(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=Yt(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()/qt(n)),pn(i,a.candles,Kt(e)),an("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),mn()},kn=async()=>{await xn({market:"coin",symbol:et,interval:Qe,canvas:fe})},Sn=async()=>{await xn({market:"fx",symbol:tt,interval:Ze,canvas:pe})},hn=({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()}})};de.addEventListener("change",async()=>{et=String(de.value||"BTC_JPY").trim().toUpperCase(),Jt("coin",et);try{await Ot("coin",et),await kn(),await tn("coin")}catch(e){const t=ln(e instanceof Error?e.message:String(e));cn("error",t)}}),me.addEventListener("change",async()=>{tt=String(me.value||"USD_JPY").trim().toUpperCase(),Jt("fx",tt);try{await Ot("fx",tt),await Sn(),await tn("fx")}catch(e){const t=ln(e instanceof Error?e.message:String(e));cn("error",t)}}),hn({container:ue,getCurrent:()=>Qe,setCurrent:e=>{Qe=e},onChange:kn}),hn({container:ge,getCurrent:()=>Ze,setCurrent:e=>{Ze=e},onChange:Sn}),Te.addEventListener("click",async()=>{await fn(Te,async()=>{await Qt("coin")})}),Ue.addEventListener("click",async()=>{await fn(Ue,async()=>{await Qt("fx")})}),ve.addEventListener("input",()=>{Ft("coin")}),Ce.addEventListener("input",()=>{Ft("fx")}),Ye.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),l=Ut(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&l?await fn(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:l});if(!e?.ok){const t=ln(e?.error||"coin close order failed");return an("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,error:t}),cn("error",t),void await dn("coin.closeOrder",t,e)}an("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,result:e.result||null}),cn("success","決済注文を送信しました。"),await Qt("coin"),await tn("coin")}):cn("error","決済対象データが不正です。")}),Re.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),l=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&l?await fn(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:l});if(!e?.ok){const t=ln(e?.error||"fx close order failed");return an("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,error:t}),cn("error",t),void await dn("fx.closeOrder",t,e)}an("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,result:e.result||null}),cn("success","決済注文を送信しました。"),await Qt("fx"),await tn("fx")}):cn("error","決済対象データが不正です。")}),Ie.addEventListener("click",async()=>{await on("BUY",Ie)}),Ae.addEventListener("click",async()=>{await on("SELL",Ae)}),Be.addEventListener("click",async()=>{await nn("BUY",Be)}),Le.addEventListener("click",async()=>{await nn("SELL",Le)}),Ke.addEventListener("click",async()=>{await fn(Ke,async()=>{await tn("coin")})}),_e.addEventListener("click",async()=>{await fn(_e,async()=>{await tn("coin")})}),We.addEventListener("click",async()=>{await fn(We,async()=>{await tn("fx")})}),Ge.addEventListener("click",async()=>{await fn(Ge,async()=>{await tn("fx")})}),M.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(P.value||"").trim();if(t){sn("user",t),P.value="",D.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return sn("assistant",`Error: ${e.error||"unknown"}`),cn("error",ln(e.error)),void await dn("ai.chat",ln(e.error),e);sn("assistant",String(e.answer||"")),mn()}catch(e){const t=ln(e instanceof Error?e.message:String(e));sn("assistant",`Error: ${t}`),cn("error",t),await dn("ai.chat.exception",t,e)}finally{D.disabled=!1}}}),window.addEventListener("error",e=>{const t=ln(e.error?.message||e.message||"Unknown UI error");an("[ui] uncaught error",{error:t}),cn("error",t),dn("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=ln(t);an("[ui] unhandled rejection",{error:n}),cn("error",n),dn("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{Bt(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=Et(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void sn("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";sn("line-assistant",String(t.text||""),e)}}),window.botApi.onTradeExecution(e=>{if(!e)return;const t=Et(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(),l="CLOSE"===r?"決済":"新規",c=`${i} ${o} ${a}${s?` @ ${s}`:""}`;an(`[${n}] execution filled`,{symbol:i,side:o,settleType:r,size:a,price:s,raw:t}),cn("success",`約定成功 (${l}) ${c}`.trim()),Qt(n).catch(()=>{}),tn(n).catch(()=>{})}));const En=async()=>{if(at&&!st&&!lt){lt=!0;try{if(!await async function(){let e=0;if(Jt("coin",et),Jt("fx",tt),zt("coin")){e+=1;try{await Ot("coin",et)}catch(e){const t=ln(e instanceof Error?e.message:String(e));an("[coin] quote fetch failed",{error:t}),cn("error",t)}await kn()}if(zt("fx")){e+=1;try{await Ot("fx",tt)}catch(e){const t=ln(e instanceof Error?e.message:String(e));an("[fx] quote fetch failed",{error:t}),cn("error",t)}await Sn()}if(window.botApi?.getSymbolRules&&zt("coin"))try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){wt.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&wt.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){an("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}return e>0}())return;await Qt(),await tn(),zt("coin")&&!ot&&(ot=setInterval(()=>{Vt("coin")},1e3)),zt("fx")&&!rt&&(rt=setInterval(()=>{Vt("fx")},1e3)),st=!0}finally{lt=!1}}};At(!1),(()=>{try{Ct("true"===window.localStorage.getItem(vt))}catch{Ct(!1)}})(),(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&&(F.value=String(e.config.riskDailyLossLimitJpy||5e4),J.value=String(e.config.fxApiKey||""),q.value=String(e.config.fxApiSecret||"")),t?.ok&&t.config&&(j.value=String(t.config.riskDailyLossLimitJpy||5e4),z.value=String(t.config.cryptoApiKey||""),O.value=String(t.config.cryptoApiSecret||"")),n?.ok&&n.config&&(te.value=n.config.aiModel||"gpt-5.4",ne.value=n.config.logOutputDir||"")})(),wn();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{};
@@ -72,16 +72,20 @@
72
72
  <span class="tab-btn-icon">HC</span>
73
73
  <span class="tab-btn-label">Host Control</span>
74
74
  </button>
75
- <button type="button" class="tab-btn" data-tab="ai" data-icon="AI">
75
+ <button type="button" class="tab-btn is-active" data-tab="ai" data-icon="AI">
76
76
  <span class="tab-btn-icon">AI</span>
77
77
  <span class="tab-btn-label">Codex Console</span>
78
78
  </button>
79
+ <button type="button" class="tab-btn" data-tab="gateway" data-icon="GW">
80
+ <span class="tab-btn-icon">GW</span>
81
+ <span class="tab-btn-label">Gateway</span>
82
+ </button>
79
83
  </div>
80
84
  </div>
81
85
  <div class="tab-group">
82
86
  <span class="tab-group-label">Installed Skills</span>
83
87
  <div class="tabs">
84
- <button id="tab-btn-skill-gmo-coin" type="button" class="tab-btn is-active" data-tab="coin" data-icon="C">
88
+ <button id="tab-btn-skill-gmo-coin" type="button" class="tab-btn" data-tab="coin" data-icon="C">
85
89
  <span class="tab-btn-icon">C</span>
86
90
  <span class="tab-btn-label">skill-gmo-coin</span>
87
91
  </button>
@@ -93,13 +97,17 @@
93
97
  <span class="tab-btn-icon">Br</span>
94
98
  <span class="tab-btn-label">skill-browser</span>
95
99
  </button>
100
+ <button id="tab-btn-skill-macro-economy" type="button" class="tab-btn" data-tab="macro" data-icon="ME">
101
+ <span class="tab-btn-icon">ME</span>
102
+ <span class="tab-btn-label">skill-macro-economy</span>
103
+ </button>
96
104
  </div>
97
105
  </div>
98
106
  </nav>
99
107
  </aside>
100
108
 
101
109
  <section class="content-shell">
102
- <section id="tab-coin" class="tab-panel is-active">
110
+ <section id="tab-coin" class="tab-panel">
103
111
  <section class="panel market-shell coin-theme">
104
112
  <div class="skill-surface-head">
105
113
  <div>
@@ -419,9 +427,9 @@
419
427
  </section>
420
428
  </section>
421
429
 
422
- <section id="tab-browser" class="tab-panel">
423
- <section class="panel">
424
- <div class="skill-surface-head">
430
+ <section id="tab-browser" class="tab-panel">
431
+ <section class="panel">
432
+ <div class="skill-surface-head">
425
433
  <div>
426
434
  <div class="skill-surface-kicker">Installed Skill</div>
427
435
  <h2 id="browser-skill-title">Browser Skill</h2>
@@ -431,16 +439,172 @@
431
439
  <span id="browser-skill-enabled" class="skill-surface-badge">enabled</span>
432
440
  <span id="browser-skill-version" class="skill-surface-badge is-muted">builtin</span>
433
441
  </div>
442
+ </div>
443
+ <div id="browser-skill-cards" class="browser-skill-shell"></div>
444
+ <div class="browser-skill-card">
445
+ <div class="browser-mcp-status-row">
446
+ <span id="browser-chromium-status" class="skill-surface-badge is-muted">unknown</span>
447
+ <span id="browser-chromium-detail" class="muted">Checking local Playwright browser installation...</span>
448
+ </div>
449
+ <div class="row config-actions">
450
+ <button id="browser-refresh-playwright-mcp-btn" type="button" class="secondary">Refresh Browser Status</button>
451
+ </div>
452
+ <div id="browser-prereq-hint" class="muted">Before using browser tasks, run <code>npx playwright install chromium</code> once on this machine.</div>
453
+ </div>
454
+ <div class="browser-skill-card">
455
+ <h3>Declared Tools</h3>
456
+ <div id="browser-skill-tools" class="skill-tags"></div>
457
+ </div>
458
+ </section>
459
+ </section>
460
+
461
+ <section id="tab-gateway" class="tab-panel">
462
+ <section class="panel">
463
+ <div class="panel-title-block">
464
+ <h2>WeGet Gateway MCP</h2>
465
+ <p class="muted">Codex integration, gateway MCP registration, and per-skill connectivity tests.</p>
466
+ </div>
467
+ <div class="macro-status-strip">
468
+ <div class="macro-status-main">
469
+ <div class="macro-stat-row">
470
+ <span class="muted">Codex Auth</span>
471
+ <strong id="gateway-codex-auth">-</strong>
472
+ </div>
473
+ <div class="macro-stat-row">
474
+ <span class="muted">Gateway MCP</span>
475
+ <strong id="gateway-mcp-status">-</strong>
476
+ </div>
477
+ <div class="macro-stat-row">
478
+ <span class="muted">Chromium</span>
479
+ <strong id="gateway-browser-status">-</strong>
480
+ </div>
481
+ </div>
482
+ <div class="macro-toolbar-actions">
483
+ <button id="gateway-install-btn" type="button">Install Gateway MCP</button>
484
+ <button id="gateway-refresh-btn" type="button" class="secondary">Refresh Status</button>
485
+ </div>
434
486
  </div>
435
- <div id="browser-skill-cards" class="browser-skill-shell"></div>
436
487
  <div class="browser-skill-card">
437
- <h3>Declared Tools</h3>
438
- <div id="browser-skill-tools" class="skill-tags"></div>
488
+ <h3>Connection Detail</h3>
489
+ <div id="gateway-context-file" class="muted">Context file: -</div>
490
+ <pre id="gateway-detail" class="status-output">Loading gateway status...</pre>
491
+ </div>
492
+ </section>
493
+ <section class="panel">
494
+ <div class="panel-title-block">
495
+ <h2>Skill Connectivity</h2>
496
+ <p class="muted">Use these tests to verify the gateway can actually reach each skill path.</p>
497
+ </div>
498
+ <div id="gateway-skill-grid" class="gateway-skill-grid"></div>
499
+ </section>
500
+ </section>
501
+
502
+ <section id="tab-macro" class="tab-panel">
503
+ <section class="panel">
504
+ <div class="skill-surface-head">
505
+ <div>
506
+ <div class="skill-surface-kicker">Installed Skill</div>
507
+ <h2 id="macro-skill-title">Macro Economy Skill</h2>
508
+ <div id="macro-skill-package" class="skill-surface-package">@ai.weget.jp/skill-macro-economy</div>
509
+ </div>
510
+ <div class="skill-surface-statuses">
511
+ <span id="macro-skill-enabled" class="skill-surface-badge">enabled</span>
512
+ <span id="macro-skill-version" class="skill-surface-badge is-muted">builtin</span>
513
+ </div>
514
+ </div>
515
+ <section class="macro-status-strip">
516
+ <div class="macro-status-main">
517
+ <strong>Snapshot Status</strong>
518
+ <div class="macro-stat-row">
519
+ <span class="muted">As Of</span>
520
+ <strong id="macro-snapshot-as-of">-</strong>
521
+ </div>
522
+ <div class="macro-stat-row">
523
+ <span class="muted">Fear & Greed</span>
524
+ <strong id="macro-fear-greed-score">-</strong>
525
+ </div>
526
+ </div>
527
+ <div class="macro-toolbar-actions">
528
+ <button id="macro-refresh-snapshot-btn" type="button">Refresh Snapshot</button>
529
+ <button id="macro-refresh-day-btn" type="button" class="secondary">Load Calendar & News</button>
530
+ </div>
531
+ <label class="macro-date-field">
532
+ <span>Macro Date</span>
533
+ <input id="macro-date-key" type="date" />
534
+ </label>
535
+ </section>
536
+ <p id="macro-snapshot-hint" class="muted macro-status-hint">Snapshot is loaded from the macro economy skill runtime.</p>
537
+ <div class="macro-two-col">
538
+ <section class="browser-skill-card macro-side-card">
539
+ <h3>Coin Snapshot</h3>
540
+ <div id="macro-coin-cards" class="macro-chip-grid"></div>
541
+ </section>
542
+ <section class="browser-skill-card macro-side-card">
543
+ <h3>FX Snapshot</h3>
544
+ <div id="macro-fx-cards" class="macro-chip-grid"></div>
545
+ </section>
546
+ </div>
547
+ <div class="macro-two-col">
548
+ <section class="browser-skill-card macro-side-card">
549
+ <h3>Coin Heatmap Movers</h3>
550
+ <div id="macro-coin-heatmap-treemap" class="macro-treemap"></div>
551
+ </section>
552
+ <section class="browser-skill-card macro-side-card">
553
+ <h3>FX Heatmap Movers</h3>
554
+ <div id="macro-fx-heatmap-treemap" class="macro-treemap"></div>
555
+ </section>
556
+ </div>
557
+ </section>
558
+ <section class="panel">
559
+ <div class="panel-title-block">
560
+ <h2>Economic Calendar</h2>
561
+ <p class="muted">Server-backed macro event feed for the selected date.</p>
562
+ </div>
563
+ <div class="table-scroll">
564
+ <table class="position-table macro-table">
565
+ <thead>
566
+ <tr>
567
+ <th>Time</th>
568
+ <th>Country</th>
569
+ <th>Title</th>
570
+ <th>Impact</th>
571
+ <th>Actual</th>
572
+ <th>Forecast</th>
573
+ <th>Previous</th>
574
+ <th>Direction</th>
575
+ </tr>
576
+ </thead>
577
+ <tbody id="macro-calendar-body">
578
+ <tr><td colspan="8" class="empty-cell">No calendar data loaded.</td></tr>
579
+ </tbody>
580
+ </table>
581
+ </div>
582
+ </section>
583
+ <section class="panel">
584
+ <div class="panel-title-block">
585
+ <h2>Economic News</h2>
586
+ <p class="muted">Curated macro news with analyzer output for the selected date.</p>
587
+ </div>
588
+ <div class="table-scroll">
589
+ <table class="position-table macro-table">
590
+ <thead>
591
+ <tr>
592
+ <th>Time</th>
593
+ <th>Content</th>
594
+ <th>Impact</th>
595
+ <th>Direction</th>
596
+ <th>Source</th>
597
+ </tr>
598
+ </thead>
599
+ <tbody id="macro-news-body">
600
+ <tr><td colspan="5" class="empty-cell">No news data loaded.</td></tr>
601
+ </tbody>
602
+ </table>
439
603
  </div>
440
604
  </section>
441
605
  </section>
442
606
 
443
- <section id="tab-ai" class="tab-panel">
607
+ <section id="tab-ai" class="tab-panel is-active">
444
608
  <section class="panel">
445
609
  <div class="panel-title-block">
446
610
  <h2>Codex Console</h2>
@@ -1078,6 +1078,10 @@ button.is-loading::before {
1078
1078
  gap: 12px;
1079
1079
  }
1080
1080
 
1081
+ .browser-skill-shell:empty {
1082
+ display: none;
1083
+ }
1084
+
1081
1085
  .browser-skill-card {
1082
1086
  border: 1px solid #dbe4f1;
1083
1087
  border-radius: 12px;
@@ -1091,6 +1095,319 @@ button.is-loading::before {
1091
1095
  margin: 0;
1092
1096
  }
1093
1097
 
1098
+ .browser-mcp-status-row {
1099
+ display: grid;
1100
+ gap: 8px;
1101
+ }
1102
+
1103
+ .browser-skill-card textarea[readonly] {
1104
+ min-height: 96px;
1105
+ resize: vertical;
1106
+ }
1107
+
1108
+ .status-output {
1109
+ margin: 0;
1110
+ border-radius: 10px;
1111
+ background: #0f1626;
1112
+ color: #dce7f5;
1113
+ padding: 12px;
1114
+ white-space: pre-wrap;
1115
+ word-break: break-word;
1116
+ overflow: auto;
1117
+ }
1118
+
1119
+ .gateway-skill-grid {
1120
+ display: grid;
1121
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1122
+ gap: 12px;
1123
+ }
1124
+
1125
+ .gateway-test-card {
1126
+ align-content: start;
1127
+ }
1128
+
1129
+ .gateway-test-head {
1130
+ display: flex;
1131
+ justify-content: space-between;
1132
+ gap: 10px;
1133
+ align-items: center;
1134
+ }
1135
+
1136
+ .macro-toolbar {
1137
+ display: flex;
1138
+ justify-content: space-between;
1139
+ align-items: end;
1140
+ gap: 12px;
1141
+ flex-wrap: wrap;
1142
+ margin-top: 12px;
1143
+ }
1144
+
1145
+ .macro-toolbar-actions {
1146
+ display: flex;
1147
+ gap: 8px;
1148
+ flex-wrap: wrap;
1149
+ }
1150
+
1151
+ .macro-date-field {
1152
+ margin: 0;
1153
+ min-width: 220px;
1154
+ }
1155
+
1156
+ .macro-status-strip {
1157
+ display: grid;
1158
+ grid-template-columns: minmax(0, 1fr) auto auto;
1159
+ align-items: center;
1160
+ gap: 12px;
1161
+ padding: 12px 14px;
1162
+ border: 1px solid #dbe4f1;
1163
+ border-radius: 12px;
1164
+ background: #f8fbff;
1165
+ }
1166
+
1167
+ .macro-status-main {
1168
+ display: flex;
1169
+ align-items: center;
1170
+ gap: 18px;
1171
+ flex-wrap: wrap;
1172
+ }
1173
+
1174
+ .macro-status-main > strong {
1175
+ color: #16253c;
1176
+ font-size: 18px;
1177
+ }
1178
+
1179
+ .macro-status-hint {
1180
+ margin: 10px 2px 0;
1181
+ }
1182
+
1183
+ .macro-two-col {
1184
+ display: grid;
1185
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1186
+ gap: 12px;
1187
+ margin-top: 12px;
1188
+ }
1189
+
1190
+ .macro-side-card {
1191
+ align-content: start;
1192
+ }
1193
+
1194
+ .macro-stat-row {
1195
+ display: flex;
1196
+ align-items: baseline;
1197
+ gap: 8px;
1198
+ white-space: nowrap;
1199
+ }
1200
+
1201
+ .macro-stat-row strong {
1202
+ color: #223451;
1203
+ font-size: 18px;
1204
+ }
1205
+
1206
+ .macro-chip-grid {
1207
+ display: grid;
1208
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1209
+ gap: 8px;
1210
+ }
1211
+
1212
+ .macro-chip {
1213
+ border-radius: 10px;
1214
+ padding: 10px 12px;
1215
+ background: #edf4ff;
1216
+ border: 1px solid #d4e2fb;
1217
+ display: grid;
1218
+ gap: 4px;
1219
+ }
1220
+
1221
+ .macro-chip.is-negative {
1222
+ background: #fff1f1;
1223
+ border-color: #f7d3d3;
1224
+ }
1225
+
1226
+ .macro-chip-code {
1227
+ color: #597090;
1228
+ font-size: 12px;
1229
+ font-weight: 700;
1230
+ letter-spacing: 0.03em;
1231
+ text-transform: uppercase;
1232
+ }
1233
+
1234
+ .macro-chip-value {
1235
+ color: #16253c;
1236
+ font-size: 18px;
1237
+ font-weight: 700;
1238
+ }
1239
+
1240
+ .macro-chip-delta {
1241
+ color: #1f9d55;
1242
+ font-size: 13px;
1243
+ font-weight: 600;
1244
+ }
1245
+
1246
+ .macro-chip-delta.is-negative {
1247
+ color: #d64545;
1248
+ }
1249
+
1250
+ .macro-treemap {
1251
+ position: relative;
1252
+ height: 240px;
1253
+ border-radius: 12px;
1254
+ overflow: hidden;
1255
+ background: #d7e3ea;
1256
+ }
1257
+
1258
+ .macro-treemap-empty {
1259
+ position: absolute;
1260
+ inset: 0;
1261
+ display: flex;
1262
+ align-items: center;
1263
+ justify-content: center;
1264
+ place-items: center;
1265
+ color: #60708a;
1266
+ background: #f4f7fb;
1267
+ padding: 12px;
1268
+ }
1269
+
1270
+ .macro-treemap-tile {
1271
+ position: absolute;
1272
+ box-sizing: border-box;
1273
+ border: 1px solid rgba(255, 255, 255, 0.75);
1274
+ display: flex;
1275
+ flex-direction: column;
1276
+ align-content: center;
1277
+ align-items: center;
1278
+ justify-items: center;
1279
+ justify-content: center;
1280
+ text-align: center;
1281
+ padding: 12px 10px;
1282
+ gap: 4px;
1283
+ color: #ffffff;
1284
+ }
1285
+
1286
+ .macro-treemap-tile.up {
1287
+ background: color-mix(in srgb, #22c55e calc(var(--tile-intensity, 0.5) * 100%), #96f0b5);
1288
+ }
1289
+
1290
+ .macro-treemap-tile.down {
1291
+ background: color-mix(in srgb, #ef4444 calc(var(--tile-intensity, 0.5) * 100%), #f7b0b0);
1292
+ }
1293
+
1294
+ .macro-treemap-symbol {
1295
+ font-size: clamp(17px, 1.8vw, 30px);
1296
+ font-weight: 700;
1297
+ line-height: 1;
1298
+ }
1299
+
1300
+ .macro-treemap-change {
1301
+ font-size: clamp(15px, 1.5vw, 22px);
1302
+ font-weight: 600;
1303
+ line-height: 1;
1304
+ }
1305
+
1306
+ .table-scroll {
1307
+ overflow-x: auto;
1308
+ }
1309
+
1310
+ .macro-table td {
1311
+ vertical-align: top;
1312
+ }
1313
+
1314
+ .macro-table td:nth-child(2),
1315
+ .macro-table td:nth-child(3),
1316
+ .macro-table td:nth-child(4),
1317
+ .macro-table th:nth-child(2),
1318
+ .macro-table th:nth-child(3),
1319
+ .macro-table th:nth-child(4) {
1320
+ text-align: left;
1321
+ }
1322
+
1323
+ .macro-analyzer {
1324
+ max-width: none;
1325
+ white-space: pre-wrap;
1326
+ word-break: break-word;
1327
+ line-height: 1.55;
1328
+ background: #f8fbff;
1329
+ }
1330
+
1331
+ .macro-ai-label {
1332
+ color: #62748f;
1333
+ font-weight: 700;
1334
+ text-align: center;
1335
+ vertical-align: middle;
1336
+ }
1337
+
1338
+ .macro-ai-row td {
1339
+ border-top: 0;
1340
+ padding: 12px 14px;
1341
+ }
1342
+
1343
+ .macro-data-row td {
1344
+ vertical-align: top;
1345
+ }
1346
+
1347
+ .macro-title-cell {
1348
+ color: #10233f;
1349
+ line-height: 1.45;
1350
+ }
1351
+
1352
+ .macro-title-cell strong {
1353
+ font-weight: 800;
1354
+ }
1355
+
1356
+ .macro-impact-badge {
1357
+ display: inline-flex;
1358
+ align-items: center;
1359
+ justify-content: center;
1360
+ min-width: 64px;
1361
+ border-radius: 999px;
1362
+ padding: 4px 10px;
1363
+ font-size: 12px;
1364
+ font-weight: 700;
1365
+ }
1366
+
1367
+ .macro-impact-badge.high {
1368
+ background: #fee2e2;
1369
+ color: #991b1b;
1370
+ }
1371
+
1372
+ .macro-impact-badge.medium {
1373
+ background: #fef3c7;
1374
+ color: #92400e;
1375
+ }
1376
+
1377
+ .macro-impact-badge.low {
1378
+ background: #dcfce7;
1379
+ color: #166534;
1380
+ }
1381
+
1382
+ .macro-importance-stars {
1383
+ color: #f59e0b;
1384
+ letter-spacing: 0.08em;
1385
+ font-size: 14px;
1386
+ }
1387
+
1388
+ .macro-direction {
1389
+ display: inline-block;
1390
+ font-weight: 800;
1391
+ letter-spacing: 0.06em;
1392
+ }
1393
+
1394
+ .macro-direction.up {
1395
+ color: #22c55e;
1396
+ }
1397
+
1398
+ .macro-direction.down {
1399
+ color: #ef4444;
1400
+ }
1401
+
1402
+ .macro-direction.flat {
1403
+ color: #64748b;
1404
+ }
1405
+
1406
+ .macro-link {
1407
+ color: #2359d1;
1408
+ word-break: break-all;
1409
+ }
1410
+
1094
1411
  .chat-tools {
1095
1412
  display: flex;
1096
1413
  gap: 8px;
@@ -1124,6 +1441,63 @@ button.is-loading::before {
1124
1441
  background: #f2f4f8;
1125
1442
  }
1126
1443
 
1444
+ .msg-title {
1445
+ font-weight: 700;
1446
+ margin-bottom: 6px;
1447
+ }
1448
+
1449
+ .msg.assistant-thinking {
1450
+ background: linear-gradient(135deg, #eef4ff 0%, #f8fbff 100%);
1451
+ border: 1px solid #d7e3f6;
1452
+ }
1453
+
1454
+ .thinking-body {
1455
+ display: inline-flex;
1456
+ align-items: center;
1457
+ gap: 10px;
1458
+ }
1459
+
1460
+ .thinking-text {
1461
+ color: #294267;
1462
+ font-weight: 600;
1463
+ }
1464
+
1465
+ .thinking-dots {
1466
+ display: inline-flex;
1467
+ gap: 4px;
1468
+ align-items: center;
1469
+ }
1470
+
1471
+ .thinking-dots span {
1472
+ width: 8px;
1473
+ height: 8px;
1474
+ border-radius: 50%;
1475
+ background: #2359d1;
1476
+ opacity: 0.35;
1477
+ animation: thinking-bounce 1.1s infinite ease-in-out;
1478
+ }
1479
+
1480
+ .thinking-dots span:nth-child(2) {
1481
+ animation-delay: 0.15s;
1482
+ }
1483
+
1484
+ .thinking-dots span:nth-child(3) {
1485
+ animation-delay: 0.3s;
1486
+ }
1487
+
1488
+ @keyframes thinking-bounce {
1489
+ 0%,
1490
+ 80%,
1491
+ 100% {
1492
+ transform: translateY(0);
1493
+ opacity: 0.35;
1494
+ }
1495
+ 40% {
1496
+ transform: translateY(-4px);
1497
+ opacity: 1;
1498
+ }
1499
+ }
1500
+
1127
1501
  .msg.line-user {
1128
1502
  background: #e6f7ee;
1129
1503
  }
@@ -1266,4 +1640,27 @@ button.is-loading::before {
1266
1640
  .browser-skill-shell {
1267
1641
  grid-template-columns: 1fr;
1268
1642
  }
1643
+
1644
+ .gateway-skill-grid {
1645
+ grid-template-columns: 1fr;
1646
+ }
1647
+
1648
+ .macro-status-strip,
1649
+ .macro-two-col {
1650
+ grid-template-columns: 1fr;
1651
+ }
1652
+
1653
+ .macro-status-main {
1654
+ align-items: flex-start;
1655
+ flex-direction: column;
1656
+ gap: 8px;
1657
+ }
1658
+
1659
+ .macro-chip-grid {
1660
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1661
+ }
1662
+
1663
+ .macro-treemap {
1664
+ height: 220px;
1665
+ }
1269
1666
  }
@@ -1 +1 @@
1
- import e from"node:fs/promises";import{existsSync as t}from"node:fs";import o from"node:os";import n from"node:path";import{spawn as r,spawnSync as i}from"node:child_process";const s="gpt-5.4";let c=null;const a=async e=>{if(!e)return"";const t=[];for await(const o of e)t.push(Buffer.isBuffer(o)?o:Buffer.from(String(o)));return Buffer.concat(t).toString("utf8")},d=async({args:e,stdinText:o,cwd:s})=>new Promise((d,l)=>{const u=(()=>{if(c)return c;if("win32"===process.platform){const e=[String(process.env.ProgramFiles||"C:\\Program Files"),String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)")];for(const o of e){const e=n.join(o,"nodejs","node.exe"),r=n.join(o,"nodejs","node_modules","@openai","codex","bin","codex.js");if(t(e)&&t(r))return c={command:e,baseArgs:[r]},c}const o=["codex.cmd","codex"];for(const e of o){const t=i("where.exe",[e],{windowsHide:!0,encoding:"utf8"}),o=String(t.stdout||"").split(/\r?\n/).map(e=>e.trim()).find(Boolean);if(o)return c={command:o,baseArgs:[]},c}}return c={command:"codex",baseArgs:[]},c})(),g={...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"},m=r(u.command,[...u.baseArgs,...e],{cwd:s,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:g});let p=!1;const f=e=>{p||(p=!0,e())},w=a(m.stdout),x=a(m.stderr);m.on("error",e=>{f(()=>l(e))}),m.on("close",async e=>{const[t,o]=await Promise.all([w,x]);f(()=>d({stdout:t,stderr:o,exitCode:Number.isFinite(e)?Number(e):1}))}),"string"==typeof o&&m.stdin.write(o),m.stdin.end()});export const createCodexService=({getModel:t,getWorkingDir:i,emitLog:c})=>({runPrompt:async({prompt:r,model:a,contextLabel:l})=>{const u=String(a||t()||s).trim()||s,g=i(),m=await e.mkdtemp(n.join(o.tmpdir(),"weget-codex-")),p=n.join(m,"last-message.txt"),f=`${String(r||"").trim()}\n\nRespond as plain text only.`;c("[codex] exec start",{model:u,cwd:g,context:String(l||"").trim()});try{const t=await d({cwd:g,args:["exec","-C",g,"--skip-git-repo-check","-m",u,"-o",p,"-"],stdinText:f}),o=await e.readFile(p,"utf8").catch(()=>"");if(0!==t.exitCode)throw new Error((t.stderr||t.stdout||`codex exec failed with code ${t.exitCode}`).trim());const n=String(o||t.stdout||"").trim();if(!n)throw new Error("codex returned empty output");return c("[codex] exec completed",{model:u,context:String(l||"").trim()}),n}finally{await e.rm(m,{recursive:!0,force:!0}).catch(()=>{})}},getAuthStatus:async()=>{try{const e=await d({cwd:i(),args:["login","status"]}),t=String(e.stdout||e.stderr||"").trim();return 0!==e.exitCode?{status:"unknown",detail:t||"codex login status failed"}:/logged in/i.test(t)?{status:"logged_in",detail:t}:/not logged in|logged out/i.test(t)?{status:"logged_out",detail:t}:{status:"unknown",detail:t||"unknown codex auth state"}}catch(e){return{status:"unknown",detail:e instanceof Error?e.message:String(e)}}},startLogin:async()=>{const t=i();try{if("win32"===process.platform){const i=String(t||"").replace(/'/g,"''"),s=n.join(o.tmpdir(),`weget-codex-login-${Date.now()}.ps1`),c=["$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)","chcp 65001 > $null",'$env:NO_COLOR = "1"','$env:FORCE_COLOR = "0"','$env:CLICOLOR = "0"','$env:LANG = "en_US.UTF-8"','$env:LC_ALL = "en_US.UTF-8"',`Set-Location -LiteralPath '${i}'`,"$stripAnsi = {"," param([string]$line)",' if ($null -eq $line) { return "" }',' return ($line -replace "`e\\[[0-9;?]*[ -/]*[@-~]", "")',"}","codex login --device-auth 2>&1 | ForEach-Object { & $stripAnsi ([string]$_) }"].join(`${o.EOL}`);await e.writeFile(s,c,"utf8");const a=["$script = @()",'$script += "-NoExit"','$script += "-ExecutionPolicy"','$script += "Bypass"','$script += "-File"',`$script += '${s.replace(/'/g,"''")}'`,`Start-Process -FilePath 'powershell.exe' -WorkingDirectory '${i}' -ArgumentList $script`].join("; ");return r("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",a],{cwd:t,stdio:"ignore",windowsHide:!0}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in a new PowerShell window with UTF-8 output."}}if("darwin"===process.platform){const e=`cd ${JSON.stringify(t)} && codex login --device-auth`;return r("open",["-a","Terminal",e],{cwd:t,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in Terminal."}}const i=`cd ${JSON.stringify(t)} && codex login --device-auth; exec $SHELL`,s=[["x-terminal-emulator",["-e","bash","-lc",i]],["gnome-terminal",["--","bash","-lc",i]],["konsole",["-e","bash","-lc",i]]];for(const[e,o]of s)try{return r(e,o,{cwd:t,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:`Started \`codex login --device-auth\` using ${e}.`}}catch{}return{ok:!1,detail:"No supported terminal launcher was found. Run `codex login --device-auth` manually."}}catch(e){const t=e instanceof Error?e.message:String(e);return c("[codex] login launch failed",{error:t}),{ok:!1,detail:t}}}});
1
+ import t from"node:fs/promises";import{existsSync as e}from"node:fs";import{createRequire as r}from"node:module";import s from"node:os";import o from"node:path";import{spawn as a,spawnSync as n}from"node:child_process";const i="gpt-5.4",c=r(import.meta.url);let d=null,l=null,g=null,u=null;const m=async t=>{if(!t)return"";const e=[];for await(const r of t)e.push(Buffer.isBuffer(r)?r:Buffer.from(String(r)));return Buffer.concat(e).toString("utf8")},w=t=>String(t||"").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,""),p=t=>{let e="";return{push:r=>{if(!t)return;e+=w(Buffer.isBuffer(r)?r.toString("utf8"):String(r));const s=e.split(/\r?\n/);e=s.pop()||"";for(const e of s){const r=String(e||"").trim();r&&t(r)}},flush:()=>{if(!t)return;const r=String(e||"").trim();r&&t(r),e=""}}},f=t=>{const e=w(String(t||"")).trim();return e?/^[{}[\],:]+$/.test(e)||/^["']?[a-zA-Z0-9_-]+["']?\s*:\s*$/.test(e)||/^`{3,}|^#{1,6}\s*$/.test(e)||/^Assistant:?\s*$/i.test(e)||/^Warning: no last agent message/i.test(e)?"":/^ERROR:\s*unexpected status\s+\d+/i.test(e)?"Tool call failed":e:""},x=(t,e)=>{const r=w(String(t||"")).replace(/\s+/g," ").trim();return r?r.length>180?`${r.slice(0,177)}...`:r:e},h=e=>{const r=(new Date).toISOString(),s=`exec-${Date.now()}`;let a="",n="",i=!1,c=Promise.resolve();const d=[],l=async()=>{if(i)return n;const r=o.join(e,"codex-sessions");await t.mkdir(r,{recursive:!0});const c=String(a||s||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_");return n=o.join(r,`${c}.log`),i=!0,d.length>0&&(await t.appendFile(n,d.join(""),"utf8"),d.length=0),n},g=(e,r={})=>{(e=>{c=c.then(async()=>{if(!i&&!a)return void d.push(e);const r=await l();await t.appendFile(r,e,"utf8")}).catch(()=>{})})(`${JSON.stringify({ts:(new Date).toISOString(),event:e,...r},null,0)}\n`)};return{get startedAt(){return r},setSessionId:t=>{const e=String(t||"").trim();e&&a!==e&&(a=e,g("session_identified",{sessionId:e}))},write:g,flush:async()=>(i||await l(),await c,n)}},y=()=>{if(l)return l;const t=[o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","npx.cmd"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","npx.cmd"),o.join(String(process.env.APPDATA||""),"npm","npx.cmd")].filter(Boolean);for(const r of t)if(e(r))return l=r,r;const r=n("where.exe",["npx.cmd"],{windowsHide:!0,encoding:"utf8"}),s=String(r.stdout||"").split(/\r?\n/).map(t=>t.trim()).find(Boolean);return s?(l=s,s):"npx.cmd"},S=()=>{if(g)return g;const t=[o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","node.exe"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","node.exe")];for(const r of t)if(e(r))return g=r,r;return g="node",g},C=()=>{if(u)return u;try{const t=c.resolve("@ai.weget.jp/weget-gateway-mcp/dist/index.js");return u=t,t}catch{}const t=[o.join(String(process.env.APPDATA||""),"npm","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js")].filter(Boolean);for(const r of t)if(e(r))return u=r,r;return u=o.join(String(process.env.APPDATA||""),"npm","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),u},_=t=>{const e=o.join(t,"weget-gateway-context.json");return"win32"===process.platform?{command:S(),args:[C(),"--context-file",e]}:{command:"npx",args:["-y","@ai.weget.jp/weget-gateway-mcp@latest","--context-file",e]}},v=t=>String(t||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),P=(t,e)=>{const r=String(t||""),s=_(e);if("win32"===process.platform){const t=new RegExp(`command:\\s+${v(s.command)}`,"i"),e=new RegExp(v(String(s.args[0]||"")),"i"),o=new RegExp(v(String(s.args[2]||"")),"i");return t.test(r)&&e.test(r)&&o.test(r)}return new RegExp(`command:\\s+${v(s.command)}`,"i").test(r)&&r.includes(String(s.args[0]||""))},k=async({args:t,stdinText:r,cwd:s,extraEnv:i,onStdoutLine:c,onStderrLine:l})=>new Promise((g,u)=>{const w=(()=>{if(d)return d;if("win32"===process.platform){const t=[String(process.env.ProgramFiles||"C:\\Program Files"),String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)")];for(const r of t){const t=o.join(r,"nodejs","node.exe"),s=o.join(r,"nodejs","node_modules","@openai","codex","bin","codex.js");if(e(t)&&e(s))return d={command:t,baseArgs:[s]},d}const r=["codex.cmd","codex"];for(const t of r){const e=n("where.exe",[t],{windowsHide:!0,encoding:"utf8"}),r=String(e.stdout||"").split(/\r?\n/).map(t=>t.trim()).find(Boolean);if(r)return d={command:r,baseArgs:[]},d}}return d={command:"codex",baseArgs:[]},d})(),f={...process.env,...i||{},NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"},x=a(w.command,[...w.baseArgs,...t],{cwd:s,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:f});let h=!1;const y=t=>{h||(h=!0,t())},S=m(x.stdout),C=m(x.stderr),_=p(c),v=p(l);x.stdout?.on("data",t=>_.push(t)),x.stderr?.on("data",t=>v.push(t)),x.on("error",t=>{y(()=>u(t))}),x.on("close",async t=>{_.flush(),v.flush();const[e,r]=await Promise.all([S,C]);y(()=>g({stdout:e,stderr:r,exitCode:Number.isFinite(t)?Number(t):1}))}),"string"==typeof r&&x.stdin.write(r),x.stdin.end()}),$=async({command:t,args:e,cwd:r})=>new Promise((s,o)=>{const n=a(t,e,{cwd:r,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:{...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"}});let i=!1;const c=t=>{i||(i=!0,t())},d=m(n.stdout),l=m(n.stderr);n.on("error",t=>c(()=>o(t))),n.on("close",async t=>{const[e,r]=await Promise.all([d,l]);c(()=>s({stdout:e,stderr:r,exitCode:Number.isFinite(t)?Number(t):1}))})});export const createCodexService=({getModel:e,getWorkingDir:r,getLogDir:n,emitLog:c,getGatewayContext:d})=>{const l=()=>{const t=d?.()||{},e={WEGET_ACTIVE_SKILLS:JSON.stringify(Array.isArray(t.activeSkills)?t.activeSkills:[]),WEGET_HOST_METADATA:JSON.stringify(t.hostMetadata&&"object"==typeof t.hostMetadata?t.hostMetadata:{})},r=String(t.botId||"").trim(),s=String(t.platformApiBaseUrl||"").trim(),o=String(t.accessToken||"").trim(),a=String(t.fileBridgeDir||"").trim(),n=String(t.fileBridgeToken||"").trim();return r&&(e.WEGET_BOT_ID=r),s&&(e.WEGET_PLATFORM_API_BASE_URL=s),o&&(e.WEGET_PLATFORM_API_ACCESS_TOKEN=o),a&&(e.WEGET_FILE_BRIDGE_DIR=a),n&&(e.WEGET_FILE_BRIDGE_TOKEN=n),e},g=()=>o.join(r(),"weget-gateway-context.json"),u=async()=>{const e=d?.()||{};await t.mkdir(r(),{recursive:!0}),await t.writeFile(g(),JSON.stringify({activeSkills:Array.isArray(e.activeSkills)?e.activeSkills:[],hostMetadata:e.hostMetadata&&"object"==typeof e.hostMetadata?e.hostMetadata:{},botId:String(e.botId||"").trim(),platformApiBaseUrl:String(e.platformApiBaseUrl||"").trim(),accessToken:String(e.accessToken||"").trim(),fileBridgeDir:String(e.fileBridgeDir||"").trim(),fileBridgeToken:String(e.fileBridgeToken||"").trim(),logDir:n(),updatedAt:(new Date).toISOString()},null,2),"utf8")},m=async({target:e,payload:r})=>{const s=o.join(n(),"gateway-tests");await t.mkdir(s,{recursive:!0});const a=o.join(s,`${e}-${Date.now()}.log`);return await t.writeFile(a,`${JSON.stringify(r,null,2)}\n`,"utf8"),a},w=async({userPrompt:t,contextLabel:r})=>{const s=d?.()||{},o=Array.isArray(s.activeSkills)?s.activeSkills:[],a=s.hostMetadata&&"object"==typeof s.hostMetadata?s.hostMetadata:{},[n,c]=await Promise.all([p("weget-gateway"),S()]);return["You are running inside WeGet Bot Host.","","Active skills:",...o.length?o.map(t=>{const e=Array.isArray(t.tools)&&t.tools.length?` tools=${t.tools.join(", ")}`:"",r=String(t.version||"").trim()?` v${String(t.version||"").trim()}`:"";return`- ${String(t.name||"").trim()}${r}${e}`}):["- none"],"","Runtime status:",...[`- weget-gateway MCP: ${n.status}`,`- browser runtime: ${c.status}`,`- context: ${String(r||"").trim()||"general"}`,`- default model: ${String(a.aiModel||e()||i).trim()||i}`],"","Tool policy:","- Prefer direct reasoning for simple text-only requests.","- When the user asks about macro conditions, economic situation, calendar, or news, prefer the WeGet macro tools before summarizing.","- Use WeGet browser tools only for website access, page extraction, navigation, or screenshots.","- If browser runtime or gateway MCP is unavailable, state that limitation explicitly instead of pretending the task was executed.","- For trading or any action with side effects, do not claim execution unless a concrete tool call actually performed it.","- Keep the final answer plain text only.",...(t=>{const e=String(t||"").trim().toLowerCase();return!!e&&[/macro/,/economic/,/economy/,/calendar/,/news/,/usd\s*\/\s*jpy/,/usd_jpy/,/risk sentiment/,/economic situation/,/macro snapshot/,/经济/,/宏观/,/日历/,/新闻/,/形势/,/経済/,/マクロ/,/ニュース/,/カレンダー/,/相場/].some(t=>t.test(e))})(t)?["","Macro tool routing hint:","- This request matches macro intent.","- Before answering, use the WeGet macro tools to fetch snapshot, calendar, and news if available.","- Prefer gateway tool calls such as weget_macro_get_snapshot, weget_macro_list_calendar, and weget_macro_list_news.","- Base the summary on fetched tool results rather than unsupported memory."]:[],"","User request:",String(t||"").trim(),"","Respond as plain text only."].join("\n")},p=async t=>{const e=String(t||"").trim();if(!e)return{status:"unknown",detail:"MCP server name is required"};try{"weget-gateway"===e&&await u().catch(()=>{});const t=await k({cwd:r(),args:["mcp","get",e],extraEnv:l()}),s=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?/No MCP server named/i.test(s)?{status:"missing",detail:s}:{status:"unknown",detail:s||"codex mcp list failed"}:"playwright"!==e||(t=>{const e=String(t||"");return"win32"===process.platform?/command:\s+.*node(?:\.exe)?/i.test(e)&&/@playwright[\\\/]mcp[\\\/]cli\.js/i.test(e)&&/args:\s+.*--headless/i.test(e):/command:\s+npx/i.test(e)&&/@playwright\/mcp@latest/i.test(e)&&/args:\s+.*--headless/i.test(e)})(s)?"weget-gateway"!==e||P(s,r())?{status:"configured",detail:s||`${e} is configured`}:{status:"missing",detail:"WeGet Gateway MCP exists but points to an outdated launch path. Reinstall it from the host UI."}:{status:"missing",detail:"Playwright MCP exists but uses an unsupported Windows wrapper. Click Install Playwright MCP to repair it."}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},S=async()=>{try{const t="win32"===process.platform?"powershell.exe":"npx",e="win32"===process.platform?["-NoProfile","-Command",`& '${y().replace(/'/g,"''")}' playwright install --list`]:["playwright","install","--list"],s=await $({command:t,args:e,cwd:r()}),o=String(s.stdout||s.stderr||"").trim();return 0!==s.exitCode?{status:"unknown",detail:o||"playwright install --list failed"}:/chromium-\d+/i.test(o)||/\\ms-playwright\\chromium-/i.test(o)?{status:"installed",detail:o}:{status:"missing",detail:o||"Chromium is not installed for Playwright."}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}};return{runPrompt:async({prompt:a,model:d,contextLabel:m,onStatus:p})=>{const x=String(d||e()||i).trim()||i,y=r();await u().catch(()=>{});const S=h(n()),C=await t.mkdtemp(o.join(s.tmpdir(),"weget-codex-")),_=o.join(C,"last-message.txt"),v=await w({userPrompt:String(a||"").trim(),contextLabel:m});S.write("exec_start",{model:x,cwd:y,context:String(m||"").trim(),prompt:String(a||"").trim(),instruction:v,outputPath:_,tempDir:C,gatewayContextFile:g(),traceStartedAt:S.startedAt}),p?.("Preparing request"),c("[codex] exec start",{model:x,cwd:y,context:String(m||"").trim()});try{const e=await k({cwd:y,extraEnv:l(),args:["exec","--enable","rmcp_client","--dangerously-bypass-approvals-and-sandbox","-C",y,"--skip-git-repo-check","-m",x,"-o",_,"-"],stdinText:v,onStdoutLine:t=>{S.write("stdout_line",{line:t});const e=f(t);if(e)return/^re-connecting/i.test(e)||/checking tools|tool use|using tool|calling tool/i.test(e)?(S.write("status",{source:"stdout",status:e}),void p?.(e)):void(/reading data|fetching|loading|snapshot|calendar|news/i.test(e)&&(S.write("status",{source:"stdout",status:e}),p?.(e)))},onStderrLine:t=>{S.write("stderr_line",{line:t});const e=String(t||"").match(/session id:\s*([a-zA-Z0-9-]+)/i);e?.[1]&&S.setSessionId(e[1]);const r=f(t);if(r&&!/^--------$/.test(r)&&!/^user$/i.test(r))return/^OpenAI Codex/i.test(r)?(S.write("status",{source:"stderr",status:"Starting Codex"}),void p?.("Starting Codex")):void(/workdir:|model:|provider:|approval:|sandbox:|reasoning effort:|reasoning summaries:|session id:/i.test(r)||(S.write("status",{source:"stderr",status:r}),p?.(r)))}});S.write("status",{source:"host",status:"Finalizing reply"}),p?.("Finalizing reply");const r=await t.readFile(_,"utf8").catch(()=>"");if(0!==e.exitCode)throw S.write("exec_error",{exitCode:e.exitCode,stdout:e.stdout,stderr:e.stderr,lastMessage:r}),new Error((e.stderr||e.stdout||`codex exec failed with code ${e.exitCode}`).trim());const s=String(r||e.stdout||"").trim();if(!s)throw S.write("exec_error",{exitCode:e.exitCode,stdout:e.stdout,stderr:e.stderr,lastMessage:r,error:"codex returned empty output"}),new Error("codex returned empty output");return S.write("exec_completed",{exitCode:e.exitCode,stdout:e.stdout,stderr:e.stderr,lastMessage:r,finalAnswer:s}),c("[codex] exec completed",{model:x,context:String(m||"").trim()}),s}catch(t){throw S.write("exec_exception",{error:t instanceof Error?t.message:String(t)}),t}finally{const e=await S.flush().catch(()=>"");e&&c("[codex] session trace written",{context:String(m||"").trim(),tracePath:e}),await t.rm(C,{recursive:!0,force:!0}).catch(()=>{})}},getAuthStatus:async()=>{try{const t=await k({cwd:r(),args:["login","status"]}),e=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?{status:"unknown",detail:e||"codex login status failed"}:/logged in/i.test(e)?{status:"logged_in",detail:e}:/not logged in|logged out/i.test(e)?{status:"logged_out",detail:e}:{status:"unknown",detail:e||"unknown codex auth state"}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},startLogin:async()=>{const e=r();try{if("win32"===process.platform){const r=String(e||"").replace(/'/g,"''"),n=o.join(s.tmpdir(),`weget-codex-login-${Date.now()}.ps1`),i=["$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)","chcp 65001 > $null",'$env:NO_COLOR = "1"','$env:FORCE_COLOR = "0"','$env:CLICOLOR = "0"','$env:LANG = "en_US.UTF-8"','$env:LC_ALL = "en_US.UTF-8"',`Set-Location -LiteralPath '${r}'`,"$stripAnsi = {"," param([string]$line)",' if ($null -eq $line) { return "" }',' return ($line -replace "`e\\[[0-9;?]*[ -/]*[@-~]", "")',"}","codex login --device-auth 2>&1 | ForEach-Object { & $stripAnsi ([string]$_) }"].join(`${s.EOL}`);await t.writeFile(n,i,"utf8");const c=["$script = @()",'$script += "-NoExit"','$script += "-ExecutionPolicy"','$script += "Bypass"','$script += "-File"',`$script += '${n.replace(/'/g,"''")}'`,`Start-Process -FilePath 'powershell.exe' -WorkingDirectory '${r}' -ArgumentList $script`].join("; ");return a("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",c],{cwd:e,stdio:"ignore",windowsHide:!0}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in a new PowerShell window with UTF-8 output."}}if("darwin"===process.platform){const t=`cd ${JSON.stringify(e)} && codex login --device-auth`;return a("open",["-a","Terminal",t],{cwd:e,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in Terminal."}}const r=`cd ${JSON.stringify(e)} && codex login --device-auth; exec $SHELL`,n=[["x-terminal-emulator",["-e","bash","-lc",r]],["gnome-terminal",["--","bash","-lc",r]],["konsole",["-e","bash","-lc",r]]];for(const[t,r]of n)try{return a(t,r,{cwd:e,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:`Started \`codex login --device-auth\` using ${t}.`}}catch{}return{ok:!1,detail:"No supported terminal launcher was found. Run `codex login --device-auth` manually."}}catch(t){const e=t instanceof Error?t.message:String(t);return c("[codex] login launch failed",{error:e}),{ok:!1,detail:e}}},getMcpServerStatus:p,installWegetGatewayMcp:async()=>{try{await u();const t=await p("weget-gateway");if("configured"===t.status&&P(t.detail,r()))return{ok:!0,detail:t.detail||"WeGet Gateway MCP is already configured."};const e=await k({cwd:r(),args:["mcp","remove","weget-gateway"],extraEnv:l()});if(0!==e.exitCode){const t=String(e.stdout||e.stderr||"").trim().toLowerCase();if(!(t.includes("no mcp server named")||t.includes("not found")||t.includes("no server named")))return{ok:!1,detail:String(e.stdout||e.stderr||"").trim()||"Failed to remove existing WeGet Gateway MCP entry."}}const s=_(r()),o=await k({cwd:r(),args:["mcp","add","weget-gateway",s.command,...s.args],extraEnv:l()}),a=String(o.stdout||o.stderr||"").trim();if(0!==o.exitCode)return{ok:!1,detail:a||"Failed to add WeGet Gateway MCP to Codex."};const n=await p("weget-gateway");return"configured"===n.status?{ok:!0,detail:n.detail||"WeGet Gateway MCP configured."}:{ok:!0,detail:a||"WeGet Gateway MCP add command completed, but verification returned no explicit entry."}}catch(t){const e=t instanceof Error?t.message:String(t);return c("[codex] weget gateway mcp install failed",{error:e}),{ok:!1,detail:e}}},getPlaywrightBrowserStatus:S,getGatewayContextFilePath:g,runGatewaySelfTest:async e=>{if("codex_macro"===e){const a=(new Date).toISOString().slice(0,10);await u().catch(()=>{});const c=h(n()),d=await t.mkdtemp(o.join(s.tmpdir(),"weget-codex-test-")),w=o.join(d,"last-message.txt"),p=["You are running a WeGet gateway chain self-test.","Use WeGet gateway MCP tools only.",`Call weget_macro_get_snapshot, weget_macro_list_calendar with dateKey=${a}, and weget_macro_list_news with dateKey=${a}.`,"If all three tool calls succeed, reply with exactly: OK macro chain","If any tool call fails, reply with exactly: NG macro chain: <short reason>","Respond as plain text only."].join("\n");c.write("exec_start",{model:i,cwd:r(),context:"gateway_self_test:codex_macro",prompt:"codex macro chain self-test",instruction:p,outputPath:w,tempDir:d,gatewayContextFile:g(),traceStartedAt:c.startedAt});try{const s=await k({cwd:r(),extraEnv:l(),args:["exec","--enable","rmcp_client","--dangerously-bypass-approvals-and-sandbox","-C",r(),"--skip-git-repo-check","-m",i,"-o",w,"-"],stdinText:p,onStdoutLine:t=>{c.write("stdout_line",{line:t})},onStderrLine:t=>{c.write("stderr_line",{line:t});const e=String(t||"").match(/session id:\s*([a-zA-Z0-9-]+)/i);e?.[1]&&c.setSessionId(e[1])}}),o=await t.readFile(w,"utf8").catch(()=>""),n=String(o||s.stdout||"").trim(),u=String(s.stderr||""),f=String(s.stdout||""),h=/tool weget-gateway\.weget_macro_get_snapshot\(\{\}\)/i.test(u),y=new RegExp(`tool weget-gateway\\.weget_macro_list_calendar\\(\\{"dateKey":"${v(a)}"\\}\\)`,"i").test(u),S=new RegExp(`tool weget-gateway\\.weget_macro_list_news\\(\\{"dateKey":"${v(a)}"\\}\\)`,"i").test(u),C=/weget-gateway\.weget_macro_get_snapshot\(\{\}\) success/i.test(u),_=/weget-gateway\.weget_macro_list_calendar\(\{\"dateKey\":\"\d{4}-\d{2}-\d{2}\"\}\) success/i.test(u),P=/weget-gateway\.weget_macro_list_news\(\{\"dateKey\":\"\d{4}-\d{2}-\d{2}\"\}\) success/i.test(u),$=0===s.exitCode&&/^OK macro chain$/i.test(n)&&h&&y&&S&&C&&_&&P;c.write($?"exec_completed":"exec_error",{exitCode:s.exitCode,stdout:f,stderr:u,lastMessage:o,finalAnswer:n,invokedSnapshot:h,invokedCalendar:y,invokedNews:S,succeededSnapshot:C,succeededCalendar:_,succeededNews:P});const O=await c.flush().catch(()=>""),E={target:e,ok:$,summary:$?"Codex gateway macro chain OK":"Codex gateway macro chain failed",contextFilePath:g(),tracePath:O,codex:{exitCode:s.exitCode,stdout:f,stderr:u,finalAnswer:n},assertions:{invokedSnapshot:h,invokedCalendar:y,invokedNews:S,succeededSnapshot:C,succeededCalendar:_,succeededNews:P},ts:(new Date).toISOString()},j=await m({target:e,payload:E});return await t.rm(d,{recursive:!0,force:!0}).catch(()=>{}),{ok:$,summary:$?"Codex gateway macro chain OK":x(n||u,"Codex gateway macro chain failed"),detail:n||u||f,logPath:j,result:E}}catch(r){c.write("exec_exception",{error:r instanceof Error?r.message:String(r)});const s=await c.flush().catch(()=>""),o=r instanceof Error?r.message:String(r),a=await m({target:e,payload:{target:e,ok:!1,summary:"Codex gateway macro chain failed",contextFilePath:g(),tracePath:s,error:o,ts:(new Date).toISOString()}});return await t.rm(d,{recursive:!0,force:!0}).catch(()=>{}),{ok:!1,summary:x(o,"Codex gateway macro chain failed"),detail:o,logPath:a}}}await u();const c=_(r());let d,w=null;try{d=await $({command:c.command,args:[...c.args,"--self-test","--test-target",e],cwd:r()}),"gateway"===e&&(w=await(async({command:t,args:e,cwd:r,timeoutMs:s=2500})=>new Promise((o,n)=>{const i=a(t,e,{cwd:r,stdio:["ignore","pipe","pipe"],windowsHide:!0,env:{...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"}}),c=[],d=[];let l=!1;const g=t=>{l||(l=!0,o(t))};i.stdout.on("data",t=>c.push(Buffer.from(t))),i.stderr.on("data",t=>d.push(Buffer.from(t))),i.on("error",t=>{l||(l=!0,n(t))}),i.on("close",t=>{g({ok:!1,exitCode:Number.isFinite(t)?Number(t):1,stdout:Buffer.concat(c).toString("utf8"),stderr:Buffer.concat(d).toString("utf8"),responseText:""})}),setTimeout(()=>{const t=i.stdin;t?.write(`${JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"weget-gateway-probe",version:"0.1.0"}}})}\n`)},150),setTimeout(()=>{if(l)return;const t=Buffer.concat(c).toString("utf8"),e=Buffer.concat(d).toString("utf8"),r=t.trim(),s=/"result"\s*:/.test(r)&&/"protocolVersion"\s*:/.test(r);i.kill(),g({ok:s,exitCode:null,stdout:t,stderr:e,responseText:r})},Math.max(250,s))}))({command:c.command,args:c.args,cwd:r()}))}catch(t){const r=t instanceof Error?t.message:String(t),s=await m({target:e,payload:{target:e,launch:c,contextFilePath:g(),failure:"spawn_exception",error:r,ts:(new Date).toISOString()}});return{ok:!1,summary:x(r,`${e} test failed`),detail:r,logPath:s}}const p=String(d.stdout||d.stderr||"").trim();let f;if(p)try{f=JSON.parse(p)}catch{}let y=0===d.exitCode&&Boolean(!f||!1!==f.ok),S=x(p,`${e} self-test completed`);"gateway"===e&&w&&!w.ok?(y=!1,S=x(w.stderr||w.stdout,"Gateway handshake probe failed.")):"gateway"===e&&w?.ok&&(S="Gateway MCP handshake OK");const C={target:e,ok:y,summary:S,launch:c,contextFilePath:g(),selfTest:{exitCode:d.exitCode,stdout:d.stdout,stderr:d.stderr,result:f??null},handshakeProbe:w?{ok:w.ok,exitCode:w.exitCode,stdout:w.stdout,stderr:w.stderr,responseText:w.responseText}:null,ts:(new Date).toISOString()};return{ok:y,summary:S,detail:p||S,logPath:await m({target:e,payload:C}),result:f}}}};
@@ -0,0 +1 @@
1
+ import t from"node:crypto";import r from"node:fs/promises";import e from"node:path";export const createGatewayFileBridgeService=({getMacroSnapshot:a,getMacroCalendar:i,getMacroNews:o,emitLog:n,getWorkingDir:s})=>{let c=null,d=null,l=!1;const w=(t,r)=>n?.(`[gateway-file-bridge] ${t}`,r),f=()=>e.join(s(),"weget-gateway-file-bridge"),y=()=>e.join(f(),"requests"),u=()=>e.join(f(),"responses"),m=async(t,a)=>{const i=e.join(u(),`${t}.json`);await r.writeFile(i,`${JSON.stringify(a,null,2)}\n`,"utf8")},g=async t=>{const e=await r.readFile(t,"utf8"),n=JSON.parse(e),s=String(n.id||"").trim(),d=String(n.token||"").trim();if(s)if(c&&d===c.token)try{if("macro_snapshot"===n.action){const t=await a({force:Boolean(n.payload?.force)});return void await m(s,{ok:!0,snapshot:t})}if("macro_calendar"===n.action){const t=String(n.payload?.dateKey||"").trim();if(!t)throw new Error("dateKey is required");const r=await i({dateKey:t});return void await m(s,{ok:!0,rows:r})}if("macro_news"===n.action){const t=String(n.payload?.dateKey||"").trim();if(!t)throw new Error("dateKey is required");const r=await o({dateKey:t});return void await m(s,{ok:!0,rows:r})}await m(s,{ok:!1,error:`unsupported action: ${n.action}`})}catch(t){await m(s,{ok:!1,error:t instanceof Error?t.message:String(t)})}finally{await r.unlink(t).catch(()=>{})}else await m(s,{ok:!1,error:"unauthorized"})};return{ensureStarted:async()=>c||(await(async()=>{await r.mkdir(y(),{recursive:!0}),await r.mkdir(u(),{recursive:!0})})(),c={dir:f(),token:t.randomUUID()},d=setInterval(()=>{(async()=>{if(!l){l=!0;try{const t=(await r.readdir(y()).catch(()=>[])).filter(t=>t.endsWith(".json")).slice(0,8);for(const r of t)await g(e.join(y(),r))}finally{l=!1}}})()},100),w("started",{dir:c.dir}),c),getContext:()=>c,stop:async()=>{var t;d&&clearInterval(d),d=null,c=null,await(t=0,new Promise(r=>setTimeout(r,t))),w("stopped")}}};
@@ -1 +1 @@
1
- import e from"node:path";import a from"node:fs/promises";import*as t from"@ai.weget.jp/skill-browser";import*as i from"@ai.weget.jp/skill-gmo-coin";import*as n from"@ai.weget.jp/skill-gmo-fx";const r=async e=>{await a.mkdir(e,{recursive:!0})},l=[t,i,n];export const createSkillRuntimeManager=({installRoot:a,botId:t,log:i,api:n,getRuntimeConfig:s,emitSkillEvent:o})=>{let m=new Map;const c=async()=>{await r(a),await r(e.join(a,"data"))},g=t=>{const i=String(t||"").trim().replace(/^@/,"").replace(/[\/\\]/g,"__");return e.join(a,"data",`${i}.json`)},p=t=>{const i=String(t||"").trim().replace(/^@/,"").replace(/[\/\\]/g,"__");return e.join(a,"data",i)},u=async e=>{const a=e.default||e.skill||null;if(!a?.manifest?.name)return null;const l=String(a.manifest.name||"").trim();await r(p(l));const m={packageName:l,packageVersion:String(a.manifest.version||""),botId:t?.(),log:i,api:n,skillDataDir:p(l),configPath:g(l),runtimeConfig:s?.()||{},emitEvent:e=>o?.(l,e)},c="function"==typeof e.createRuntime?e.createRuntime:a.createRuntime,u=c?c(m):{};return{packageName:l,packageDir:"",version:String(a.manifest.version||""),manifest:a.manifest,ui:e.hostUi||a.hostUi||void 0,module:a,runtime:u}},k=async()=>{await c();const e=new Map;for(const a of l){let t=null;try{t=await u(a)}catch(e){i?.("[skills] failed to load bundled skill",{error:e instanceof Error?e.message:String(e)})}t&&e.set(t.packageName,t)}return m=e,Array.from(m.values())};return{ensureInstallRoot:c,installSkill:async({packageName:e,version:a})=>(i?.("[skills] install skipped for bundled skill",{packageName:e,version:a||null}),k()),uninstallSkill:async({packageName:e})=>(i?.("[skills] uninstall skipped for bundled skill",{packageName:e}),k()),refreshLoadedSkills:k,getLoadedSkill:e=>m.get(String(e||"").trim())||null,getLoadedSkills:()=>Array.from(m.values())}};
1
+ import e from"node:path";import a from"node:fs/promises";import*as t from"@ai.weget.jp/skill-browser";import*as i from"@ai.weget.jp/skill-gmo-coin";import*as n from"@ai.weget.jp/skill-gmo-fx";import*as r from"@ai.weget.jp/skill-macro-economy";const l=async e=>{await a.mkdir(e,{recursive:!0})},o=[t,i,n,r];export const createSkillRuntimeManager=({installRoot:a,botId:t,log:i,api:n,getRuntimeConfig:r,emitSkillEvent:s})=>{let m=new Map;const c=async()=>{await l(a),await l(e.join(a,"data"))},g=t=>{const i=String(t||"").trim().replace(/^@/,"").replace(/[\/\\]/g,"__");return e.join(a,"data",`${i}.json`)},p=t=>{const i=String(t||"").trim().replace(/^@/,"").replace(/[\/\\]/g,"__");return e.join(a,"data",i)},k=async e=>{const a=e.default||e.skill||null;if(!a?.manifest?.name)return null;const o=String(a.manifest.name||"").trim();await l(p(o));const m={packageName:o,packageVersion:String(a.manifest.version||""),botId:t?.(),log:i,api:n,skillDataDir:p(o),configPath:g(o),runtimeConfig:r?.()||{},emitEvent:e=>s?.(o,e)},c="function"==typeof e.createRuntime?e.createRuntime:a.createRuntime,k=c?c(m):{};return{packageName:o,packageDir:"",version:String(a.manifest.version||""),manifest:a.manifest,ui:e.hostUi||a.hostUi||void 0,module:a,runtime:k}},u=async()=>{await c();const e=new Map;for(const a of o){let t=null;try{t=await k(a)}catch(e){i?.("[skills] failed to load bundled skill",{error:e instanceof Error?e.message:String(e)})}t&&e.set(t.packageName,t)}return m=e,Array.from(m.values())};return{ensureInstallRoot:c,installSkill:async({packageName:e,version:a})=>(i?.("[skills] install skipped for bundled skill",{packageName:e,version:a||null}),u()),uninstallSkill:async({packageName:e})=>(i?.("[skills] uninstall skipped for bundled skill",{packageName:e}),u()),refreshLoadedSkills:u,getLoadedSkill:e=>m.get(String(e||"").trim())||null,getLoadedSkills:()=>Array.from(m.values())}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai.weget.jp/bot",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "WeGet bot host for Codex-centered skill runtime ",
@@ -28,10 +28,12 @@
28
28
  "build": "npm run build:ts"
29
29
  },
30
30
  "dependencies": {
31
- "@ai.weget.jp/skill-browser": ">=0.1.0 <1",
32
- "@ai.weget.jp/skill-gmo-coin": ">=0.1.2 <1",
33
- "@ai.weget.jp/skill-gmo-fx": ">=0.1.2 <1",
34
- "@ai.weget.jp/skill-sdk": ">=0.1.1 <1",
31
+ "@ai.weget.jp/skill-browser": ">=0.1.1 <1",
32
+ "@ai.weget.jp/skill-gmo-coin": ">=0.1.3 <1",
33
+ "@ai.weget.jp/skill-gmo-fx": ">=0.1.3 <1",
34
+ "@ai.weget.jp/skill-macro-economy": ">=0.1.1 <1",
35
+ "@ai.weget.jp/skill-sdk": ">=0.1.2 <1",
36
+ "@ai.weget.jp/weget-gateway-mcp": ">=0.1.1 <1",
35
37
  "electron": "^31.7.7",
36
38
  "ws": "^8.18.3"
37
39
  },