@ai.weget.jp/bot 0.1.11 → 0.1.12
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 +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/ipc/registerHandlers.js +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/preload.cjs +2 -4
- package/dist/src/renderer/app.js +1 -1
- package/dist/src/renderer/index.html +93 -63
- package/dist/src/renderer/styles.css +122 -4
- package/dist/src/services/authApiService.js +1 -1
- package/dist/src/services/codexService.js +1 -1
- package/dist/src/skillHostState.js +1 -1
- package/dist/src/skillRuntimeManager.js +1 -0
- package/package.json +4 -4
- package/scripts/build-ts.cjs +2 -0
- package/dist/src/gmoCoinRuntime.js +0 -1
- package/dist/src/gmoFxRuntime.js +0 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Default built-in endpoints:
|
|
|
43
43
|
- `botUploadApiUrl=https://api.weget.jp/bot/upload-url`
|
|
44
44
|
- `gmoCryptoPrivateApiBaseUrl=https://api.coin.z.com/private`
|
|
45
45
|
- `gmoFxPrivateApiBaseUrl=https://forex-api.coin.z.com/private`
|
|
46
|
-
- `aiModel=gpt-4
|
|
46
|
+
- `aiModel=gpt-5.4`
|
|
47
47
|
|
|
48
48
|
## Local UI Structure
|
|
49
49
|
|
package/dist/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./skills.js";export*from"./skillHostState.js";export*from"./platformApi.js";export
|
|
1
|
+
export*from"./skills.js";export*from"./skillHostState.js";export*from"./platformApi.js";export const createBotHostManifest=()=>({name:"@ai.weget.jp/bot",version:"0.1.12",skills:[]});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,
|
|
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)}}})};
|
package/dist/src/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:path";import t from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as o}from"node:url";import{createRequire as a}from"node:module";import{SYSTEM_CONFIG as s}from"./config/systemConfig.js";import{BotWsClient as r}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as c}from"./services/codexService.js";import{createWindowManager as g}from"./services/windowManagerService.js";import{createGmoCoinRuntime as d}from"./gmoCoinRuntime.js";import{createGmoFxRuntime as m}from"./gmoFxRuntime.js";import{createSkillHostState as p}from"./skillHostState.js";const u=a(import.meta.url),{app:f,BrowserWindow:y,ipcMain:S,nativeImage:w}=u("electron"),h=o(import.meta.url),k=e.dirname(h),x=["trade","ai"],b=()=>e.join(f.getPath("userData"),"login-profile.json"),A=()=>e.join(f.getPath("userData"),"gmo-fx-config.json"),v=()=>e.join(f.getPath("userData"),"gmo-coin-config.json"),_=()=>e.join(f.getPath("userData"),"skill-states.json");let C=null,P=null;const D=new Map;let L=()=>e.join(f.getPath("documents"),"weget-bot-logs");const E=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},O=async({level:i,source:o,message:a,details:s,ts:r})=>{const n=L();await t.mkdir(n,{recursive:!0});const l=new Date,c=l.getFullYear(),g=String(l.getMonth()+1).padStart(2,"0"),d=String(l.getDate()).padStart(2,"0"),m=e.join(n,`${i}-${c}${g}${d}.log`),p=JSON.stringify({ts:r||l.toISOString(),level:i,source:o,message:a,details:s??null},null,0);await t.appendFile(m,`${p}\n`,"utf8")},j=g({BrowserWindow:y,preloadPath:e.join(k,"preload.cjs"),rendererDir:e.join(k,"renderer"),windowIconPath:e.join(k,"renderer","logo.png")}),I=(e,t)=>{const i=(new Date).toISOString();j.emitBotLog({message:e,data:t||null,ts:i}),O({level:E(e),source:"runtime",message:e,details:t,ts:i}).catch(()=>{})},M=e=>{j.emitChatEvent(e)},B=e=>e&&"object"==typeof e?e:{},T=m({configPath:A(),emitLog:I,getRuntimeConfig:()=>({cryptoApiBaseUrl:s.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:s.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:s.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:s.gmoFxPublicApiBaseUrl,assetsPath:s.gmoAssetsPath,fxAssetsPath:s.gmoFxAssetsPath,coinMarginPath:s.gmoCoinMarginPath,positionsPath:s.gmoPositionsPath,positionSummaryPath:s.gmoPositionSummaryPath,fxOrderPath:s.gmoFxOrderPath,coinCloseOrderPath:s.gmoCoinCloseOrderPath,fxCloseOrderPath:s.gmoFxCloseOrderPath}),emitTradeExecution:e=>{j.emitTradeExecution(e)},emitTradeHistory:e=>{const t=`trade-${Date.now()}`;P?.sendTaskEvent(t,"trade_history",e),I("[trade] history event sent (execution)",e)}}),U=d({configPath:v(),emitLog:I,getRuntimeConfig:()=>({cryptoApiBaseUrl:s.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:s.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:s.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:s.gmoFxPublicApiBaseUrl,assetsPath:s.gmoAssetsPath,fxAssetsPath:s.gmoFxAssetsPath,coinMarginPath:s.gmoCoinMarginPath,positionsPath:s.gmoPositionsPath,positionSummaryPath:s.gmoPositionSummaryPath,fxOrderPath:s.gmoFxOrderPath,coinCloseOrderPath:s.gmoCoinCloseOrderPath,fxCloseOrderPath:s.gmoFxCloseOrderPath}),emitTradeExecution:e=>{j.emitTradeExecution(e)},emitTradeHistory:e=>{const t=`trade-${Date.now()}`;P?.sendTaskEvent(t,"trade_history",e),I("[trade] history event sent (execution)",e)}}),F=p({gmoFxConfigPath:A(),gmoCoinConfigPath:v(),log:I});T.loadConfig(),U.loadConfig(),async function(){try{const e=await t.readFile(_(),"utf8"),i=JSON.parse(e);return(Array.isArray(i?.items)?i.items:[]).filter(e=>Boolean(e&&"object"==typeof e)).map(e=>({user_id:String(e.user_id||""),bot_id:String(e.bot_id||""),skill_id:String(e.skill_id||""),package_name:String(e.package_name||""),enabled:Boolean(e.enabled),install_status:String(e.install_status||""),version:e.version?String(e.version):null,config_json:B(e.config_json),created_at:e.created_at?String(e.created_at):void 0,updated_at:e.updated_at?String(e.updated_at):void 0}))}catch(e){if("ENOENT"===e?.code)return[];throw e}}().then(e=>{e.length>0&&(F.applySnapshots(e),I("[skills] restored local snapshot",{count:e.length}))}).catch(e=>{I("[skills] failed to restore local snapshot",{error:e instanceof Error?e.message:String(e)})}),L=()=>String(T.getConfig().errorLogDir||U.getConfig().errorLogDir||"").trim()||e.join(f.getPath("documents"),"weget-bot-logs");const G=c({getModel:()=>T.getConfig().aiModel||U.getConfig().aiModel,getWorkingDir:()=>f.getPath("userData"),emitLog:I}),J=l({getEnv:()=>({loginApiUrl:s.loginApiUrl,botUploadApiUrl:s.botUploadApiUrl}),emitLog:I,getCurrentSession:()=>C});const K=()=>{const e=F.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),t=F.isSkillEnabled("@ai.weget.jp/skill-gmo-coin");"connected"===P?.getStatus()&&e?T.startExecutionStreams():T.stopExecutionStreams(),"connected"===P?.getStatus()&&t?U.startExecutionStreams():U.stopExecutionStreams(),I("[skills] applied managed states",{active_skills:F.getActiveSkills().map(e=>e.name)})},R=async()=>{const e=J.getBotSkillsListApiUrl(),i=String(C?.botId||"").trim();if(!e||!i)return;const o=await J.callBotApi(e,{bot_id:i}),a=Array.isArray(o.states)?o.states.filter(e=>Boolean(e&&"object"==typeof e)).map(e=>({user_id:String(e.user_id||""),bot_id:String(e.bot_id||""),skill_id:String(e.skill_id||""),package_name:String(e.package_name||""),enabled:Boolean(e.enabled),install_status:String(e.install_status||""),version:e.version?String(e.version):null,config_json:B(e.config_json),created_at:e.created_at?String(e.created_at):void 0,updated_at:e.updated_at?String(e.updated_at):void 0})):[];F.applySnapshots(a),await(async e=>{await t.writeFile(_(),JSON.stringify({updatedAt:(new Date).toISOString(),items:e},null,2),"utf8")})(a),K(),I("[config] skill states synced",{count:a.length,bot_id:i,enabled:a.filter(e=>e.enabled).length})},N=e=>{if("fx"===e&&!F.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===e&&!F.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")};P=new r({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:x,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(e,t)=>I(e,t),onStatus:e=>j.emitBotStatus({status:e}),onLineMessage:async e=>{const t=String(e.line_user_id||""),i=String(e.text||"").trim();if(M({type:"line_user",lineUserId:t,text:i}),!i)return{answer:""};try{const e=await G.runPrompt({prompt:i,contextLabel:"line_message"});return M({type:"line_assistant",lineUserId:t,text:e}),{answer:e}}catch(e){const i=e instanceof Error?e.message:String(e);I("[ai] line reply failed",{error:i});const o=`AI error: ${i}`;return M({type:"line_assistant",lineUserId:t,text:o}),{answer:o}}},onRunTask:async e=>{const t=String(e.task_id||"").trim(),i=String(e.prompt||"").trim();if(t){if(D.set(t,{cancelled:!1}),P?.sendTaskEvent(t,"task_received",{prompt:i,conversation_id:String(e.conversation_id||""),continue_from_task_id:String(e.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return P?.sendTaskEvent(t,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void D.delete(t);try{P?.sendTaskEvent(t,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const e=await G.runPrompt({prompt:i,contextLabel:`task:${t}`}),o=D.get(t);if(o?.cancelled)return P?.sendTaskEvent(t,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void D.delete(t);P?.sendTaskEvent(t,"ai_output",{summary:e,ts:(new Date).toISOString()}),P?.sendTaskEvent(t,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),P?.sendTaskEvent(t,"task_completed",{summary:e.slice(0,500),ts:(new Date).toISOString()})}catch(e){P?.sendTaskEvent(t,"task_failed",{error:e instanceof Error?e.message:String(e),ts:(new Date).toISOString()})}finally{D.delete(t)}}else I("[task] run_task missing task_id")},onTaskControl:async e=>{const t=String(e.task_id||"").trim(),i=String(e.action||"").trim();if(t&&i&&"cancel_task"===i){const e=D.get(t);if(e)return e.cancelled=!0,void I("[task] cancel requested",{task_id:t});P?.sendTaskEvent(t,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()})}},onServerConfigUpdate:async e=>{const t=Array.isArray(e.scopes)?e.scopes.map(e=>String(e||"").trim()):[],i=t.length>0?t:["runtime","skills"];i.includes("runtime")&&await(async()=>{const e=J.getBotRuntimeConfigApiUrl(),t=String(C?.botId||"").trim();if(!e||!t)return;const i=await J.callBotApi(e,{bot_id:t}),o=String(i.chat_model||"").trim(),a={...o?{aiModel:o}:{}};0!==Object.keys(a).length&&(await T.saveConfig({...T.getConfig(),...a}),await U.saveConfig({...U.getConfig(),...a}),I("[config] runtime config synced",{chat_model:o||""}))})(),i.includes("skills")&&await R()}}),n({ipcMain:S,env:{capabilities:x,loginApiUrl:s.loginApiUrl},signInWithLoginApi:J.signInWithLoginApi,saveLoginProfile:async({botId:e,email:i,password:o,remember:a})=>{if(a)await t.writeFile(b(),JSON.stringify({botId:e,email:i,password:o,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await t.unlink(b())}catch(e){if("ENOENT"!==e?.code)throw e}},readSavedLoginProfile:async()=>{try{const e=await t.readFile(b(),"utf8"),i=JSON.parse(e);return{botId:String(i?.botId||""),email:String(i?.email||""),password:String(i?.password||""),remember:Boolean(i?.remember)}}catch(e){if("ENOENT"===e?.code)return{botId:"",email:"",password:"",remember:!1};throw e}},getFxApiConfig:()=>{const e=T.getConfig();return{riskDailyLossLimitJpy:e.riskDailyLossLimitJpy,fxApiKey:e.fxApiKey,fxApiSecret:e.fxApiSecret,errorLogDir:e.errorLogDir}},saveFxApiConfig:async e=>{const t=await T.saveConfig({...T.getConfig(),...e&&"object"==typeof e?e:{}});return"connected"===P?.getStatus()&&K(),{riskDailyLossLimitJpy:t.riskDailyLossLimitJpy,fxApiKey:t.fxApiKey,fxApiSecret:t.fxApiSecret,errorLogDir:t.errorLogDir}},getCoinApiConfig:()=>{const e=U.getConfig();return{riskDailyLossLimitJpy:e.riskDailyLossLimitJpy,cryptoApiKey:e.cryptoApiKey,cryptoApiSecret:e.cryptoApiSecret,errorLogDir:e.errorLogDir}},saveCoinApiConfig:async e=>{const t=await U.saveConfig({...U.getConfig(),...e&&"object"==typeof e?e:{}});return"connected"===P?.getStatus()&&K(),{riskDailyLossLimitJpy:t.riskDailyLossLimitJpy,cryptoApiKey:t.cryptoApiKey,cryptoApiSecret:t.cryptoApiSecret,errorLogDir:t.errorLogDir}},getAiConfig:()=>{const e=T.getConfig(),t=U.getConfig();return{aiModel:e.aiModel||t.aiModel}},saveAiConfig:async e=>{const t=e&&"object"==typeof e?e:{},i=await T.saveConfig({...T.getConfig(),...t});return await U.saveConfig({...U.getConfig(),...t}),{aiModel:i.aiModel}},setCurrentSession:e=>{C=e||null;const t=C?"connected":"disconnected";j.emitBotStatus({status:t})},getCurrentSession:()=>C,getActiveSkills:()=>F.getActiveSkills().map(e=>({name:e.name,displayName:e.displayName,version:e.version})),getCodexAuthStatus:()=>G.getAuthStatus(),startCodexLogin:()=>G.startLogin(),getManagedSkills:()=>F.getManagedSkills(),emitLog:I,openGmoKlineWindow:async({symbol:e,interval:t,market:i})=>(()=>{const o="fx"===i?"fx":"coin";return N(o),("fx"===o?T.marketData:U.marketData).fetchKline({symbol:e,interval:t,market:o})})(),getGmoMarketQuotes:async({market:e,symbols:t})=>(()=>{const i="fx"===e?"fx":"coin";return N(i),("fx"===i?T.marketData:U.marketData).fetchQuotes({market:i,symbols:t})})(),getGmoSymbolRules:async({market:e})=>(()=>{const t="fx"===e?"fx":"coin";return N(t),("fx"===t?T.marketData:U.marketData).fetchSymbolRules({market:t})})(),getGmoBuyingPower:async({market:e})=>({market:e,availableJpy:await(N(e),("fx"===e?T.gateway:U.gateway).getBuyingPower(e))}),getGmoAccountMetrics:async({market:e})=>(N(e),("fx"===e?T.gateway:U.gateway).getAccountMetrics(e)),getGmoPositionSummary:async({market:e,symbol:t})=>({market:e,symbol:t,items:await(N(e),("fx"===e?T.gateway:U.gateway).getPositionSummary({market:e,symbol:t}))}),getGmoOpenPositions:async({market:e,symbol:t,page:i,prevId:o,count:a})=>({market:e,symbol:t||"",items:await(N(e),("fx"===e?T.gateway:U.gateway).getOpenPositions({market:"fx"===e?"fx":"crypto",symbol:t||void 0,page:i,prevId:o,count:a}))}),placeGmoFxOrder:async({symbol:e,side:t,size:i})=>{N("fx");return await T.gateway.placeFxOrder({symbol:e,side:t,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:e,side:t,size:i})=>{N("coin");return await U.gateway.placeCoinOrder({symbol:e,side:t,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:e,side:t,positionId:i,size:o})=>{N("fx");return await T.gateway.placeFxCloseOrder({symbol:e,side:t,positionId:i,size:o,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:e,side:t,positionId:i,size:o})=>{N("coin");return await U.gateway.placeCoinCloseOrder({symbol:e,side:t,positionId:i,size:o,executionType:"MARKET"})},getGmoApiState:e=>"fx"===e?T.status.getApiState():U.status.getApiState(),testGmoApi:e=>(N(e),"fx"===e?T.status.testApi():U.status.testApi()),buildTradeStatusSnapshot:e=>(N(e),"fx"===e?T.status.buildSnapshot():U.status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:e=>{P?.start(e),K()},stopWsClient:()=>{T.stopExecutionStreams(),U.stopExecutionStreams(),P?.stop()},handleBotChatMessage:async({text:e})=>{M({type:"user",text:String(e||"")});const t=await G.runPrompt({prompt:e,contextLabel:"desktop_chat"});return M({type:"assistant",text:t}),{answer:t}},writeErrorLog:async({source:e,message:t,details:i})=>{await(async({source:e,message:t,details:i})=>{await O({level:"error",source:e,message:t,details:i})})({source:e,message:t,details:i})},writeRuntimeLog:async({level:e,source:t,message:i,details:o})=>{await O({level:e,source:t,message:i,details:o})}});const W=e.join(k,"renderer","logo.png");f.whenReady().then(()=>{(()=>{if("darwin"!==process.platform)return;if(!i(W))return;const e=w.createFromPath(W);e.isEmpty()||f.dock.setIcon(e)})(),j.createMainWindow(),f.on("activate",()=>{0===y.getAllWindows().length&&j.createMainWindow()})}),f.on("window-all-closed",()=>{T.stopExecutionStreams(),U.stopExecutionStreams(),P?.stop(),"darwin"!==process.platform&&f.quit()});
|
|
1
|
+
import t from"node:path";import e from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import{createRequire as 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()});
|
package/dist/src/preload.cjs
CHANGED
|
@@ -4,10 +4,8 @@ contextBridge.exposeInMainWorld('botApi', {
|
|
|
4
4
|
login: (email, password, botId, remember) => ipcRenderer.invoke('auth:login', { email, password, botId, remember }),
|
|
5
5
|
logout: () => ipcRenderer.invoke('auth:logout'),
|
|
6
6
|
getSavedProfile: () => ipcRenderer.invoke('auth:getSavedProfile'),
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
getCoinApiConfig: () => ipcRenderer.invoke('trade:getCoinApiConfig'),
|
|
10
|
-
saveCoinApiConfig: (config) => ipcRenderer.invoke('trade:saveCoinApiConfig', config),
|
|
7
|
+
getSkillConfig: (skillName) => ipcRenderer.invoke('skill:getConfig', skillName),
|
|
8
|
+
saveSkillConfig: (skillName, config) => ipcRenderer.invoke('skill:saveConfig', { skillName, config }),
|
|
11
9
|
getAiConfig: () => ipcRenderer.invoke('ai:getConfig'),
|
|
12
10
|
saveAiConfig: (config) => ipcRenderer.invoke('ai:saveConfig', config),
|
|
13
11
|
getRuntimeInfo: () => ipcRenderer.invoke('bot:getRuntimeInfo'),
|
package/dist/src/renderer/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("user-notice"),r=document.getElementById("bot-id"),a=document.getElementById("email"),s=document.getElementById("password"),c=document.getElementById("remember-me"),l=document.getElementById("status"),d=document.getElementById("session"),m=document.getElementById("runtime-info"),u=document.getElementById("skill-state-list"),g=document.getElementById("tab-btn-skill-gmo-coin"),f=document.getElementById("tab-btn-skill-gmo-fx"),p=document.getElementById("tab-btn-skill-browser"),y=document.getElementById("coin-skill-title"),b=document.getElementById("fx-skill-title"),w=document.getElementById("browser-skill-title"),x=document.getElementById("coin-skill-package"),k=document.getElementById("fx-skill-package"),h=document.getElementById("browser-skill-package"),S=document.getElementById("coin-skill-enabled"),v=document.getElementById("fx-skill-enabled"),E=document.getElementById("browser-skill-enabled"),C=document.getElementById("coin-skill-version"),I=document.getElementById("fx-skill-version"),A=document.getElementById("browser-skill-version"),B=document.getElementById("browser-skill-tools"),L=document.getElementById("logs"),N=document.getElementById("messages"),$=document.getElementById("chat-form"),U=document.getElementById("chat-input"),M=document.getElementById("send-btn"),P=document.getElementById("error-log-dir"),T=document.getElementById("risk-daily-loss-limit-jpy"),F=document.getElementById("crypto-api-key"),D=document.getElementById("crypto-api-secret"),z=document.getElementById("fx-api-key"),O=document.getElementById("fx-api-secret"),j=document.getElementById("toggle-crypto-secret-btn"),q=document.getElementById("toggle-fx-secret-btn"),J=document.getElementById("save-api-config-btn"),Y=document.getElementById("ai-model"),R=document.getElementById("save-ai-config-btn"),H=document.getElementById("codex-login-btn"),_=document.getElementById("codex-copy-login-btn"),K=document.getElementById("codex-refresh-auth-btn"),W=document.getElementById("codex-login-command"),G=document.getElementById("logout-btn"),V=document.getElementById("login-btn"),Q=document.getElementById("coin-symbol-select"),X=document.getElementById("fx-symbol-select"),Z=document.getElementById("coin-kline-intervals"),ee=document.getElementById("fx-kline-intervals"),te=document.getElementById("coin-kline-canvas"),ne=document.getElementById("fx-kline-canvas"),ie=document.getElementById("coin-market-icon"),oe=document.getElementById("fx-market-icon"),re=document.getElementById("coin-order-bid"),ae=document.getElementById("coin-order-ask"),se=document.getElementById("coin-order-spread"),ce=document.getElementById("fx-order-bid"),le=document.getElementById("fx-order-ask"),de=document.getElementById("fx-order-spread"),me=document.getElementById("coin-order-qty"),ue=document.getElementById("fx-order-qty"),ge=document.getElementById("coin-buy-btn"),fe=document.getElementById("coin-sell-btn"),pe=document.getElementById("fx-buy-btn"),ye=document.getElementById("fx-sell-btn"),be=document.getElementById("coin-required-amount"),we=document.getElementById("fx-required-amount"),xe=document.getElementById("coin-account-info-refresh-btn"),ke=document.getElementById("fx-account-info-refresh-btn"),he=document.getElementById("coin-account-pnl"),Se=document.getElementById("coin-account-margin"),ve=document.getElementById("coin-account-available"),Ee=document.getElementById("coin-account-margin-ratio"),Ce=document.getElementById("fx-account-pnl"),Ie=document.getElementById("fx-account-margin"),Ae=document.getElementById("fx-account-available"),Be=document.getElementById("fx-account-margin-ratio"),Le=document.getElementById("coin-position-summary-body"),Ne=document.getElementById("coin-position-list-body"),$e=document.getElementById("fx-position-summary-body"),Ue=document.getElementById("fx-position-list-body"),Me=document.getElementById("coin-position-summary-refresh-btn"),Pe=document.getElementById("coin-position-list-refresh-btn"),Te=document.getElementById("fx-position-summary-refresh-btn"),Fe=document.getElementById("fx-position-list-refresh-btn"),De=Array.from(document.querySelectorAll(".tab-btn")),ze=Array.from(document.querySelectorAll(".tab-panel"));let Oe="15m",je="15m",qe=String(Q?.value||"BTC_JPY").trim().toUpperCase(),Je=String(X?.value||"USD_JPY").trim().toUpperCase(),Ye=null,Re=null,He=null,_e=null,Ke=!1,We=!1,Ge=!1,Ve=!1,Qe=!1,Xe="",Ze="",et=0,tt=0,nt=null,it=null;const ot=new Map,rt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},at={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let st=[],ct=[];const lt=e=>e&&"object"==typeof e?e:{},dt=e=>{for(const t of De)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of ze)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of De)e.addEventListener("click",()=>dt(e.dataset.tab||"coin"));const mt=e=>{const n=Ke!==e;Ke=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&Wt(),e?n&&on():(Mt(),We=!1)},ut=e=>{const t=String(e||"disconnected").trim().toLowerCase();l.textContent=t,l.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?l.classList.add("status-disconnected"):l.classList.add("status-connecting"):l.classList.add("status-connected")},gt=(e,t=6)=>{if(null===e||!Number.isFinite(e))return"-";const n=Math.abs(e);return n>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:3}):n>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:Math.min(t,5)}):e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:t})},ft=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,pt=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,yt=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),bt=e=>{const t=String(e||"").trim().replace(/,/g,"");if(!t)return"";if(!/^\d+(\.\d+)?$/.test(t))return"";if(!t.includes("."))return t;return t.replace(/(\.\d*?[1-9])0+$/,"$1").replace(/\.0+$/,"").replace(/\.$/,"")},wt=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},xt=(e,t)=>{const n=String(e||"").trim(),i=n.startsWith("-"),o=i?n.slice(1):n,[r,a=""]=o.split("."),s=(a+"0".repeat(t)).slice(0,t),c=BigInt((r||"0")+s);return i?-c:c},kt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=gt(t.bid),a=gt(t.ask);n.textContent=r,i.textContent=a,o.textContent=gt(t.spread,8)},ht=e=>{if("coin"===e){const e=Number(String(me.value||"").trim()),t=nt;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(be.textContent="- 円"):void(be.textContent=ft(e*t/2))}const t=Number(String(ue.value||"").trim()),n=it;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?we.textContent="- 円":we.textContent=ft(t*n/20)},St=async(e,t)=>{if(!window.botApi?.getMarketQuotes)return null;const n=String(t||"").trim().toUpperCase(),i=await window.botApi.getMarketQuotes({market:e,symbols:[n]});if(!i?.ok)throw new Error(String(i?.error||"quote api failed"));const o=(i.quotes||[]).find(e=>String(e.symbol||"").toUpperCase()===n)||null;return"coin"===e?(kt({symbol:n,quote:o,orderBidNode:re,orderAskNode:ae,orderSpreadNode:se}),nt=o?.ask??null,ht("coin")):(kt({symbol:n,quote:o,orderBidNode:ce,orderAskNode:le,orderSpreadNode:de}),it=o?.ask??null,ht("fx")),o},vt=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(ie.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;oe.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},Et=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,Ct=e=>"coin"===e?rt:at,It=e=>"coin"===e?te:ne,At=e=>"coin"===e?qe:Je,Bt=e=>{const t=At(e),n=("coin"===e?st:ct).filter(e=>String(e.symbol||"").trim().toUpperCase()===t),i=[];for(const e of n){const t=String(e.side||"").trim().toUpperCase(),n=Number(e.averagePositionRate||0);if(!Number.isFinite(n)||n<=0)continue;const o="BUY"===t;i.push({price:n,label:o?"BUY Avg":"SELL Avg",color:o?"#1f9d55":"#e03131"})}return i},Lt=e=>{const t=Ct(e);t.candles.length&&Vt(It(e),t.candles,Bt(e))},Nt=(e,t)=>{if(!t)return;const n=Ct(e);if(!n.candles.length)return;if(n.symbol!==t.symbol)return;const i=n.candles[n.candles.length-1],o=null!==t.bid&&null!==t.ask?(t.bid+t.ask)/2:null!==t.ask?t.ask:t.bid;null!==o&&Number.isFinite(o)&&(i.close=o,i.high=Math.max(i.high,o),i.low=Math.min(i.low,o),Vt(It(e),n.candles,Bt(e)))},$t=async e=>{const t=Ct(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==At(e))return;if(!(Math.floor(Date.now()/Et(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await en():await tn()}finally{t.fetching=!1}}},Ut=async e=>{if("coin"!==e){if(!Qe){Qe=!0;try{const e=await St("fx",Je);Nt("fx",e),await $t("fx")}catch(e){const t=Yt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Ze||n-tt>15e3)&&(qt("[fx] quote poll failed",{error:t}),Ze=t,tt=n)}finally{Qe=!1}}}else{if(Ve)return;Ve=!0;try{const e=await St("coin",qe);Nt("coin",e),await $t("coin")}catch(e){const t=Yt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Xe||n-et>15e3)&&(qt("[coin] quote poll failed",{error:t}),Xe=t,et=n)}finally{Ve=!1}}},Mt=()=>{He&&(clearInterval(He),He=null),_e&&(clearInterval(_e),_e=null)},Pt=async e=>{if(window.botApi?.getAccountMetrics){if(!e||"coin"===e){const e=await window.botApi.getAccountMetrics({market:"coin"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);he.textContent=n>0?pt(i):ft(0),Se.textContent=ft(n),ve.textContent=ft(t),Ee.textContent=yt(Number(e.marginRatio||0))}else qt("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if(!e||"fx"===e){const e=await window.botApi.getAccountMetrics({market:"fx"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);Ce.textContent=n>0?pt(i):ft(0),Ie.textContent=ft(n),Ae.textContent=ft(t),Be.textContent=yt(Number(e.marginRatio||0))}else qt("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},Tt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").toUpperCase(),o=n.replace("_","/"),r=String(i.side||"-"),a=String(i.size||"").trim(),s=Number(a||0),c=Number(i.price||0),l=Number(i.lossGain||0),d=Number(i.totalSwap||0),m=String(i.timestamp||"-"),u=Number(i.positionId||0),g=Number.isFinite(u)&&u>0&&a?`data-market="${e}" data-symbol="${n}" data-side="${String(r||"").toUpperCase()}" data-position-id="${u}" data-size="${a}"`:"disabled",f=document.createElement("tr");f.innerHTML=`\n <td><button type="button" class="close-btn" ${g}>決済</button></td>\n <td>${o}<br />${r}</td>\n <td>${s.toLocaleString("ja-JP")}</td>\n <td>${c.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${pt(l)}<br />${pt(d)}</td>\n <td>${m.replace("T"," ").slice(0,19)}</td>\n `,t.appendChild(f)}}else t.innerHTML='<tr><td colspan="6" class="empty-cell">対象のお取引はございません。</td></tr>'},Ft=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").replace("_","/"),o=String(i.side||"-"),r=Number("fx"===e?i.sumPositionSize||0:i.sumPositionQuantity||0),a=Number(i.averagePositionRate||0),s=Number(i.positionLossGain||0),c=Number("fx"===e&&i.sumTotalSwap||0),l=document.createElement("tr");l.innerHTML=`\n <td><button type="button" class="close-btn">決済</button></td>\n <td>${n}<br />${o}</td>\n <td>${r.toLocaleString("ja-JP")}</td>\n <td>${a.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${pt(s)}<br />${pt(c)}</td>\n `,t.appendChild(l)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},Dt=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if(!e||"coin"===e){const e=await window.botApi.getPositionSummary({market:"coin",symbol:qe});e?.ok?(st=Array.isArray(e.items)?e.items:[],Ft("coin",Le,st),Lt("coin")):(qt("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),st=[],Ft("coin",Le,[]),Lt("coin"))}if(!e||"fx"===e){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(ct=Array.isArray(e.items)?e.items:[],Ft("fx",$e,ct),Lt("fx")):(qt("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),ct=[],Ft("fx",$e,[]),Lt("fx"))}if(!e||"coin"===e){const e=await window.botApi.getOpenPositions({market:"coin",symbol:qe,page:1,count:100});e?.ok?Tt("coin",Ne,Array.isArray(e.items)?e.items:[]):(qt("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),Tt("coin",Ne,[]))}if(!e||"fx"===e){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?Tt("fx",Ue,Array.isArray(e.items)?e.items:[]):(qt("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),Tt("fx",Ue,[]))}}},zt=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(ue.value||"").trim();i?await Gt(t,async()=>{const t=await n.placeFxOrder({symbol:Je,side:e,size:i});if(!t?.ok){const n=Yt(t?.error||"fx order failed");return qt("[fx] order failed",{symbol:Je,side:e,size:i,error:n}),Rt("error",n),void await Ht("fx.order",n,t)}qt("[fx] order placed",{symbol:Je,side:e,size:i,result:t.result||null}),Rt("success",`${e} 注文を送信しました。`),await Pt("fx"),await Dt("fx")}):Rt("error","数量を入力してください。")},Ot=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=bt(String(me.value||""));if(!i)return void Rt("error","数量の形式が不正です。");const o=((e,t)=>{const n=ot.get(e);if(!n)return{ok:!0};const i=bt(n.minOrderSize),o=bt(n.sizeStep),r=bt(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(wt(t),wt(i),wt(o),wt(r)),s=xt(t,a),c=xt(i,a),l=xt(o,a);if(s<c)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(l>0n&&s%l!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>xt(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(qe,i);o.ok?await Gt(t,async()=>{const t=await n.placeCoinOrder({symbol:qe,side:e,size:i});if(!t?.ok){const n=Yt(t?.error||"coin order failed");return qt("[coin] order failed",{symbol:qe,side:e,size:i,error:n}),Rt("error",n),void await Ht("coin.order",n,t)}qt("[coin] order placed",{symbol:qe,side:e,size:i,result:t.result||null}),Rt("success",`${e} 注文を送信しました。`),await Pt("coin"),await Dt("coin")}):Rt("error",o.reason||"数量が取引ルールに一致しません。")},jt=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},qt=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:jt(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!L)return;const i=document.createElement("div");i.className="log-entry";const o=document.createElement("div");o.className="log-line";const r=document.createElement("span");if(r.className="log-ts",r.textContent=n||(new Date).toISOString(),o.appendChild(r),o.append(document.createTextNode(e||"")),i.appendChild(o),null!=t&&""!==t){const e=document.createElement("pre");e.className="status-output",e.textContent="string"==typeof t?t:JSON.stringify(t,null,2),i.appendChild(e)}for(L.appendChild(i);L.children.length>300;)L.removeChild(L.firstChild);L.scrollTop=L.scrollHeight},Jt=(e,t,n="")=>{const i=document.createElement("div");i.className=`msg ${e}`;let o=n||("user"===e?"You":"Assistant");"line-user"===e&&(o=n||"LINE User"),"line-assistant"===e&&(o=n||"AI"),i.textContent=`${o}: ${t}`,N.appendChild(i),N.scrollTop=N.scrollHeight},Yt=e=>{const t=String(e||"").trim(),n=t.toLowerCase();return t?n.includes("api key")&&n.includes("missing")?"API Key 未设置,请先在 Config 页面保存。":n.includes("auth")||n.includes("401")||n.includes("403")?"认证失败,请检查 API Key 和 Secret。":n.includes("network")||n.includes("fetch failed")||n.includes("timeout")?"网络连接异常,请检查网络后重试。":n.includes("message is required")?"请输入内容后再发送。":t:"处理失败,请稍后重试。"},Rt=(e,t)=>{Ye&&(clearTimeout(Ye),Ye=null),o.classList.remove("hidden","notice-error","notice-success"),o.classList.add("error"===e?"notice-error":"notice-success"),o.textContent=t,Ye=setTimeout(()=>{_t()},"success"===e?2200:4200)},Ht=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},_t=()=>{Ye&&(clearTimeout(Ye),Ye=null),o.classList.add("hidden"),o.classList.remove("notice-error","notice-success"),o.textContent=""},Kt=(e,t)=>{Re&&(clearTimeout(Re),Re=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,Re=setTimeout(()=>{Wt()},"success"===e?2200:4200)},Wt=()=>{Re&&(clearTimeout(Re),Re=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},Gt=async(e,t)=>{if(e.disabled)return;e.disabled=!0,e.classList.add("is-loading");const n=e.textContent||"";try{await t()}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[ui] action failed",{error:t}),Rt("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},Vt=(e,t,n=[])=>{const i=e.getContext("2d");if(!i)return;const o=e.width,r=e.height;if(i.clearRect(0,0,o,r),!t.length)return;const a=Math.max(...t.map(e=>e.high)),s=Math.min(...t.map(e=>e.low)),c=n.map(e=>e.price).filter(e=>Number.isFinite(e)),l=c.length?Math.max(a,...c):a,d=c.length?Math.min(s,...c):s,m=Math.max(1e-8,l-d),u=10,g=10,f=o-10-62-8,p=r-10-24-8,y=Math.max(2,f/t.length),b=e=>g+(l-e)/m*p,w=e=>e>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:0}):e>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:3}):e.toLocaleString("en-US",{minimumFractionDigits:5,maximumFractionDigits:5});i.strokeStyle="#c9d4ea",i.strokeRect(.5,.5,o-1,r-1),i.strokeStyle="#e3eaf5",i.lineWidth=1;for(let e=0;e<=4;e+=1){const t=g+p/4*e;i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke()}t.forEach((e,t)=>{const n=u+t*y+.5*y,o=b(e.high),r=b(e.low),a=b(e.open),s=b(e.close),c=e.close>=e.open;i.strokeStyle=c?"#1f9d55":"#d64545",i.fillStyle=c?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,r),i.stroke();const l=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(l,m,d,g)});for(const e of n){const t=b(e.price);i.save(),i.setLineDash([4,4]),i.strokeStyle=e.color,i.lineWidth=1,i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke(),i.restore(),i.font="12px Segoe UI";const n=`${e.label} ${w(e.price)}`,o=Math.ceil(i.measureText(n).width)+10,r=14,a=Math.max(12,Math.min(g+p-18,t-9));i.fillStyle=e.color,i.fillRect(r,a,o,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(n,r+5,a+9)}const x=t[t.length-1],k=x?.close;if(Number.isFinite(k)){const e=b(Number(k));i.save(),i.setLineDash([5,4]),i.strokeStyle="#2b79ff",i.lineWidth=1,i.beginPath(),i.moveTo(u,e),i.lineTo(u+f,e),i.stroke(),i.restore();const t=w(Number(k));i.font="12px Segoe UI";const n=Math.ceil(i.measureText(t).width)+10,o=u+f+6,r=Math.max(12,Math.min(g+p-18,e-9));i.fillStyle="#2b79ff",i.fillRect(o,r,n,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(t,o+5,r+9)}i.fillStyle="#66758d",i.font="12px Segoe UI",i.textBaseline="middle",i.textAlign="left";for(let e=0;e<=4;e+=1){const t=l-m/4*e,n=g+p/4*e;i.fillText(w(t),u+f+8,n)}const h=e=>{const t=new Date(e>1e12?e:1e3*e);if(Number.isNaN(t.getTime()))return"-";const n=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0");return`${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${n}:${i}`};i.textAlign="center",i.textBaseline="top";for(let e=0;e<=4;e+=1){const n=Math.min(t.length-1,Math.floor((t.length-1)*(e/4))),o=u+f*e/4;i.fillText(h(t[n]?.time||0),o,g+p+6)}},Qt=(e,t)=>{const{tabBtn:n,titleNode:i,packageNode:o,enabledNode:r,versionNode:a,fallbackTitle:s,fallbackPackage:c}=t,l=e?.displayName||s,d=e?.name||c,m=!e||Boolean(e.enabled),u=String(e?.installStatus||"builtin").trim()||"builtin",g=String(e?.configuredVersion||e?.version||"builtin").trim()||"builtin";n&&(n.textContent=d.replace("@ai.weget.jp/",""),n.classList.toggle("is-disabled",!m)),i&&(i.textContent=l),o&&(o.textContent=d),r&&(r.textContent=m?"enabled":"disabled",r.classList.toggle("is-disabled",!m)),a&&(a.textContent=`${u} · ${g}`)},Xt=async()=>{if(!window.botApi?.getRuntimeInfo)return;const[e,t]=await Promise.all([window.botApi.getRuntimeInfo(),window.botApi.getSkills?window.botApi.getSkills():(async()=>({ok:!1,skills:[]}))()]);if(!e?.ok)return;ut(e.status||"disconnected"),e.session?(mt(!0),d.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(mt(!1),d.textContent="local mode (not logged in)"),(e=>{m.innerHTML="";const t=lt(e),n=lt(t.runtime),i=t.session?lt(t.session):null,o=[["Bot ID",String(i?.botId||"-")],["User ID",String(i?.userId||"-")],["Email",String(i?.email||"-")],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["Active Skills",Array.isArray(n.activeSkills)&&n.activeSkills.map(e=>{const t=lt(e);return String(t.displayName||t.name||"").trim()}).filter(Boolean).join(", ")||"-"],["GMO FX API State",String(n.gmoFxApiState||"unknown")],["GMO Coin API State",String(n.gmoCoinApiState||"unknown")],["Codex Auth",String(n.codexAuthStatus||"unknown")],["Codex Auth Detail",String(n.codexAuthDetail||"-")],["Default Model",String(n.aiModel||"-")],["Login API",String(n.loginApiUrl||"-")]];for(const[e,t]of o){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const i=document.createElement("div");i.className="runtime-value",i.textContent=t,m.appendChild(n),m.appendChild(i)}})(e);const n=t?.ok&&t.skills||[];(e=>{if(!u)return;u.innerHTML="";const t=Array.isArray(e)?e:[];if(0===t.length){const e=document.createElement("div");return e.className="skill-empty",e.textContent="No managed skills are available in this bot host.",void u.appendChild(e)}for(const e of t){const t=document.createElement("section");t.className="skill-card"+(e.enabled?"":" is-disabled");const n=document.createElement("div");n.className="skill-card-head";const i=document.createElement("div");i.className="skill-card-title";const o=document.createElement("strong");o.textContent=e.displayName||e.name;const r=document.createElement("code");if(r.textContent=e.name,i.appendChild(o),i.appendChild(r),e.description){const t=document.createElement("div");t.className="muted",t.textContent=e.description,i.appendChild(t)}const a=document.createElement("div");a.className="skill-badges";const s=document.createElement("span");s.className="skill-badge "+(e.enabled?"enabled":"disabled"),s.textContent=e.enabled?"enabled":"disabled",a.appendChild(s);const c=document.createElement("span");c.className="skill-badge status",c.textContent=e.installStatus||"unknown",a.appendChild(c),n.appendChild(i),n.appendChild(a),t.appendChild(n);const l=e=>{const t=document.createElement("div");t.className="skill-card-section";const n=document.createElement("div");return n.className="skill-section-label",n.textContent=e,t.appendChild(n),t},d=l("Package"),m=document.createElement("code");m.className="skill-package-code",m.textContent=e.name,d.appendChild(m),t.appendChild(d);const g=l("Tools"),f=document.createElement("div");f.className="skill-tags";const p=Array.isArray(e.tools)?e.tools:[];if(p.length>0)for(const e of p){const t=document.createElement("span");t.className="skill-tag",t.textContent=e,f.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No tools declared",f.appendChild(e)}g.appendChild(f),t.appendChild(g);const y=l("Permissions");if(Array.isArray(e.permissions)&&e.permissions.length>0){const t=document.createElement("div");t.className="skill-tags";for(const n of e.permissions){const e=document.createElement("span");e.className="skill-tag permissions",e.textContent=n,t.appendChild(e)}y.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No permissions declared",y.appendChild(e)}t.appendChild(y);const b=l("State"),w=document.createElement("div");w.className="skill-meta";const x=[["Bundled Version",e.version||"-"],["Configured Version",e.configuredVersion||"-"],["UI",e.hasUi?"has ui":"no ui"],["Config Keys",String(Object.keys(e.configJson||{}).length)]];for(const[e,t]of x){const n=document.createElement("div");n.className="skill-meta-row";const i=document.createElement("span");i.textContent=e;const o=document.createElement("strong");o.textContent=t,n.appendChild(i),n.appendChild(o),w.appendChild(n)}b.appendChild(w),t.appendChild(b),u.appendChild(t)}})(n),Qt(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{tabBtn:g,titleNode:y,packageNode:x,enabledNode:S,versionNode:C,fallbackTitle:"GMO Coin Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-coin"}),Qt(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{tabBtn:f,titleNode:b,packageNode:k,enabledNode:v,versionNode:I,fallbackTitle:"GMO FX Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-fx"});const i=n.find(e=>"@ai.weget.jp/skill-browser"===e.name);Qt(i,{tabBtn:p,titleNode:w,packageNode:h,enabledNode:E,versionNode:A,fallbackTitle:"Browser Skill",fallbackPackage:"@ai.weget.jp/skill-browser"}),((e,t,n)=>{if(e){if(e.innerHTML="",0===t.length){const t=document.createElement("span");return t.className="muted",t.textContent=n,void e.appendChild(t)}for(const n of t){const t=document.createElement("span");t.className="skill-tag",t.textContent=n,e.appendChild(t)}}})(B,Array.isArray(i?.tools)?i.tools:[],"No browser tools declared.")};J.addEventListener("click",async()=>{await Gt(J,async()=>{if(!window.botApi?.saveFxApiConfig||!window.botApi.saveCoinApiConfig)return;const e={riskDailyLossLimitJpy:Number(T.value||"0"),errorLogDir:String(P.value||"").trim()},t={...e,fxApiKey:String(z.value||"").trim(),fxApiSecret:String(O.value||"").trim()},n={...e,cryptoApiKey:String(F.value||"").trim(),cryptoApiSecret:String(D.value||"").trim()},[i,o]=await Promise.all([window.botApi.saveFxApiConfig(t),window.botApi.saveCoinApiConfig(n)]);return i.ok?o.ok?(qt("[trade-config] saved"),Rt("success","配置已保存到本机。"),void await Xt()):(qt(`[trade-config] coin save failed: ${o.error||"unknown"}`),Rt("error",Yt(o.error)),void await Ht("trade-config.coin.save",Yt(o.error),o)):(qt(`[trade-config] fx save failed: ${i.error||"unknown"}`),Rt("error",Yt(i.error)),void await Ht("trade-config.fx.save",Yt(i.error),i))})}),R.addEventListener("click",async()=>{await Gt(R,async()=>{if(!window.botApi?.saveAiConfig)return;const e={aiModel:String(Y.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini"},t=await window.botApi.saveAiConfig(e);if(!t.ok)return qt(`[ai-config] save failed: ${t.error||"unknown"}`),Rt("error",Yt(t.error)),void await Ht("ai-config.save",Yt(t.error),t);qt("[ai-config] saved"),Rt("success","AI 设置已保存。"),await Xt()})}),H.addEventListener("click",async()=>{await Gt(H,async()=>{if(!window.botApi?.startCodexLogin)return;const e=await window.botApi.startCodexLogin();if(!e.ok)return qt(`[codex] login launch failed: ${e.detail||"unknown"}`),Rt("error",Yt(e.detail)),void await Ht("codex.login.launch",Yt(e.detail),e);qt("[codex] login launched",{detail:e.detail}),Rt("success",e.detail||"Codex login started."),setTimeout(()=>{Xt()},1500)})}),_.addEventListener("click",async()=>{const e=String(W.textContent||"codex login --device-auth").trim();try{if(!await(async e=>{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0;const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","true"),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select();const n=document.execCommand("copy");return document.body.removeChild(t),n})(e))return void Rt("error","无法复制命令,请手动执行。");qt("[codex] login command copied"),Rt("success","Codex 登录命令已复制。")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[codex] copy login command failed",{error:t}),Rt("error",t)}}),K.addEventListener("click",async()=>{await Gt(K,async()=>{await Xt(),qt("[codex] auth status refreshed"),Rt("success","Codex 状态已刷新。")})}),j.addEventListener("click",()=>{const e="password"===D.type?"text":"password";D.type=e,j.textContent="password"===e?"显示":"隐藏"}),q.addEventListener("click",()=>{const e="password"===O.type?"text":"password";O.type=e,q.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;Wt();const t=a.value.trim(),n=r.value.trim(),i=s.value;if(t&&n&&i){V.disabled=!0,V.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,c.checked);if(!e.ok)return qt(`[ui] login failed: ${e.error||"unknown"}`),void Kt("error",Yt(e.error));qt("[ui] login success"),Kt("success","登录成功。"),Rt("success","登录成功。"),await Xt()}finally{V.disabled=!1,V.textContent="Login"}}}),G.addEventListener("click",async()=>{await Gt(G,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return qt(`[ui] logout failed: ${e.error||"unknown"}`),Rt("error",Yt(e.error)),void await Ht("auth.logout",Yt(e.error),e);qt("[ui] logout"),Rt("success","已登出。"),mt(!1),d.textContent="local mode (not logged in)",await Xt()})});const Zt=async({market:e,symbol:t,interval:n,canvas:i})=>{if(!window.botApi)return;const o=await window.botApi.openGmoKlineWindow({symbol:t,interval:n,market:e});if(!o.ok)return qt(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),Rt("error",Yt(o.error)),void await Ht(`${e}.kline`,Yt(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=Ct(e);a.symbol=String(o.symbol||t||"").toUpperCase(),a.interval=n,a.candles=r.map(e=>({time:Number(e.time||0),open:Number(e.open||0),high:Number(e.high||0),low:Number(e.low||0),close:Number(e.close||0)})),a.lastFetchBucket=Math.floor(Date.now()/Et(n)),Vt(i,a.candles,Bt(e)),qt("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),_t()},en=async()=>{await Zt({market:"coin",symbol:qe,interval:Oe,canvas:te})},tn=async()=>{await Zt({market:"fx",symbol:Je,interval:je,canvas:ne})},nn=({container:e,getCurrent:t,setCurrent:n,onChange:i})=>{const o=Array.from(e.querySelectorAll(".interval-btn"));for(const e of o)e.addEventListener("click",async()=>{const r=String(e.dataset.interval||"").trim();if(r&&r!==t()){n(r);for(const t of o)t.classList.toggle("is-active",t===e);await i()}})};Q.addEventListener("change",async()=>{qe=String(Q.value||"BTC_JPY").trim().toUpperCase(),vt("coin",qe);try{await St("coin",qe),await en(),await Dt("coin")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Rt("error",t)}}),X.addEventListener("change",async()=>{Je=String(X.value||"USD_JPY").trim().toUpperCase(),vt("fx",Je);try{await St("fx",Je),await tn(),await Dt("fx")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Rt("error",t)}}),nn({container:Z,getCurrent:()=>Oe,setCurrent:e=>{Oe=e},onChange:en}),nn({container:ee,getCurrent:()=>je,setCurrent:e=>{je=e},onChange:tn}),xe.addEventListener("click",async()=>{await Gt(xe,async()=>{await Pt("coin")})}),ke.addEventListener("click",async()=>{await Gt(ke,async()=>{await Pt("fx")})}),me.addEventListener("input",()=>{ht("coin")}),ue.addEventListener("input",()=>{ht("fx")}),Ne.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeCoinPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=bt(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&c?await Gt(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=Yt(e?.error||"coin close order failed");return qt("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),Rt("error",t),void await Ht("coin.closeOrder",t,e)}qt("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),Rt("success","決済注文を送信しました。"),await Pt("coin"),await Dt("coin")}):Rt("error","決済対象データが不正です。")}),Ue.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeFxPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&c?await Gt(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=Yt(e?.error||"fx close order failed");return qt("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),Rt("error",t),void await Ht("fx.closeOrder",t,e)}qt("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),Rt("success","決済注文を送信しました。"),await Pt("fx"),await Dt("fx")}):Rt("error","決済対象データが不正です。")}),ge.addEventListener("click",async()=>{await Ot("BUY",ge)}),fe.addEventListener("click",async()=>{await Ot("SELL",fe)}),pe.addEventListener("click",async()=>{await zt("BUY",pe)}),ye.addEventListener("click",async()=>{await zt("SELL",ye)}),Me.addEventListener("click",async()=>{await Gt(Me,async()=>{await Dt("coin")})}),Pe.addEventListener("click",async()=>{await Gt(Pe,async()=>{await Dt("coin")})}),Te.addEventListener("click",async()=>{await Gt(Te,async()=>{await Dt("fx")})}),Fe.addEventListener("click",async()=>{await Gt(Fe,async()=>{await Dt("fx")})}),$.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(U.value||"").trim();if(t){Jt("user",t),U.value="",M.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return Jt("assistant",`Error: ${e.error||"unknown"}`),Rt("error",Yt(e.error)),void await Ht("ai.chat",Yt(e.error),e);Jt("assistant",String(e.answer||"")),_t()}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Jt("assistant",`Error: ${t}`),Rt("error",t),await Ht("ai.chat.exception",t,e)}finally{M.disabled=!1}}}),window.addEventListener("error",e=>{const t=Yt(e.error?.message||e.message||"Unknown UI error");qt("[ui] uncaught error",{error:t}),Rt("error",t),Ht("ui.error",t,{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason.message:String(e.reason||"Unhandled promise rejection"),n=Yt(t);qt("[ui] unhandled rejection",{error:n}),Rt("error",n),Ht("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{ut(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=lt(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void Jt("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";Jt("line-assistant",String(t.text||""),e)}}),window.botApi.onTradeExecution(e=>{if(!e)return;const t=lt(e),n="coin"===String(t.market||"").trim().toLowerCase()?"coin":"fx",i=String(t.symbol||"").trim().toUpperCase(),o=String(t.side||"").trim().toUpperCase()||"-",r=String(t.settleType||"").trim().toUpperCase()||"OPEN",a=String(t.executionSize??"").trim(),s=String(t.executionPrice??"").trim(),c="CLOSE"===r?"決済":"新規",l=`${i} ${o} ${a}${s?` @ ${s}`:""}`;qt(`[${n}] execution filled`,{symbol:i,side:o,settleType:r,size:a,price:s,raw:t}),Rt("success",`約定成功 (${c}) ${l}`.trim()),Pt(n).catch(()=>{}),Dt(n).catch(()=>{})}));const on=async()=>{if(Ke&&!We&&!Ge){Ge=!0;try{await async function(){vt("coin",qe),vt("fx",Je);try{await St("coin",qe)}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[coin] quote fetch failed",{error:t}),Rt("error",t)}try{await St("fx",Je)}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[fx] quote fetch failed",{error:t}),Rt("error",t)}if(await en(),await tn(),window.botApi?.getSymbolRules)try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){ot.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&ot.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){qt("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}}(),await Pt(),await Dt(),He||(He=setInterval(()=>{Ut("coin")},1e3)),_e||(_e=setInterval(()=>{Ut("fx")},1e3)),We=!0}finally{Ge=!1}}};mt(!1),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(r.value=e.profile.botId||"",a.value=e.profile.email||"",s.value=e.profile.password||"",c.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getFxApiConfig||!window.botApi.getCoinApiConfig||!window.botApi.getAiConfig)return;const[e,t,n]=await Promise.all([window.botApi.getFxApiConfig(),window.botApi.getCoinApiConfig(),window.botApi.getAiConfig()]);e?.ok&&e.config&&(P.value=e.config.errorLogDir||"",T.value=String(e.config.riskDailyLossLimitJpy||5e4),z.value=e.config.fxApiKey||"",O.value=e.config.fxApiSecret||""),t?.ok&&t.config&&(P.value||(P.value=t.config.errorLogDir||""),T.value||(T.value=String(t.config.riskDailyLossLimitJpy||5e4)),F.value=t.config.cryptoApiKey||"",D.value=t.config.cryptoApiSecret||""),n?.ok&&n.config&&(Y.value=n.config.aiModel||"gpt-4.1-mini")})(),Xt();export{};
|
|
1
|
+
const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("sidebar-nav"),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{};
|
|
@@ -58,24 +58,47 @@
|
|
|
58
58
|
</div>
|
|
59
59
|
</header>
|
|
60
60
|
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
<button type="button" class="
|
|
66
|
-
<button type="button" class="tab-btn" data-tab="ai">Codex Console</button>
|
|
61
|
+
<div class="app-layout">
|
|
62
|
+
<aside id="sidebar-nav" class="sidebar-nav">
|
|
63
|
+
<div class="sidebar-head">
|
|
64
|
+
<strong>Console</strong>
|
|
65
|
+
<button id="sidebar-toggle-btn" type="button" class="sidebar-toggle-btn secondary" aria-label="Toggle menu">◀</button>
|
|
67
66
|
</div>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
<nav class="tabs-shell">
|
|
68
|
+
<div class="tab-group">
|
|
69
|
+
<span class="tab-group-label">Host</span>
|
|
70
|
+
<div class="tabs">
|
|
71
|
+
<button type="button" class="tab-btn" data-tab="config" data-icon="HC">
|
|
72
|
+
<span class="tab-btn-icon">HC</span>
|
|
73
|
+
<span class="tab-btn-label">Host Control</span>
|
|
74
|
+
</button>
|
|
75
|
+
<button type="button" class="tab-btn" data-tab="ai" data-icon="AI">
|
|
76
|
+
<span class="tab-btn-icon">AI</span>
|
|
77
|
+
<span class="tab-btn-label">Codex Console</span>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="tab-group">
|
|
82
|
+
<span class="tab-group-label">Installed Skills</span>
|
|
83
|
+
<div class="tabs">
|
|
84
|
+
<button id="tab-btn-skill-gmo-coin" type="button" class="tab-btn is-active" data-tab="coin" data-icon="C">
|
|
85
|
+
<span class="tab-btn-icon">C</span>
|
|
86
|
+
<span class="tab-btn-label">skill-gmo-coin</span>
|
|
87
|
+
</button>
|
|
88
|
+
<button id="tab-btn-skill-gmo-fx" type="button" class="tab-btn" data-tab="fx" data-icon="FX">
|
|
89
|
+
<span class="tab-btn-icon">FX</span>
|
|
90
|
+
<span class="tab-btn-label">skill-gmo-fx</span>
|
|
91
|
+
</button>
|
|
92
|
+
<button id="tab-btn-skill-browser" type="button" class="tab-btn" data-tab="browser" data-icon="Br">
|
|
93
|
+
<span class="tab-btn-icon">Br</span>
|
|
94
|
+
<span class="tab-btn-label">skill-browser</span>
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</nav>
|
|
99
|
+
</aside>
|
|
78
100
|
|
|
101
|
+
<section class="content-shell">
|
|
79
102
|
<section id="tab-coin" class="tab-panel is-active">
|
|
80
103
|
<section class="panel market-shell coin-theme">
|
|
81
104
|
<div class="skill-surface-head">
|
|
@@ -211,6 +234,27 @@
|
|
|
211
234
|
</table>
|
|
212
235
|
</section>
|
|
213
236
|
</div>
|
|
237
|
+
<section class="panel host-section">
|
|
238
|
+
<h3 id="coin-config-title">Coin Skill Config</h3>
|
|
239
|
+
<label>
|
|
240
|
+
<span id="coin-config-label-riskDailyLossLimitJpy">Coin Risk Daily Loss Limit (JPY)</span>
|
|
241
|
+
<input id="coin-risk-daily-loss-limit-jpy" type="number" min="1" step="1" />
|
|
242
|
+
</label>
|
|
243
|
+
<label>
|
|
244
|
+
<span id="coin-config-label-cryptoApiKey">Crypto API Key</span>
|
|
245
|
+
<input id="crypto-api-key" type="text" />
|
|
246
|
+
</label>
|
|
247
|
+
<label>
|
|
248
|
+
<span id="coin-config-label-cryptoApiSecret">Crypto API Secret</span>
|
|
249
|
+
<div class="inline-input-row">
|
|
250
|
+
<input id="crypto-api-secret" type="password" />
|
|
251
|
+
<button id="toggle-crypto-secret-btn" type="button" class="secondary">显示</button>
|
|
252
|
+
</div>
|
|
253
|
+
</label>
|
|
254
|
+
<div class="row config-actions">
|
|
255
|
+
<button id="save-coin-config-btn" type="button">Save Coin Skill Config</button>
|
|
256
|
+
</div>
|
|
257
|
+
</section>
|
|
214
258
|
</section>
|
|
215
259
|
</section>
|
|
216
260
|
|
|
@@ -351,6 +395,27 @@
|
|
|
351
395
|
</table>
|
|
352
396
|
</section>
|
|
353
397
|
</div>
|
|
398
|
+
<section class="panel host-section">
|
|
399
|
+
<h3 id="fx-config-title">FX Skill Config</h3>
|
|
400
|
+
<label>
|
|
401
|
+
<span id="fx-config-label-riskDailyLossLimitJpy">FX Risk Daily Loss Limit (JPY)</span>
|
|
402
|
+
<input id="fx-risk-daily-loss-limit-jpy" type="number" min="1" step="1" />
|
|
403
|
+
</label>
|
|
404
|
+
<label>
|
|
405
|
+
<span id="fx-config-label-fxApiKey">FX API Key</span>
|
|
406
|
+
<input id="fx-api-key" type="text" />
|
|
407
|
+
</label>
|
|
408
|
+
<label>
|
|
409
|
+
<span id="fx-config-label-fxApiSecret">FX API Secret</span>
|
|
410
|
+
<div class="inline-input-row">
|
|
411
|
+
<input id="fx-api-secret" type="password" />
|
|
412
|
+
<button id="toggle-fx-secret-btn" type="button" class="secondary">显示</button>
|
|
413
|
+
</div>
|
|
414
|
+
</label>
|
|
415
|
+
<div class="row config-actions">
|
|
416
|
+
<button id="save-fx-config-btn" type="button">Save FX Skill Config</button>
|
|
417
|
+
</div>
|
|
418
|
+
</section>
|
|
354
419
|
</section>
|
|
355
420
|
</section>
|
|
356
421
|
|
|
@@ -367,15 +432,10 @@
|
|
|
367
432
|
<span id="browser-skill-version" class="skill-surface-badge is-muted">builtin</span>
|
|
368
433
|
</div>
|
|
369
434
|
</div>
|
|
370
|
-
<div class="browser-skill-shell">
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
</div>
|
|
375
|
-
<div class="browser-skill-card">
|
|
376
|
-
<h3>Declared Tools</h3>
|
|
377
|
-
<div id="browser-skill-tools" class="skill-tags"></div>
|
|
378
|
-
</div>
|
|
435
|
+
<div id="browser-skill-cards" class="browser-skill-shell"></div>
|
|
436
|
+
<div class="browser-skill-card">
|
|
437
|
+
<h3>Declared Tools</h3>
|
|
438
|
+
<div id="browser-skill-tools" class="skill-tags"></div>
|
|
379
439
|
</div>
|
|
380
440
|
</section>
|
|
381
441
|
</section>
|
|
@@ -426,50 +486,18 @@
|
|
|
426
486
|
<label>
|
|
427
487
|
<span>模型</span>
|
|
428
488
|
<select id="ai-model">
|
|
429
|
-
<option value="gpt-4
|
|
489
|
+
<option value="gpt-5.4">gpt-5.4</option>
|
|
490
|
+
<option value="gpt-5-mini">gpt-5-mini</option>
|
|
430
491
|
<option value="gpt-4.1">gpt-4.1</option>
|
|
431
|
-
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
|
432
492
|
</select>
|
|
433
493
|
</label>
|
|
434
|
-
<div class="row config-actions">
|
|
435
|
-
<button id="save-ai-config-btn" type="button">保存 AI 设置</button>
|
|
436
|
-
<button id="logout-btn" type="button" class="secondary">Logout</button>
|
|
437
|
-
</div>
|
|
438
|
-
</section>
|
|
439
|
-
<section class="host-section">
|
|
440
|
-
<h3>GMO Credentials</h3>
|
|
441
|
-
<label>
|
|
442
|
-
<span>Error Log Directory</span>
|
|
443
|
-
<input id="error-log-dir" type="text" placeholder="e.g. D:\\weget-bot\\logs" />
|
|
444
|
-
</label>
|
|
445
|
-
<label>
|
|
446
|
-
<span>Risk Daily Loss Limit (JPY)</span>
|
|
447
|
-
<input id="risk-daily-loss-limit-jpy" type="number" min="1" step="1" />
|
|
448
|
-
</label>
|
|
449
494
|
<label>
|
|
450
|
-
<span>
|
|
451
|
-
<input id="
|
|
452
|
-
</label>
|
|
453
|
-
<label>
|
|
454
|
-
<span>Crypto API Secret</span>
|
|
455
|
-
<div class="inline-input-row">
|
|
456
|
-
<input id="crypto-api-secret" type="password" />
|
|
457
|
-
<button id="toggle-crypto-secret-btn" type="button" class="secondary">显示</button>
|
|
458
|
-
</div>
|
|
459
|
-
</label>
|
|
460
|
-
<label>
|
|
461
|
-
<span>FX API Key</span>
|
|
462
|
-
<input id="fx-api-key" type="text" />
|
|
463
|
-
</label>
|
|
464
|
-
<label>
|
|
465
|
-
<span>FX API Secret</span>
|
|
466
|
-
<div class="inline-input-row">
|
|
467
|
-
<input id="fx-api-secret" type="password" />
|
|
468
|
-
<button id="toggle-fx-secret-btn" type="button" class="secondary">显示</button>
|
|
469
|
-
</div>
|
|
495
|
+
<span>Log Output Directory</span>
|
|
496
|
+
<input id="host-log-output-dir" type="text" placeholder="e.g. D:\\weget-bot\\logs" />
|
|
470
497
|
</label>
|
|
471
498
|
<div class="row config-actions">
|
|
472
|
-
<button id="save-
|
|
499
|
+
<button id="save-ai-config-btn" type="button">保存 AI 设置</button>
|
|
500
|
+
<button id="logout-btn" type="button" class="secondary">Logout</button>
|
|
473
501
|
</div>
|
|
474
502
|
</section>
|
|
475
503
|
<section class="host-section skill-panel">
|
|
@@ -481,6 +509,8 @@
|
|
|
481
509
|
</section>
|
|
482
510
|
</section>
|
|
483
511
|
</section>
|
|
512
|
+
</section>
|
|
513
|
+
</div>
|
|
484
514
|
</main>
|
|
485
515
|
<script type="module" src="./app.js"></script>
|
|
486
516
|
</body>
|
|
@@ -6,11 +6,72 @@ body {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.app-shell {
|
|
9
|
-
max-width:
|
|
9
|
+
max-width: 1400px;
|
|
10
10
|
margin: 16px auto;
|
|
11
11
|
padding: 0 12px 16px;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
.app-layout {
|
|
15
|
+
display: grid;
|
|
16
|
+
grid-template-columns: 260px minmax(0, 1fr);
|
|
17
|
+
gap: 14px;
|
|
18
|
+
align-items: start;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.content-shell {
|
|
22
|
+
min-width: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sidebar-nav {
|
|
26
|
+
position: sticky;
|
|
27
|
+
top: 16px;
|
|
28
|
+
display: grid;
|
|
29
|
+
gap: 12px;
|
|
30
|
+
padding: 14px;
|
|
31
|
+
border-radius: 14px;
|
|
32
|
+
background: linear-gradient(180deg, #122033 0%, #18283f 100%);
|
|
33
|
+
color: #dce7f5;
|
|
34
|
+
box-shadow: 0 10px 24px rgba(20, 25, 45, 0.18);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sidebar-head {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
gap: 10px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sidebar-toggle-btn {
|
|
45
|
+
min-width: 38px;
|
|
46
|
+
padding: 8px 10px;
|
|
47
|
+
background: rgba(255, 255, 255, 0.12);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.app-shell.sidebar-collapsed .app-layout {
|
|
51
|
+
grid-template-columns: 76px minmax(0, 1fr);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.app-shell.sidebar-collapsed .sidebar-nav {
|
|
55
|
+
padding-inline: 10px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.app-shell.sidebar-collapsed .sidebar-head strong,
|
|
59
|
+
.app-shell.sidebar-collapsed .tab-group-label {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.app-shell.sidebar-collapsed .tab-btn {
|
|
64
|
+
width: 52px;
|
|
65
|
+
min-width: 52px;
|
|
66
|
+
min-height: 44px;
|
|
67
|
+
padding: 8px 6px;
|
|
68
|
+
justify-items: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.app-shell.sidebar-collapsed .tab-btn-label {
|
|
72
|
+
display: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
14
75
|
.user-notice {
|
|
15
76
|
position: fixed;
|
|
16
77
|
top: 14px;
|
|
@@ -173,7 +234,6 @@ body {
|
|
|
173
234
|
.tabs-shell {
|
|
174
235
|
display: grid;
|
|
175
236
|
gap: 10px;
|
|
176
|
-
margin-bottom: 12px;
|
|
177
237
|
}
|
|
178
238
|
|
|
179
239
|
.tab-group {
|
|
@@ -190,9 +250,8 @@ body {
|
|
|
190
250
|
}
|
|
191
251
|
|
|
192
252
|
.tabs {
|
|
193
|
-
display:
|
|
253
|
+
display: grid;
|
|
194
254
|
gap: 8px;
|
|
195
|
-
flex-wrap: wrap;
|
|
196
255
|
}
|
|
197
256
|
|
|
198
257
|
.tab-btn {
|
|
@@ -202,6 +261,11 @@ body {
|
|
|
202
261
|
border-radius: 8px;
|
|
203
262
|
padding: 8px 12px;
|
|
204
263
|
cursor: pointer;
|
|
264
|
+
text-align: left;
|
|
265
|
+
display: grid;
|
|
266
|
+
grid-template-columns: auto 1fr;
|
|
267
|
+
align-items: center;
|
|
268
|
+
gap: 10px;
|
|
205
269
|
}
|
|
206
270
|
|
|
207
271
|
.tab-btn.is-active {
|
|
@@ -210,6 +274,31 @@ body {
|
|
|
210
274
|
border-color: #2359d1;
|
|
211
275
|
}
|
|
212
276
|
|
|
277
|
+
.tab-btn-icon {
|
|
278
|
+
width: 28px;
|
|
279
|
+
height: 28px;
|
|
280
|
+
display: inline-grid;
|
|
281
|
+
place-items: center;
|
|
282
|
+
border-radius: 8px;
|
|
283
|
+
background: rgba(35, 89, 209, 0.12);
|
|
284
|
+
color: #2359d1;
|
|
285
|
+
font-size: 12px;
|
|
286
|
+
font-weight: 700;
|
|
287
|
+
line-height: 1;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.tab-btn.is-active .tab-btn-icon {
|
|
291
|
+
background: rgba(255, 255, 255, 0.18);
|
|
292
|
+
color: #ffffff;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.tab-btn-label {
|
|
296
|
+
min-width: 0;
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
text-overflow: ellipsis;
|
|
299
|
+
white-space: nowrap;
|
|
300
|
+
}
|
|
301
|
+
|
|
213
302
|
.tab-panel {
|
|
214
303
|
display: none;
|
|
215
304
|
}
|
|
@@ -318,6 +407,17 @@ body {
|
|
|
318
407
|
border-color: #d8dee8;
|
|
319
408
|
}
|
|
320
409
|
|
|
410
|
+
.tab-btn.is-active.is-disabled {
|
|
411
|
+
background: #2359d1;
|
|
412
|
+
color: #fff;
|
|
413
|
+
border-color: #2359d1;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.tab-btn.is-active.is-disabled .tab-btn-icon {
|
|
417
|
+
background: rgba(255, 255, 255, 0.18);
|
|
418
|
+
color: #ffffff;
|
|
419
|
+
}
|
|
420
|
+
|
|
321
421
|
.market-layout {
|
|
322
422
|
display: grid;
|
|
323
423
|
grid-template-columns: 1fr 340px;
|
|
@@ -1137,6 +1237,24 @@ button.is-loading::before {
|
|
|
1137
1237
|
}
|
|
1138
1238
|
|
|
1139
1239
|
@media (max-width: 980px) {
|
|
1240
|
+
.app-layout {
|
|
1241
|
+
grid-template-columns: 1fr;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.sidebar-nav {
|
|
1245
|
+
position: static;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.app-shell.sidebar-collapsed .app-layout {
|
|
1249
|
+
grid-template-columns: 1fr;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
.app-shell.sidebar-collapsed .sidebar-head strong,
|
|
1253
|
+
.app-shell.sidebar-collapsed .tab-group-label,
|
|
1254
|
+
.app-shell.sidebar-collapsed .tab-btn:not(.is-active) {
|
|
1255
|
+
display: initial;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1140
1258
|
.positions-grid {
|
|
1141
1259
|
grid-template-columns: 1fr;
|
|
1142
1260
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=t=>t&&"object"==typeof t?t:{},e=(t,o="")=>{if(null==t)return t;const r=String(o||"");if("string"==typeof t)return/(password|token|authorization|api_?key|secret|cookie|jwt)/i.test(r)?t.length<=8?"****":`${t.slice(0,3)}****${t.slice(-3)}`:t.length>2e3?`${t.slice(0,2e3)}...(truncated)`:t;if("number"==typeof t||"boolean"==typeof t)return t;if(Array.isArray(t)){const o=t.slice(0,50).map(t=>e(t,r));return t.length>50&&o.push(`...(${t.length-50} more items)`),o}if("object"==typeof t){const o={};for(const[r,i]of Object.entries(t))o[r]=e(i,r);return o}return String(t)};export const createAuthApiService=({getEnv:o,emitLog:r,getCurrentSession:i})=>{const n=async e=>{const o=await e.text();if(!o)return{};try{const e=JSON.parse(o);return t(e)}catch{return{raw:o}}};return{signInWithLoginApi:async(e,i)=>{const s=o();if(!s.loginApiUrl)throw new Error("Missing loginApiUrl in local config");const l=new AbortController,a=setTimeout(()=>l.abort(),15e3);let c;r("[auth] login api request",{url:s.loginApiUrl,email:e});try{c=await fetch(s.loginApiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,password:i,client:"bot"}),signal:l.signal})}catch(e){const o=t(t(e).cause),r=o?`${String(o.code||"")} ${String(o.syscall||"")} ${String(o.hostname||"")}`.trim():"";throw new Error("login fetch failed"+(r?`: ${r}`:""))}finally{clearTimeout(a)}r("[auth] login api response",{status:c.status});const p=await n(c);if(!c.ok)throw new Error(String(p.error||p.message||p.msg||"Login failed"));return((e,o)=>{const r=t(e),i=t(r.data),n=t(r.user),s=t(i.user),l=r.access_token||r.token||r.jwt||i.access_token||i.token;if(!l)throw new Error("Login API response missing token field (access_token/token/jwt)");const a=r.user_id||n.id||r.sub||i.user_id||s.id||"",c=r.email||n.email||i.email||s.email||o,p=r.chat_model||i.chat_model||"gpt-4
|
|
1
|
+
const t=t=>t&&"object"==typeof t?t:{},e=(t,o="")=>{if(null==t)return t;const r=String(o||"");if("string"==typeof t)return/(password|token|authorization|api_?key|secret|cookie|jwt)/i.test(r)?t.length<=8?"****":`${t.slice(0,3)}****${t.slice(-3)}`:t.length>2e3?`${t.slice(0,2e3)}...(truncated)`:t;if("number"==typeof t||"boolean"==typeof t)return t;if(Array.isArray(t)){const o=t.slice(0,50).map(t=>e(t,r));return t.length>50&&o.push(`...(${t.length-50} more items)`),o}if("object"==typeof t){const o={};for(const[r,i]of Object.entries(t))o[r]=e(i,r);return o}return String(t)};export const createAuthApiService=({getEnv:o,emitLog:r,getCurrentSession:i})=>{const n=async e=>{const o=await e.text();if(!o)return{};try{const e=JSON.parse(o);return t(e)}catch{return{raw:o}}};return{signInWithLoginApi:async(e,i)=>{const s=o();if(!s.loginApiUrl)throw new Error("Missing loginApiUrl in local config");const l=new AbortController,a=setTimeout(()=>l.abort(),15e3);let c;r("[auth] login api request",{url:s.loginApiUrl,email:e});try{c=await fetch(s.loginApiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,password:i,client:"bot"}),signal:l.signal})}catch(e){const o=t(t(e).cause),r=o?`${String(o.code||"")} ${String(o.syscall||"")} ${String(o.hostname||"")}`.trim():"";throw new Error("login fetch failed"+(r?`: ${r}`:""))}finally{clearTimeout(a)}r("[auth] login api response",{status:c.status});const p=await n(c);if(!c.ok)throw new Error(String(p.error||p.message||p.msg||"Login failed"));return((e,o)=>{const r=t(e),i=t(r.data),n=t(r.user),s=t(i.user),l=r.access_token||r.token||r.jwt||i.access_token||i.token;if(!l)throw new Error("Login API response missing token field (access_token/token/jwt)");const a=r.user_id||n.id||r.sub||i.user_id||s.id||"",c=r.email||n.email||i.email||s.email||o,p=r.chat_model||i.chat_model||"gpt-5.4",u=r.chat_system_prompt||i.chat_system_prompt||"";return{accessToken:String(l),userId:String(a),email:String(c),chatModel:String(p),chatSystemPrompt:String(u)}})(p,e)},callBotApi:async(t,o)=>{if(!t)throw new Error("bot control api url is missing");const s=i();if(!s?.accessToken)throw new Error("bot session access token missing");r("[bot-api] request",{url:t,bot_id:s.botId||"",body:e(o)});const l=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s.accessToken}`},body:JSON.stringify(o||{})}),a=await n(l);if(r("[bot-api] response",{url:t,status:l.status,ok:l.ok,body:e(a)}),!l.ok)throw new Error(String(a.error||a.message||`api failed: ${l.status}`));return a},requestBotImageUploadUrl:async({filename:t,contentType:s})=>{const l=(()=>{const t=o();return t.botUploadApiUrl?t.botUploadApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/upload-url"):""})();if(!l)throw new Error("Missing upload/login api url in local config");const a=i();if(!a?.accessToken)throw new Error("bot session access token missing");r("[bot-api] request",{url:l,bot_id:a.botId||"",body:e({bot_id:a.botId||"",filename:t,content_type:s})});const c=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a.accessToken}`},body:JSON.stringify({bot_id:a.botId||"",filename:t,content_type:s})}),p=await n(c);if(r("[bot-api] response",{url:l,status:c.status,ok:c.ok,body:e(p)}),!c.ok)throw new Error(String(p.error||p.message||"failed to request bot upload url"));if(!p.upload_url||!p.preview_url)throw new Error("upload url api response missing upload_url or preview_url");return{upload_url:String(p.upload_url),preview_url:String(p.preview_url),file_url:p.file_url?String(p.file_url):void 0}},uploadImageToSignedUrl:async({uploadUrl:t,contentType:e,bytes:o})=>{const r=await fetch(t,{method:"PUT",headers:{"Content-Type":e},body:o});if(!r.ok){const t=await r.text();throw new Error(`s3 upload failed: ${r.status} ${t}`)}},getBotRuntimeConfigApiUrl:()=>{const t=o();return t.botRuntimeConfigApiUrl?t.botRuntimeConfigApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/runtime-config"):""},getBotSkillsListApiUrl:()=>{const t=o();return t.botSkillsListApiUrl?t.botSkillsListApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/skills/list"):""}}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
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 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const e=new Set(["active"]);export const createSkillHostState=({log:a})=>{let t=new Map,s=new Map;const i=e=>t.get(String(e||"").trim())||null,r=a=>{const t=i(a);return t?Boolean(t.enabled)&&e.has(String(t.install_status||"").trim().toLowerCase()):s.has(String(a||"").trim())},n=()=>Array.from(s.values()).filter(e=>r(e.name));return{setAvailableSkills:e=>{s=new Map((Array.isArray(e)?e:[]).filter(e=>e&&"object"==typeof e&&String(e.name||"").trim()).map(e=>[String(e.name||"").trim(),e])),a?.("[skills] available skills updated",{count:s.size,skills:Array.from(s.keys())})},getAvailableSkills:()=>Array.from(s.values()),getActiveSkills:n,getManagedSkills:()=>{const e=new Set([...Array.from(s.keys()),...Array.from(t.keys())]);return Array.from(e).map(e=>{const a=s.get(e),i=t.get(e);return{name:e,displayName:a?.displayName||i?.skill_id||e,version:a?.version||i?.version||"unknown",description:a?.description,permissions:Array.isArray(a?.permissions)?[...a.permissions]:[],tools:Array.isArray(a?.tools)?[...a.tools]:[],hasUi:Boolean(a?.hasUi),enabled:r(e),installStatus:i?String(i.install_status||"active"):s.has(e)?"active":"uninstalled",configuredVersion:i?.version??a?.version??null,configJson:i?.config_json&&"object"==typeof i.config_json?i.config_json:{},installed:s.has(e)}})},getStateForSkill:i,isSkillEnabled:r,applySnapshots:e=>{t=new Map((Array.isArray(e)?e:[]).filter(e=>e&&"object"==typeof e&&String(e.package_name||"").trim()).map(e=>[String(e.package_name||"").trim(),e])),a?.("[skills] host state updated",{available:s.size,active:n().map(e=>e.name)})}}};
|
|
@@ -0,0 +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())}};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai.weget.jp/bot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "WeGet bot host for Codex-centered skill runtime ",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"build": "npm run build:ts"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ai.weget.jp/skill-
|
|
32
|
-
"@ai.weget.jp/skill-gmo-
|
|
33
|
-
"@ai.weget.jp/skill-gmo-fx": ">=0.1.
|
|
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
34
|
"@ai.weget.jp/skill-sdk": ">=0.1.1 <1",
|
|
35
35
|
"electron": "^31.7.7",
|
|
36
36
|
"ws": "^8.18.3"
|
package/scripts/build-ts.cjs
CHANGED
|
@@ -11,6 +11,7 @@ try {
|
|
|
11
11
|
|
|
12
12
|
const root = process.cwd();
|
|
13
13
|
const srcDir = path.join(root, 'src');
|
|
14
|
+
const distDir = path.join(root, 'dist');
|
|
14
15
|
const buildSrcDir = path.join(root, 'dist', 'src');
|
|
15
16
|
|
|
16
17
|
const runTsc = () => {
|
|
@@ -86,6 +87,7 @@ const minifyBuildJs = async () => {
|
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
const main = async () => {
|
|
90
|
+
await fs.rm(distDir, { recursive: true, force: true });
|
|
89
91
|
runTsc();
|
|
90
92
|
await copyRendererAssets();
|
|
91
93
|
await copyFileIfExists(path.join(srcDir, 'preload.cjs'), path.join(buildSrcDir, 'preload.cjs'));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createGmoCoinExecutionWsService as e,createGmoGateway as t,createGmoMarketDataService as i,createTradeConfigService as r,createTradeStatusService as o,defaultGmoRuntimeConfig as s}from"@ai.weget.jp/skill-gmo-core";const n=e=>e&&"object"==typeof e?e:{},a=e=>{const t=Number(String(e??"").trim().replace(/,/g,""));return Number.isFinite(t)&&t>0?t:null},c=e=>"SELL"===String(e||"").trim().toUpperCase()?"SELL":"BUY",l=e=>{const t=n(e);return{market:"coin",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),settleType:String(t.settleType||"").toUpperCase()||"OPEN",executionPrice:t.executionPrice??t.price??"",executionSize:t.executionSize??t.size??"",executionId:t.executionId??null,orderId:t.orderId??null,executionTimestamp:String(t.executionTimestamp||t.timestamp||"")}};export const createGmoCoinRuntime=({configPath:p,emitLog:m,emitTradeExecution:g,emitTradeHistory:u,getRuntimeConfig:d=s})=>{const P=r({configPath:p,emitLog:m}),x=t({cryptoApiBaseUrl:d().cryptoApiBaseUrl,fxApiBaseUrl:d().fxApiBaseUrl,assetsPath:d().assetsPath,fxAssetsPath:d().fxAssetsPath,coinMarginPath:d().coinMarginPath,positionsPath:d().positionsPath,positionSummaryPath:d().positionSummaryPath,fxOrderPath:d().fxOrderPath,coinCloseOrderPath:d().coinCloseOrderPath,fxCloseOrderPath:d().fxCloseOrderPath});x.setRuntimeConfigProvider(d),x.setCredentialsProvider(P.getCredentials),x.setLogEmitter(m);const y=i({getCoinPublicBaseUrl:()=>d().coinPublicBaseUrl,getFxPublicBaseUrl:()=>d().fxPublicBaseUrl}),C=o({gateway:x,env:{getRiskDailyLossLimitJpy:()=>P.get().riskDailyLossLimitJpy},emitLog:m}),S=e({getCoinPrivateApiBaseUrl:()=>d().cryptoApiBaseUrl,getCoinCredentials:()=>P.getCredentials("crypto"),emitLog:m,onExecutionEvent:async e=>{const t=n(e),i=(e=>{const t=n(e);return{category:"trade_history",market:"coin",kind:"CLOSE"===String(t.settleType||"").toUpperCase()?"close":"new",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),size:String(t.executionSize??t.orderSize??"").trim(),price:a(t.executionPrice??t.price),price_source:null!==a(t.executionPrice??t.price)?"execution_event":"none",result:t,ts:(new Date).toISOString()}})(t);u?.(i),m("[trade] coin execution received",l(t)),g?.(l(t))}});return{tradeConfig:P,gateway:x,marketData:y,status:C,loadConfig:()=>P.load(),getConfig:()=>P.get(),saveConfig:async e=>P.save(e),startExecutionStreams:()=>S.start(),stopExecutionStreams:()=>S.stop(),restartExecutionStreams:()=>S.restart()}};
|
package/dist/src/gmoFxRuntime.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createGmoFxExecutionWsService as e,createGmoGateway as t,createGmoMarketDataService as i,createTradeConfigService as r,createTradeStatusService as s,defaultGmoRuntimeConfig as o}from"@ai.weget.jp/skill-gmo-core";const a=e=>e&&"object"==typeof e?e:{},n=e=>{const t=Number(String(e??"").trim().replace(/,/g,""));return Number.isFinite(t)&&t>0?t:null},c=e=>"SELL"===String(e||"").trim().toUpperCase()?"SELL":"BUY",l=e=>{const t=a(e);return{market:"fx",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),settleType:String(t.settleType||"").toUpperCase()||"OPEN",executionPrice:t.executionPrice??t.price??"",executionSize:t.executionSize??t.size??"",executionId:t.executionId??null,orderId:t.orderId??null,executionTimestamp:String(t.executionTimestamp||t.timestamp||"")}};export const createGmoFxRuntime=({configPath:m,emitLog:p,emitTradeExecution:g,emitTradeHistory:u,getRuntimeConfig:x=o})=>{const d=r({configPath:m,emitLog:p}),P=t({cryptoApiBaseUrl:x().cryptoApiBaseUrl,fxApiBaseUrl:x().fxApiBaseUrl,assetsPath:x().assetsPath,fxAssetsPath:x().fxAssetsPath,coinMarginPath:x().coinMarginPath,positionsPath:x().positionsPath,positionSummaryPath:x().positionSummaryPath,fxOrderPath:x().fxOrderPath,coinCloseOrderPath:x().coinCloseOrderPath,fxCloseOrderPath:x().fxCloseOrderPath});P.setRuntimeConfigProvider(x),P.setCredentialsProvider(d.getCredentials),P.setLogEmitter(p);const f=i({getCoinPublicBaseUrl:()=>x().coinPublicBaseUrl,getFxPublicBaseUrl:()=>x().fxPublicBaseUrl}),y=s({gateway:P,env:{getRiskDailyLossLimitJpy:()=>d.get().riskDailyLossLimitJpy},emitLog:p}),S=e({getFxPrivateApiBaseUrl:()=>x().fxApiBaseUrl,getFxCredentials:()=>d.getCredentials("fx"),emitLog:p,onExecutionEvent:async e=>{const t=a(e),i=(e=>{const t=a(e);return{category:"trade_history",market:"fx",kind:"CLOSE"===String(t.settleType||"").toUpperCase()?"close":"new",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),size:String(t.executionSize??t.orderSize??"").trim(),price:n(t.executionPrice??t.price),price_source:null!==n(t.executionPrice??t.price)?"execution_event":"none",result:t,ts:(new Date).toISOString()}})(t);u?.(i),p("[trade] fx execution received",l(t)),g?.(l(t))}});return{tradeConfig:d,gateway:P,marketData:f,status:y,loadConfig:()=>d.load(),getConfig:()=>d.get(),saveConfig:async e=>d.save(e),startExecutionStreams:()=>S.start(),stopExecutionStreams:()=>S.stop(),restartExecutionStreams:()=>S.restart()}};
|