@ai.weget.jp/bot 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # @ai.weget.jp/bot
2
2
 
3
- WeGet Bot host package (Electron + CLI).
3
+ Local Electron host for running Codex-based WeGet bots.
4
+
5
+ ## Responsibility
6
+
7
+ This package is the local runtime host. It is responsible for:
8
+
9
+ - local Codex login state
10
+ - local bot UI
11
+ - receiving `run_task` dispatches
12
+ - running Codex with the provided runtime config
13
+ - loading installed skill packages
14
+ - reporting runtime callbacks
15
+
16
+ It does not manage platform data or server-side control-plane state.
4
17
 
5
18
  ## Install
6
19
 
@@ -10,57 +23,33 @@ npm i -g @ai.weget.jp/bot
10
23
 
11
24
  ## Quick Start
12
25
 
13
- 1. Login to Codex on the local machine:
26
+ 1. Sign in to Codex on the local machine:
14
27
 
15
28
  ```bash
16
29
  codex login --device-auth
17
30
  ```
18
31
 
19
- You can also open the local `Host Control` page and use `Start Login`.
20
-
21
- 2. Open the local UI:
32
+ 2. Start the local UI:
22
33
 
23
34
  ```bash
24
35
  weget-bot ui
25
36
  ```
26
37
 
27
- 3. Sign in the bot to the private control plane from the local login screen.
28
-
29
- ## What The Host Owns
30
-
31
- - local Codex auth state
32
- - Bot WebSocket connection
33
- - local skill pages for installed packages
34
- - local GMO credentials and default model
35
-
36
- The host does not store or receive OpenAI keys from Platform.
37
-
38
- AI interaction is routed through local Codex only.
39
-
40
- Codex MCP integration is moving toward a single `weget-gateway` entry instead of
41
- direct per-tool MCP registration.
42
-
43
- Default built-in endpoints:
44
-
45
- - `loginApiUrl=https://api.weget.jp/login`
46
- - `botUploadApiUrl=https://api.weget.jp/bot/upload-url`
47
- - `gmoCryptoPrivateApiBaseUrl=https://api.coin.z.com/private`
48
- - `gmoFxPrivateApiBaseUrl=https://forex-api.coin.z.com/private`
49
- - `aiModel=gpt-5.4`
50
-
51
- ## Local UI Structure
38
+ 3. Connect the host and configure local skills from the UI.
52
39
 
53
- - `Host Control`: local runtime, Codex auth, default model, installed skill state, local credentials
54
- - `Codex Console`: local chat with Codex
55
- - `Installed Skills`: per-skill pages such as `skill-gmo-coin`, `skill-gmo-fx`, `skill-browser`
40
+ ## Built-in Runtime Scope
56
41
 
57
- ## MCP Direction
42
+ - skill loading
43
+ - local host pages
44
+ - bot websocket connection
45
+ - runtime callback submission
46
+ - local configuration for supported skills
58
47
 
59
- - preferred Codex MCP entry: `weget-gateway`
60
- - browser runtime prerequisite: `npx playwright install chromium`
61
- - legacy direct `playwright` MCP registration is being phased out in favor of the gateway
62
- - macro tools now prefer a local host bridge path:
63
- `Codex -> weget-gateway -> bot-host bridge -> skill runtime -> protected API`
48
+ ## Related Packages
64
49
 
65
- The package is the public runtime host. Private control plane behavior remains
66
- in `platform/` and `infra/`.
50
+ - `@ai.weget.jp/skill-sdk`
51
+ - `@ai.weget.jp/skill-browser`
52
+ - `@ai.weget.jp/skill-gmo-fx`
53
+ - `@ai.weget.jp/skill-gmo-coin`
54
+ - `@ai.weget.jp/skill-macro-economy`
55
+ - `@ai.weget.jp/weget-gateway-mcp`
package/dist/src/main.js CHANGED
@@ -1 +1 @@
1
- import t from"node:path";import e from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import{createRequire as s}from"node:module";import{SYSTEM_CONFIG as r}from"./config/systemConfig.js";import{BotWsClient as o}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as g}from"./services/codexService.js";import{createGatewayFileBridgeService as c}from"./services/gatewayFileBridgeService.js";import{createPlatformApiClient as d}from"./platformApi.js";import{createSkillRuntimeManager as m}from"./skillRuntimeManager.js";import{createWindowManager as p}from"./services/windowManagerService.js";import{createSkillHostState as u}from"./skillHostState.js";const f=s(import.meta.url),{app:y,BrowserWindow:S,ipcMain:w,nativeImage:k}=f("electron"),h=a(import.meta.url),b=t.dirname(h),v=["trade","ai"],_=()=>t.join(y.getPath("userData"),"login-profile.json"),x=()=>t.join(y.getPath("userData"),"host-config.json"),C=()=>t.join(y.getPath("userData"),"skill-states.json");let A=null,M=null,P={aiModel:"gpt-5.4",logOutputDir:""};const E=new Map;let O=()=>t.join(y.getPath("documents"),"weget-bot-logs");const D=t=>{const e=String(t||"").toLowerCase();return e.includes("debug")||e.includes("[debug]")?"debug":e.includes("error")||e.includes("failed")||e.includes("exception")?"error":"info"},j=async({level:i,source:a,message:s,details:r,ts:o})=>{const n=O();await e.mkdir(n,{recursive:!0});const l=new Date,g=l.getFullYear(),c=String(l.getMonth()+1).padStart(2,"0"),d=String(l.getDate()).padStart(2,"0"),m=t.join(n,`${i}-${g}${c}${d}.log`),p=JSON.stringify({ts:o||l.toISOString(),level:i,source:a,message:s,details:r??null},null,0);await e.appendFile(m,`${p}\n`,"utf8")},I=p({BrowserWindow:S,preloadPath:t.join(b,"preload.cjs"),rendererDir:t.join(b,"renderer"),windowIconPath:t.join(b,"renderer","logo.png")}),L=(t,e)=>{const i=(new Date).toISOString();I.emitBotLog({message:t,data:e||null,ts:i}),j({level:D(t),source:"runtime",message:t,details:e,ts:i}).catch(()=>{})},B=t=>{I.emitChatEvent(t)},T=t=>t&&"object"==typeof t?t:{},U=u({log:L}),N=l({getEnv:()=>({loginApiUrl:r.loginApiUrl,botUploadApiUrl:r.botUploadApiUrl}),emitLog:L,getCurrentSession:()=>A}),F=d({getBaseUrl:()=>String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),getSession:()=>A,log:L}),G=m({installRoot:t.join(y.getPath("userData"),"skills"),botId:()=>String(A?.botId||"").trim()||void 0,log:L,api:F,getRuntimeConfig:()=>({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:r.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:r.gmoFxPublicApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath}),emitSkillEvent:(t,e)=>{if("trade_execution"!==e.type){if("trade_history"===e.type){const i=`trade-${Date.now()}`;M?.sendTaskEvent(i,"trade_history",{package_name:t,...e.payload&&"object"==typeof e.payload?e.payload:{}}),L("[trade] history event sent (execution)",{package_name:t,payload:e.payload})}}else I.emitTradeExecution(e.payload)}}),K=()=>{const t=G.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime;if(!t)throw new Error("GMO FX skill runtime is not installed");return t},R=()=>{const t=G.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime;if(!t)throw new Error("GMO Coin skill runtime is not installed");return t},W=()=>G.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime,J=()=>G.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime,$=()=>{const t=G.getLoadedSkill("@ai.weget.jp/skill-macro-economy")?.runtime;if(!t)throw new Error("Macro Economy skill runtime is not installed");return t},z=()=>G.getLoadedSkill("@ai.weget.jp/skill-macro-economy")?.runtime,q=(t,e)=>{const i=t.gateway[e];if("function"!=typeof i)throw new Error(`skill runtime does not implement gateway.${e}`);return i},H=t=>{const e=T(t);return{riskDailyLossLimitJpy:Number(e.riskDailyLossLimitJpy||0),fxApiKey:String(e.fxApiKey||"").trim(),fxApiSecret:String(e.fxApiSecret||"").trim(),aiModel:String(e.aiModel||"").trim()||"gpt-5.4"}},Q=t=>{const e=T(t);return{riskDailyLossLimitJpy:Number(e.riskDailyLossLimitJpy||0),cryptoApiKey:String(e.cryptoApiKey||"").trim(),cryptoApiSecret:String(e.cryptoApiSecret||"").trim(),aiModel:String(e.aiModel||"").trim()||"gpt-5.4"}};O=()=>String(P.logOutputDir||"").trim()||t.join(y.getPath("documents"),"weget-bot-logs");const X=g({getModel:()=>{const t=W(),e=J();return P.aiModel||t?.getConfig?.().aiModel||e?.getConfig?.().aiModel||"gpt-5.4"},getWorkingDir:()=>y.getPath("userData"),getLogDir:()=>O(),emitLog:L,getGatewayContext:()=>({activeSkills:U.getManagedSkills().filter(t=>t.enabled).map(t=>({name:t.name,displayName:t.displayName,version:t.version,tools:t.tools})),hostMetadata:{aiModel:P.aiModel||"gpt-5.4",capabilities:v,userDataPath:y.getPath("userData"),platform:process.platform,gmoCoinPublicBaseUrl:r.gmoCryptoPublicApiBaseUrl,gmoFxPublicBaseUrl:r.gmoFxPublicApiBaseUrl},botId:String(A?.botId||"").trim(),platformApiBaseUrl:String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),accessToken:String(A?.accessToken||"").trim(),fileBridgeDir:String(rt.getContext()?.dir||"").trim(),fileBridgeToken:String(rt.getContext()?.token||"").trim()})}),Y=async t=>{const i=T(t);return P={aiModel:String(i.aiModel||P.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i.logOutputDir||"").trim()},await e.writeFile(x(),JSON.stringify({...P,updatedAt:(new Date).toISOString()},null,2),"utf8"),P};const V=async()=>{const t=await G.refreshLoadedSkills();return U.setAvailableSkills(t.map(t=>t.manifest)),t},Z=async t=>{const e=await V();L("[skills] reconciled bundled skills",{desired:t.map(t=>({package_name:t.package_name,install_status:t.install_status,enabled:t.enabled,version:t.version})),bundled:e.map(t=>({package_name:t.packageName,version:t.version}))})},tt=()=>{const t=U.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),e=U.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"),i=W(),a=J();i&&("connected"===M?.getStatus()&&t?i.startExecutionStreams():i.stopExecutionStreams()),a&&("connected"===M?.getStatus()&&e?a.startExecutionStreams():a.stopExecutionStreams()),L("[skills] applied managed states",{active_skills:U.getActiveSkills().map(t=>t.name)})},et=async()=>{const t=N.getBotRuntimeConfigApiUrl(),e=String(A?.botId||"").trim();if(!t||!e)return;const i=await N.callBotApi(t,{bot_id:e}),a=String(i.chat_model||"").trim(),s={...a?{aiModel:a}:{}};if(0===Object.keys(s).length)return;await Y({...P,...s});const r=W(),o=J();r&&await r.saveConfig({...r.getConfig(),...s}),o&&await o.saveConfig({...o.getConfig(),...s}),L("[config] runtime config synced",{chat_model:a||""})},it=async()=>{const t=N.getBotSkillsListApiUrl(),i=String(A?.botId||"").trim();if(!t||!i)return;const a=await N.callBotApi(t,{bot_id:i}),s=Array.isArray(a.states)?a.states.filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:T(t.config_json),created_at:t.created_at?String(t.created_at):void 0,updated_at:t.updated_at?String(t.updated_at):void 0})):[];U.applySnapshots(s),await(async t=>{await e.writeFile(C(),JSON.stringify({updatedAt:(new Date).toISOString(),items:t},null,2),"utf8")})(s),await Z(s),tt(),L("[config] skill states synced",{count:s.length,bot_id:i,enabled:s.filter(t=>t.enabled).length})},at=t=>{if("fx"===t&&!U.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===t&&!U.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")},st=()=>{if(!U.isSkillEnabled("@ai.weget.jp/skill-macro-economy"))throw new Error("Macro Economy skill is disabled")},rt=c({getMacroSnapshot:async({force:t}={})=>(st(),$().getSnapshot({force:t})),getMacroCalendar:async({dateKey:t})=>(st(),$().listCalendar({dateKey:t})),getMacroNews:async({dateKey:t})=>(st(),$().listNews({dateKey:t})),emitLog:L,getWorkingDir:()=>y.getPath("userData")}),ot={"@ai.weget.jp/skill-gmo-fx":{getConfig:()=>H(K().getConfig()),saveConfig:async t=>{const e=K(),i=await e.saveConfig({...e.getConfig(),...H(t)});return"connected"===M?.getStatus()&&tt(),H(i)}},"@ai.weget.jp/skill-gmo-coin":{getConfig:()=>Q(R().getConfig()),saveConfig:async t=>{const e=R(),i=await e.saveConfig({...e.getConfig(),...Q(t)});return"connected"===M?.getStatus()&&tt(),Q(i)}}},nt=t=>{const e=String(t||"").trim(),i=ot[e];if(!i)throw new Error(`skill config is not managed by bot host: ${t}`);return i},lt=async()=>{await G.ensureInstallRoot(),await V();const t=await async function(){try{const t=await e.readFile(C(),"utf8"),i=JSON.parse(t);return(Array.isArray(i?.items)?i.items:[]).filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:T(t.config_json),created_at:t.created_at?String(t.created_at):void 0,updated_at:t.updated_at?String(t.updated_at):void 0}))}catch(t){if("ENOENT"===t?.code)return[];throw t}}();t.length>0&&(U.applySnapshots(t),L("[skills] restored local snapshot",{count:t.length}),await Z(t)),tt()};M=new o({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:v,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(t,e)=>L(t,e),onStatus:t=>{I.emitBotStatus({status:t}),tt()},onLineMessage:async t=>{const e=String(t.line_user_id||""),i=String(t.text||"").trim();if(B({type:"line_user",lineUserId:e,text:i}),!i)return{answer:""};try{const t=await X.runPrompt({prompt:i,contextLabel:"line_message"});return B({type:"line_assistant",lineUserId:e,text:t}),{answer:t}}catch(t){const i=t instanceof Error?t.message:String(t);L("[ai] line reply failed",{error:i});const a=`AI error: ${i}`;return B({type:"line_assistant",lineUserId:e,text:a}),{answer:a}}},onRunTask:async t=>{const e=String(t.task_id||"").trim(),i=String(t.prompt||"").trim();if(e){if(E.set(e,{cancelled:!1}),M?.sendTaskEvent(e,"task_received",{prompt:i,conversation_id:String(t.conversation_id||""),continue_from_task_id:String(t.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return M?.sendTaskEvent(e,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void E.delete(e);try{M?.sendTaskEvent(e,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const t=await X.runPrompt({prompt:i,contextLabel:`task:${e}`}),a=E.get(e);if(a?.cancelled)return M?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void E.delete(e);M?.sendTaskEvent(e,"ai_output",{summary:t,ts:(new Date).toISOString()}),M?.sendTaskEvent(e,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),M?.sendTaskEvent(e,"task_completed",{summary:t.slice(0,500),ts:(new Date).toISOString()})}catch(t){M?.sendTaskEvent(e,"task_failed",{error:t instanceof Error?t.message:String(t),ts:(new Date).toISOString()})}finally{E.delete(e)}}else L("[task] run_task missing task_id")},onTaskControl:async t=>{const e=String(t.task_id||"").trim(),i=String(t.action||"").trim();if(e&&i&&"cancel_task"===i){const t=E.get(e);if(t)return t.cancelled=!0,void L("[task] cancel requested",{task_id:e});M?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()})}},onServerConfigUpdate:async t=>{const e=Array.isArray(t.scopes)?t.scopes.map(t=>String(t||"").trim()):[],i=e.length>0?e:["runtime","skills"];i.includes("runtime")&&await et(),i.includes("skills")&&await it()}}),n({ipcMain:w,env:{capabilities:v,loginApiUrl:r.loginApiUrl},signInWithLoginApi:N.signInWithLoginApi,saveLoginProfile:async({botId:t,email:i,password:a,remember:s})=>{if(s)await e.writeFile(_(),JSON.stringify({botId:t,email:i,password:a,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await e.unlink(_())}catch(t){if("ENOENT"!==t?.code)throw t}},readSavedLoginProfile:async()=>{try{const t=await e.readFile(_(),"utf8"),i=JSON.parse(t);return{botId:String(i?.botId||""),email:String(i?.email||""),password:String(i?.password||""),remember:Boolean(i?.remember)}}catch(t){if("ENOENT"===t?.code)return{botId:"",email:"",password:"",remember:!1};throw t}},getSkillConfig:t=>({config:nt(t).getConfig()}),saveSkillConfig:async(t,e)=>({config:await nt(t).saveConfig(e)}),getAiConfig:()=>({aiModel:String(P.aiModel||"gpt-5.4"),logOutputDir:String(P.logOutputDir||"")}),saveAiConfig:async t=>{const e=t&&"object"==typeof t?t:{},i=await Y({...P,...e}),a=W(),s=J(),r=a?await a.saveConfig({...a.getConfig(),...e}):null;return s&&await s.saveConfig({...s.getConfig(),...e}),{aiModel:String(i.aiModel||r?.aiModel||e.aiModel||"gpt-5.4"),logOutputDir:String(i.logOutputDir||"")}},setCurrentSession:t=>{A=t||null;const e=A?"connected":"disconnected";I.emitBotStatus({status:e})},getCurrentSession:()=>A,getActiveSkills:()=>U.getActiveSkills().map(t=>({name:t.name,displayName:t.displayName,version:t.version})),getCodexAuthStatus:()=>X.getAuthStatus(),getCodexMcpStatus:t=>X.getMcpServerStatus(t),getPlaywrightBrowserStatus:()=>X.getPlaywrightBrowserStatus(),startCodexLogin:()=>X.startLogin(),installPlaywrightMcp:()=>X.installWegetGatewayMcp(),getGatewayContextFilePath:()=>X.getGatewayContextFilePath(),runGatewaySelfTest:t=>X.runGatewaySelfTest(t),getManagedSkills:()=>U.getManagedSkills().map(t=>({...t,ui:G.getLoadedSkill(t.name)?.ui||void 0})),getMacroSnapshot:async({force:t}={})=>(st(),$().getSnapshot({force:t})),getMacroCalendar:async({dateKey:t})=>(st(),$().listCalendar({dateKey:t})),getMacroNews:async({dateKey:t})=>(st(),$().listNews({dateKey:t})),emitLog:L,openGmoKlineWindow:async({symbol:t,interval:e,market:i})=>(()=>{const a="fx"===i?"fx":"coin";return at(a),("fx"===a?K().marketData:R().marketData).fetchKline({symbol:t,interval:e,market:a})})(),getGmoMarketQuotes:async({market:t,symbols:e})=>(()=>{const i="fx"===t?"fx":"coin";return at(i),("fx"===i?K().marketData:R().marketData).fetchQuotes({market:i,symbols:e})})(),getGmoSymbolRules:async({market:t})=>(()=>{const e="fx"===t?"fx":"coin";return at(e),("fx"===e?K().marketData:R().marketData).fetchSymbolRules({market:e})})(),getGmoBuyingPower:async({market:t})=>({market:t,availableJpy:await(at(t),("fx"===t?K().gateway:R().gateway).getBuyingPower(t))}),getGmoAccountMetrics:async({market:t})=>(at(t),("fx"===t?K().gateway:R().gateway).getAccountMetrics(t)),getGmoPositionSummary:async({market:t,symbol:e})=>({market:t,symbol:e,items:await(at(t),("fx"===t?K().gateway:R().gateway).getPositionSummary({market:t,symbol:e}))}),getGmoOpenPositions:async({market:t,symbol:e,page:i,prevId:a,count:s})=>({market:t,symbol:e||"",items:await(at(t),("fx"===t?K().gateway:R().gateway).getOpenPositions({market:"fx"===t?"fx":"crypto",symbol:e||void 0,page:i,prevId:a,count:s}))}),placeGmoFxOrder:async({symbol:t,side:e,size:i})=>{at("fx");return await q(K(),"placeFxOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:t,side:e,size:i})=>{at("coin");return await q(R(),"placeCoinOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{at("fx");return await q(K(),"placeFxCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{at("coin");return await q(R(),"placeCoinCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},getGmoApiState:t=>{const e="fx"===t?W():J();return e?.status.getApiState()||"unknown"},testGmoApi:t=>(at(t),"fx"===t?K().status.testApi():R().status.testApi()),buildTradeStatusSnapshot:t=>(at(t),"fx"===t?K().status.buildSnapshot():R().status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:t=>{M?.start(t),et().catch(t=>{L("[config] runtime sync failed after login",{error:t instanceof Error?t.message:String(t)})}),it().catch(t=>{L("[config] skill sync failed after login",{error:t instanceof Error?t.message:String(t)})}),tt()},stopWsClient:()=>{W()?.stopExecutionStreams(),J()?.stopExecutionStreams(),z()?.stop?.(),M?.stop()},handleBotChatMessage:async({text:t})=>{B({type:"user",text:String(t||"")}),B({type:"assistant_status",status:"Preparing request"});const e=await X.runPrompt({prompt:t,contextLabel:"desktop_chat",onStatus:t=>B({type:"assistant_status",status:t})});return B({type:"assistant",text:e}),{answer:e}},writeErrorLog:async({source:t,message:e,details:i})=>{await(async({source:t,message:e,details:i})=>{await j({level:"error",source:t,message:e,details:i})})({source:t,message:e,details:i})},writeRuntimeLog:async({level:t,source:e,message:i,details:a})=>{await j({level:t,source:e,message:i,details:a})}});const gt=t.join(b,"renderer","logo.png");y.whenReady().then(async()=>{(()=>{if("darwin"!==process.platform)return;if(!i(gt))return;const t=k.createFromPath(gt);t.isEmpty()||y.dock.setIcon(t)})(),await(async()=>{try{const t=await e.readFile(x(),"utf8"),i=JSON.parse(t);P={aiModel:String(i?.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i?.logOutputDir||"").trim()}}catch(t){if("ENOENT"!==t?.code)throw t;P={aiModel:"gpt-5.4",logOutputDir:""}}return P})();try{await rt.ensureStarted()}catch(t){L("[gateway-file-bridge] startup failed",{error:t instanceof Error?t.message:String(t)})}try{await lt()}catch(t){L("[skills] local initialization failed",{error:t instanceof Error?t.message:String(t)})}I.createMainWindow(),y.on("activate",()=>{0===S.getAllWindows().length&&I.createMainWindow()})}),y.on("window-all-closed",()=>{W()?.stopExecutionStreams(),J()?.stopExecutionStreams(),z()?.stop?.(),rt.stop().catch(()=>{}),M?.stop(),"darwin"!==process.platform&&y.quit()});
1
+ import t from"node:path";import e from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import{createRequire as s}from"node:module";import{SYSTEM_CONFIG as r}from"./config/systemConfig.js";import{BotWsClient as n}from"./wsClient.js";import{registerIpcHandlers as o}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as g}from"./services/codexService.js";import{createGatewayFileBridgeService as c}from"./services/gatewayFileBridgeService.js";import{createPlatformApiClient as d}from"./platformApi.js";import{createSkillRuntimeManager as m}from"./skillRuntimeManager.js";import{createWindowManager as p}from"./services/windowManagerService.js";import{createSkillHostState as u}from"./skillHostState.js";const S=s(import.meta.url),{app:y,BrowserWindow:f,ipcMain:w,nativeImage:k}=S("electron"),_=a(import.meta.url),b=t.dirname(_),h=["trade","ai"],v=()=>t.join(y.getPath("userData"),"login-profile.json"),x=()=>t.join(y.getPath("userData"),"host-config.json"),A=()=>t.join(y.getPath("userData"),"skill-states.json");let C=null,M=null,E={aiModel:"gpt-5.4",logOutputDir:""};const O=new Map;let j=()=>t.join(y.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"},D=async({level:i,source:a,message:s,details:r,ts:n})=>{const o=j();await e.mkdir(o,{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(o,`${i}-${g}${c}${d}.log`),p=JSON.stringify({ts:n||l.toISOString(),level:i,source:a,message:s,details:r??null},null,0);await e.appendFile(m,`${p}\n`,"utf8")},I=p({BrowserWindow:f,preloadPath:t.join(b,"preload.cjs"),rendererDir:t.join(b,"renderer"),windowIconPath:t.join(b,"renderer","logo.png")}),B=(t,e)=>{const i=(new Date).toISOString();I.emitBotLog({message:t,data:e||null,ts:i}),D({level:P(t),source:"runtime",message:t,details:e,ts:i}).catch(()=>{})},U=t=>{I.emitChatEvent(t)},L=t=>t&&"object"==typeof t?t:{},T=u({log:B}),N=l({getEnv:()=>({loginApiUrl:r.loginApiUrl,botUploadApiUrl:r.botUploadApiUrl}),emitLog:B,getCurrentSession:()=>C}),F=d({getBaseUrl:()=>String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),getSession:()=>C,log:B}),G=async t=>{const e=(()=>{const t=String(r.loginApiUrl||"").trim();return t?t.replace(/\/login\/?$/i,"/bot/runtime/line-reply"):""})();if(!e)throw new Error("runtime line reply api url is missing");return N.callBotApi(e,t)},R=m({installRoot:t.join(y.getPath("userData"),"skills"),botId:()=>String(C?.botId||"").trim()||void 0,log:B,api:F,getRuntimeConfig:()=>({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:r.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:r.gmoFxPublicApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath}),emitSkillEvent:(t,e)=>{if("trade_execution"!==e.type){if("trade_history"===e.type){const i=`trade-${Date.now()}`;M?.sendTaskEvent(i,"trade_history",{package_name:t,...e.payload&&"object"==typeof e.payload?e.payload:{}}),B("[trade] history event sent (execution)",{package_name:t,payload:e.payload})}}else I.emitTradeExecution(e.payload)}}),K=()=>{const t=R.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime;if(!t)throw new Error("GMO FX skill runtime is not installed");return t},$=()=>{const t=R.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime;if(!t)throw new Error("GMO Coin skill runtime is not installed");return t},W=()=>R.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime,J=()=>R.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime,z=()=>{const t=R.getLoadedSkill("@ai.weget.jp/skill-macro-economy")?.runtime;if(!t)throw new Error("Macro Economy skill runtime is not installed");return t},q=()=>R.getLoadedSkill("@ai.weget.jp/skill-macro-economy")?.runtime,H=(t,e)=>{const i=t.gateway[e];if("function"!=typeof i)throw new Error(`skill runtime does not implement gateway.${e}`);return i},Q=t=>{const e=L(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"}},X=t=>{const e=L(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"}};j=()=>String(E.logOutputDir||"").trim()||t.join(y.getPath("documents"),"weget-bot-logs");const Y=g({getModel:()=>{const t=W(),e=J();return E.aiModel||t?.getConfig?.().aiModel||e?.getConfig?.().aiModel||"gpt-5.4"},getWorkingDir:()=>y.getPath("userData"),getLogDir:()=>j(),emitLog:B,getGatewayContext:()=>({activeSkills:T.getManagedSkills().filter(t=>t.enabled).map(t=>({name:t.name,displayName:t.displayName,version:t.version,tools:t.tools})),hostMetadata:{aiModel:E.aiModel||"gpt-5.4",capabilities:h,userDataPath:y.getPath("userData"),platform:process.platform,gmoCoinPublicBaseUrl:r.gmoCryptoPublicApiBaseUrl,gmoFxPublicBaseUrl:r.gmoFxPublicApiBaseUrl},botId:String(C?.botId||"").trim(),platformApiBaseUrl:String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),accessToken:String(C?.accessToken||"").trim(),fileBridgeDir:String(nt.getContext()?.dir||"").trim(),fileBridgeToken:String(nt.getContext()?.token||"").trim()})}),V=async t=>{const i=L(t);return E={aiModel:String(i.aiModel||E.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i.logOutputDir||"").trim()},await e.writeFile(x(),JSON.stringify({...E,updatedAt:(new Date).toISOString()},null,2),"utf8"),E};const Z=async()=>{const t=await R.refreshLoadedSkills();return T.setAvailableSkills(t.map(t=>t.manifest)),t},tt=async t=>{const e=await Z();B("[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}))})},et=()=>{const t=T.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),e=T.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"),i=W(),a=J();i&&("connected"===M?.getStatus()&&t?i.startExecutionStreams():i.stopExecutionStreams()),a&&("connected"===M?.getStatus()&&e?a.startExecutionStreams():a.stopExecutionStreams()),B("[skills] applied managed states",{active_skills:T.getActiveSkills().map(t=>t.name)})},it=async()=>{const t=N.getBotRuntimeConfigApiUrl(),e=String(C?.botId||"").trim();if(!t||!e)return;const i=await N.callBotApi(t,{bot_id:e}),a=String(i.chat_model||"").trim(),s={...a?{aiModel:a}:{}};if(0===Object.keys(s).length)return;await V({...E,...s});const r=W(),n=J();r&&await r.saveConfig({...r.getConfig(),...s}),n&&await n.saveConfig({...n.getConfig(),...s}),B("[config] runtime config synced",{chat_model:a||""})},at=async()=>{const t=N.getBotSkillsListApiUrl(),i=String(C?.botId||"").trim();if(!t||!i)return;const a=await N.callBotApi(t,{bot_id:i}),s=Array.isArray(a.states)?a.states.filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:L(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})):[];T.applySnapshots(s),await(async t=>{await e.writeFile(A(),JSON.stringify({updatedAt:(new Date).toISOString(),items:t},null,2),"utf8")})(s),await tt(s),et(),B("[config] skill states synced",{count:s.length,bot_id:i,enabled:s.filter(t=>t.enabled).length})},st=t=>{if("fx"===t&&!T.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===t&&!T.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")},rt=()=>{if(!T.isSkillEnabled("@ai.weget.jp/skill-macro-economy"))throw new Error("Macro Economy skill is disabled")},nt=c({getMacroSnapshot:async({force:t}={})=>(rt(),z().getSnapshot({force:t})),getMacroCalendar:async({dateKey:t})=>(rt(),z().listCalendar({dateKey:t})),getMacroNews:async({dateKey:t})=>(rt(),z().listNews({dateKey:t})),emitLog:B,getWorkingDir:()=>y.getPath("userData")}),ot={"@ai.weget.jp/skill-gmo-fx":{getConfig:()=>Q(K().getConfig()),saveConfig:async t=>{const e=K(),i=await e.saveConfig({...e.getConfig(),...Q(t)});return"connected"===M?.getStatus()&&et(),Q(i)}},"@ai.weget.jp/skill-gmo-coin":{getConfig:()=>X($().getConfig()),saveConfig:async t=>{const e=$(),i=await e.saveConfig({...e.getConfig(),...X(t)});return"connected"===M?.getStatus()&&et(),X(i)}}},lt=t=>{const e=String(t||"").trim(),i=ot[e];if(!i)throw new Error(`skill config is not managed by bot host: ${t}`);return i},gt=async()=>{await R.ensureInstallRoot(),await Z();const t=await async function(){try{const t=await e.readFile(A(),"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:L(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&&(T.applySnapshots(t),B("[skills] restored local snapshot",{count:t.length}),await tt(t)),et()};M=new n({wsUrl:"wss://ws.weget.jp",taskEventApiUrl:String(r.loginApiUrl||"").trim().replace(/\/login\/?$/i,"/bot/runtime/task-event"),version:"0.2.0",capabilities:h,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(t,e)=>B(t,e),onStatus:t=>{I.emitBotStatus({status:t}),et()},onLineMessage:async t=>{const e=String(t.line_user_id||""),i=String(t.text||"").trim();if(U({type:"line_user",lineUserId:e,text:i}),!i)return{answer:""};try{const t=await Y.runPrompt({prompt:i,contextLabel:"line_message"});return U({type:"line_assistant",lineUserId:e,text:t}),{answer:t}}catch(t){const i=t instanceof Error?t.message:String(t);B("[ai] line reply failed",{error:i});const a=`AI error: ${i}`;return U({type:"line_assistant",lineUserId:e,text:a}),{answer:a}}},onRunTask:async t=>{const e=String(t.task_id||"").trim(),i=String(t.prompt||"").trim(),a=L(t.metadata_json),s=L(t.prompt_bundle),r=L(t.runtime_config),n=String(a.source||s.source||"").trim(),o=String(a.channel||s.channel||"").trim(),l=String(t.skill_id||a.skill_id||s.skill_id||"").trim(),g=String(a.line_user_id||"").trim(),c=String(a.line_reply_token||"").trim(),d="line"===n||"line"===o||Boolean(g&&c),m="bot_host"===n,p=String(t.model||r.chat_model||"").trim(),u=String(r.system_prompt||s.system_prompt||"").trim();if(e){if(O.set(e,{cancelled:!1}),d&&g&&i&&U({type:"line_user",lineUserId:g,text:i}),M?.sendTaskEvent(e,"task_received",{prompt:i,conversation_id:String(t.conversation_id||""),continue_from_task_id:String(t.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return M?.sendTaskResult(e,"failed",{error:"prompt is required",data_json:{ts:(new Date).toISOString()}}),void O.delete(e);try{m&&U({type:"assistant_status",status:"Task received"}),l&&M?.sendSkillStatus(e,{skill_id:l,status:"running",summary:"task execution started",data_json:{ts:(new Date).toISOString()}}),M?.sendTaskEvent(e,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const a=await Y.runPrompt({prompt:i,model:p||void 0,systemPrompt:u||void 0,contextLabel:`task:${e}`,onStatus:m?t=>U({type:"assistant_status",status:t}):void 0}),s=O.get(e);if(s?.cancelled)return M?.sendTaskResult(e,"cancelled",{summary:"server_cancelled",data_json:{reason:"server_cancelled",ts:(new Date).toISOString()}}),void O.delete(e);if(M?.sendTaskEvent(e,"ai_output",{summary:a,ts:(new Date).toISOString()}),m&&U({type:"assistant",text:a}),d&&g&&c){U({type:"line_assistant",lineUserId:g,text:a});const i={bot_id:String(t.bot_id||C?.botId||""),line_user_id:g,line_reply_token:c,text:a};try{await G(i)}catch(t){B("[line] callback line reply failed",{error:t instanceof Error?t.message:String(t)})}M?.sendTaskEvent(e,"line_message_ai_output",{line_user_id:g,answer:a,ts:(new Date).toISOString()})}M?.sendTaskEvent(e,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),M?.sendTaskResult(e,"succeeded",{summary:a.slice(0,500),data_json:{ts:(new Date).toISOString()}}),l&&M?.sendSkillStatus(e,{skill_id:l,status:"succeeded",summary:a.slice(0,200),data_json:{ts:(new Date).toISOString()}})}catch(i){if(m){const t=`Error: ${i instanceof Error?i.message:String(i)}`;U({type:"assistant",text:t})}if(d&&g&&c){const e=`AI error: ${i instanceof Error?i.message:String(i)}`;U({type:"line_assistant",lineUserId:g,text:e});const a={bot_id:String(t.bot_id||C?.botId||""),line_user_id:g,line_reply_token:c,text:e};try{await G(a)}catch(t){B("[line] callback line reply failed",{error:t instanceof Error?t.message:String(t)})}}M?.sendTaskResult(e,"failed",{error:i instanceof Error?i.message:String(i),data_json:{ts:(new Date).toISOString()}}),l&&M?.sendSkillStatus(e,{skill_id:l,status:"failed",summary:i instanceof Error?i.message:String(i),data_json:{ts:(new Date).toISOString()}})}finally{O.delete(e)}}else B("[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 i=O.get(e);if(i){i.cancelled=!0;const a=String(t.skill_id||"").trim();return a&&M?.sendSkillStatus(e,{skill_id:a,status:"cancelled",summary:"server_cancelled",data_json:{ts:(new Date).toISOString()}}),void B("[task] cancel requested",{task_id:e})}M?.sendTaskResult(e,"cancelled",{summary:"server_cancelled",data_json:{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 it(),i.includes("skills")&&await at()}}),o({ipcMain:w,env:{capabilities:h,loginApiUrl:r.loginApiUrl},signInWithLoginApi:N.signInWithLoginApi,saveLoginProfile:async({botId:t,email:i,password:a,remember:s})=>{if(s)await e.writeFile(v(),JSON.stringify({botId:t,email:i,password:a,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await e.unlink(v())}catch(t){if("ENOENT"!==t?.code)throw t}},readSavedLoginProfile:async()=>{try{const t=await e.readFile(v(),"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:lt(t).getConfig()}),saveSkillConfig:async(t,e)=>({config:await lt(t).saveConfig(e)}),getAiConfig:()=>({aiModel:String(E.aiModel||"gpt-5.4"),logOutputDir:String(E.logOutputDir||"")}),saveAiConfig:async t=>{const e=t&&"object"==typeof t?t:{},i=await V({...E,...e}),a=W(),s=J(),r=a?await a.saveConfig({...a.getConfig(),...e}):null;return s&&await s.saveConfig({...s.getConfig(),...e}),{aiModel:String(i.aiModel||r?.aiModel||e.aiModel||"gpt-5.4"),logOutputDir:String(i.logOutputDir||"")}},setCurrentSession:t=>{C=t||null;const e=C?"connected":"disconnected";I.emitBotStatus({status:e})},getCurrentSession:()=>C,getActiveSkills:()=>T.getActiveSkills().map(t=>({name:t.name,displayName:t.displayName,version:t.version})),getCodexAuthStatus:()=>Y.getAuthStatus(),getCodexMcpStatus:t=>Y.getMcpServerStatus(t),getPlaywrightBrowserStatus:()=>Y.getPlaywrightBrowserStatus(),startCodexLogin:()=>Y.startLogin(),installPlaywrightMcp:()=>Y.installWegetGatewayMcp(),getGatewayContextFilePath:()=>Y.getGatewayContextFilePath(),runGatewaySelfTest:t=>Y.runGatewaySelfTest(t),getManagedSkills:()=>T.getManagedSkills().map(t=>({...t,ui:R.getLoadedSkill(t.name)?.ui||void 0})),getMacroSnapshot:async({force:t}={})=>(rt(),z().getSnapshot({force:t})),getMacroCalendar:async({dateKey:t})=>(rt(),z().listCalendar({dateKey:t})),getMacroNews:async({dateKey:t})=>(rt(),z().listNews({dateKey:t})),emitLog:B,openGmoKlineWindow:async({symbol:t,interval:e,market:i})=>(()=>{const a="fx"===i?"fx":"coin";return st(a),("fx"===a?K().marketData:$().marketData).fetchKline({symbol:t,interval:e,market:a})})(),getGmoMarketQuotes:async({market:t,symbols:e})=>(()=>{const i="fx"===t?"fx":"coin";return st(i),("fx"===i?K().marketData:$().marketData).fetchQuotes({market:i,symbols:e})})(),getGmoSymbolRules:async({market:t})=>(()=>{const e="fx"===t?"fx":"coin";return st(e),("fx"===e?K().marketData:$().marketData).fetchSymbolRules({market:e})})(),getGmoBuyingPower:async({market:t})=>({market:t,availableJpy:await(st(t),("fx"===t?K().gateway:$().gateway).getBuyingPower(t))}),getGmoAccountMetrics:async({market:t})=>(st(t),("fx"===t?K().gateway:$().gateway).getAccountMetrics(t)),getGmoPositionSummary:async({market:t,symbol:e})=>({market:t,symbol:e,items:await(st(t),("fx"===t?K().gateway:$().gateway).getPositionSummary({market:t,symbol:e}))}),getGmoOpenPositions:async({market:t,symbol:e,page:i,prevId:a,count:s})=>({market:t,symbol:e||"",items:await(st(t),("fx"===t?K().gateway:$().gateway).getOpenPositions({market:"fx"===t?"fx":"crypto",symbol:e||void 0,page:i,prevId:a,count:s}))}),placeGmoFxOrder:async({symbol:t,side:e,size:i})=>{st("fx");return await H(K(),"placeFxOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:t,side:e,size:i})=>{st("coin");return await H($(),"placeCoinOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{st("fx");return await H(K(),"placeFxCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{st("coin");return await H($(),"placeCoinCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},getGmoApiState:t=>{const e="fx"===t?W():J();return e?.status.getApiState()||"unknown"},testGmoApi:t=>(st(t),"fx"===t?K().status.testApi():$().status.testApi()),buildTradeStatusSnapshot:t=>(st(t),"fx"===t?K().status.buildSnapshot():$().status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:t=>{M?.start(t),it().catch(t=>{B("[config] runtime sync failed after login",{error:t instanceof Error?t.message:String(t)})}),at().catch(t=>{B("[config] skill sync failed after login",{error:t instanceof Error?t.message:String(t)})}),et()},stopWsClient:()=>{W()?.stopExecutionStreams(),J()?.stopExecutionStreams(),q()?.stop?.(),M?.stop()},handleBotChatMessage:async({text:t})=>{const e=String(t||"").trim(),i=String(C?.botId||"").trim();if(!e)throw new Error("message is required");if(!i)throw new Error("bot session is not ready");U({type:"assistant_status",status:"Submitting task"});const a=await F.request({method:"POST",path:"/bot/tasks",body:{source:"bot_host",bot_id:i,prompt:e,title:e.slice(0,80),channel:"desktop",metadata_json:{source:"bot_host",channel:"desktop"}}}),s=L(a.dispatch_result);if(!1===s.delivered)throw new Error(`task queued but bot is offline (${String(s.status||"offline")})`);return{answer:"",taskId:String(L(a.task).task_id||"")}},writeErrorLog:async({source:t,message:e,details:i})=>{await(async({source:t,message:e,details:i})=>{await D({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 D({level:t,source:e,message:i,details:a})}});const ct=t.join(b,"renderer","logo.png");y.whenReady().then(async()=>{(()=>{if("darwin"!==process.platform)return;if(!i(ct))return;const t=k.createFromPath(ct);t.isEmpty()||y.dock.setIcon(t)})(),await(async()=>{try{const t=await e.readFile(x(),"utf8"),i=JSON.parse(t);E={aiModel:String(i?.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i?.logOutputDir||"").trim()}}catch(t){if("ENOENT"!==t?.code)throw t;E={aiModel:"gpt-5.4",logOutputDir:""}}return E})();try{await nt.ensureStarted()}catch(t){B("[gateway-file-bridge] startup failed",{error:t instanceof Error?t.message:String(t)})}try{await gt()}catch(t){B("[skills] local initialization failed",{error:t instanceof Error?t.message:String(t)})}I.createMainWindow(),y.on("activate",()=>{0===f.getAllWindows().length&&I.createMainWindow()})}),y.on("window-all-closed",()=>{W()?.stopExecutionStreams(),J()?.stopExecutionStreams(),q()?.stop?.(),nt.stop().catch(()=>{}),M?.stop(),"darwin"!==process.platform&&y.quit()});
@@ -1 +1 @@
1
- import t from"node:fs/promises";import{existsSync as e}from"node:fs";import{createRequire as r}from"node:module";import s from"node:os";import o from"node:path";import{spawn as a,spawnSync as n}from"node:child_process";const i="gpt-5.4",c=r(import.meta.url);let d=null,l=null,g=null,u=null,m=null,w=null;const p=async t=>{if(!t)return"";const e=[];for await(const r of t)e.push(Buffer.isBuffer(r)?r:Buffer.from(String(r)));return Buffer.concat(e).toString("utf8")},f=t=>String(t||"").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,""),h=t=>{let e="";return{push:r=>{if(!t)return;e+=f(Buffer.isBuffer(r)?r.toString("utf8"):String(r));const s=e.split(/\r?\n/);e=s.pop()||"";for(const e of s){const r=String(e||"").trim();r&&t(r)}},flush:()=>{if(!t)return;const r=String(e||"").trim();r&&t(r),e=""}}},x=t=>{const e=f(String(t||"")).trim();return e?/^[{}[\],:]+$/.test(e)||/^["']?[a-zA-Z0-9_-]+["']?\s*:\s*$/.test(e)||/^`{3,}|^#{1,6}\s*$/.test(e)||/^Assistant:?\s*$/i.test(e)||/^Warning: no last agent message/i.test(e)?"":/^ERROR:\s*unexpected status\s+\d+/i.test(e)?"Tool call failed":e:""},y=(t,e)=>{const r=f(String(t||"")).replace(/\s+/g," ").trim();return r?r.length>180?`${r.slice(0,177)}...`:r:e},S=e=>{const r=(new Date).toISOString(),s=`exec-${Date.now()}`;let a="",n="",i=!1,c=Promise.resolve();const d=[],l=async()=>{if(i)return n;const r=o.join(e,"codex-sessions");await t.mkdir(r,{recursive:!0});const c=String(a||s||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_");return n=o.join(r,`${c}.log`),i=!0,d.length>0&&(await t.appendFile(n,d.join(""),"utf8"),d.length=0),n},g=(e,r={})=>{(e=>{c=c.then(async()=>{if(!i&&!a)return void d.push(e);const r=await l();await t.appendFile(r,e,"utf8")}).catch(()=>{})})(`${JSON.stringify({ts:(new Date).toISOString(),event:e,...r},null,0)}\n`)};return{get startedAt(){return r},setSessionId:t=>{const e=String(t||"").trim();e&&a!==e&&(a=e,g("session_identified",{sessionId:e}))},write:g,flush:async()=>(i||await l(),await c,n)}},C=()=>{if(l)return l;const t=[o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","npx.cmd"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","npx.cmd"),o.join(String(process.env.APPDATA||""),"npm","npx.cmd")].filter(Boolean);for(const r of t)if(e(r))return l=r,r;const r=n("where.exe",["npx.cmd"],{windowsHide:!0,encoding:"utf8"}),s=String(r.stdout||"").split(/\r?\n/).map(t=>t.trim()).find(Boolean);return s?(l=s,s):"npx.cmd"},_=()=>{if(g)return g;const t=[o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","node.exe"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","node.exe")];for(const r of t)if(e(r))return g=r,r;return g="node",g},P=()=>{if(u)return u;try{const t=c.resolve("@ai.weget.jp/weget-gateway-mcp/dist/index.js");return u=t,t}catch{}const t=[o.join(String(process.env.APPDATA||""),"npm","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js")].filter(Boolean);for(const r of t)if(e(r))return u=r,r;return u=o.join(String(process.env.APPDATA||""),"npm","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),u},v=t=>{const e=o.join(t,"weget-gateway-context.json");return"win32"===process.platform?{command:_(),args:[P(),"--context-file",e]}:{command:"npx",args:["-y","@ai.weget.jp/weget-gateway-mcp@latest","--context-file",e]}},k=t=>String(t||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),$=(t,e)=>{const r=String(t||""),s=v(e);if("win32"===process.platform){const t=new RegExp(`command:\\s+${k(s.command)}`,"i"),e=new RegExp(k(String(s.args[0]||"")),"i"),o=new RegExp(k(String(s.args[2]||"")),"i");return t.test(r)&&e.test(r)&&o.test(r)}return new RegExp(`command:\\s+${k(s.command)}`,"i").test(r)&&r.includes(String(s.args[0]||""))},O=async({args:t,stdinText:r,cwd:s,extraEnv:i,onStdoutLine:c,onStderrLine:l})=>new Promise((g,u)=>{const m=(()=>{if(d)return d;if("win32"===process.platform){const t=[String(process.env.ProgramFiles||"C:\\Program Files"),String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)")];for(const r of t){const t=o.join(r,"nodejs","node.exe"),s=o.join(r,"nodejs","node_modules","@openai","codex","bin","codex.js");if(e(t)&&e(s))return d={command:t,baseArgs:[s]},d}const r=["codex.cmd","codex"];for(const t of r){const e=n("where.exe",[t],{windowsHide:!0,encoding:"utf8"}),r=String(e.stdout||"").split(/\r?\n/).map(t=>t.trim()).find(Boolean);if(r)return d={command:r,baseArgs:[]},d}}return d={command:"codex",baseArgs:[]},d})(),w={...process.env,...i||{},NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"},f=a(m.command,[...m.baseArgs,...t],{cwd:s,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:w});let x=!1;const y=t=>{x||(x=!0,t())},S=p(f.stdout),C=p(f.stderr),_=h(c),P=h(l);f.stdout?.on("data",t=>_.push(t)),f.stderr?.on("data",t=>P.push(t)),f.on("error",t=>{y(()=>u(t))}),f.on("close",async t=>{_.flush(),P.flush();const[e,r]=await Promise.all([S,C]);y(()=>g({stdout:e,stderr:r,exitCode:Number.isFinite(t)?Number(t):1}))}),"string"==typeof r&&f.stdin.write(r),f.stdin.end()}),E=async({command:t,args:e,cwd:r})=>new Promise((s,o)=>{const n=a(t,e,{cwd:r,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:{...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"}});let i=!1;const c=t=>{i||(i=!0,t())},d=p(n.stdout),l=p(n.stderr);n.on("error",t=>c(()=>o(t))),n.on("close",async t=>{const[e,r]=await Promise.all([d,l]);c(()=>s({stdout:e,stderr:r,exitCode:Number.isFinite(t)?Number(t):1}))})}),j=async t=>null!==m?m:(w||(w=(async(t,e)=>{const r=String(t||"").trim();if(!r)return!1;try{const t=await O({cwd:e,args:["features","list"]});return 0===t.exitCode&&f(`${t.stdout||""}\n${t.stderr||""}`).split(/\r?\n/).map(t=>t.trim()).some(t=>t.startsWith(`${r} `)||t===r)}catch{return!1}})("rmcp_client",t).then(t=>(m=t,t)).finally(()=>{w=null})),w),A=async({cwd:t,model:e,outputPath:r})=>{const s=["exec"];return await j(t)&&s.push("--enable","rmcp_client"),s.push("--dangerously-bypass-approvals-and-sandbox","-C",t,"--skip-git-repo-check","-m",e,"-o",r,"-"),s};export const createCodexService=({getModel:e,getWorkingDir:r,getLogDir:n,emitLog:c,getGatewayContext:d})=>{const l=()=>{const t=d?.()||{},e={WEGET_ACTIVE_SKILLS:JSON.stringify(Array.isArray(t.activeSkills)?t.activeSkills:[]),WEGET_HOST_METADATA:JSON.stringify(t.hostMetadata&&"object"==typeof t.hostMetadata?t.hostMetadata:{})},r=String(t.botId||"").trim(),s=String(t.platformApiBaseUrl||"").trim(),o=String(t.accessToken||"").trim(),a=String(t.fileBridgeDir||"").trim(),n=String(t.fileBridgeToken||"").trim();return r&&(e.WEGET_BOT_ID=r),s&&(e.WEGET_PLATFORM_API_BASE_URL=s),o&&(e.WEGET_PLATFORM_API_ACCESS_TOKEN=o),a&&(e.WEGET_FILE_BRIDGE_DIR=a),n&&(e.WEGET_FILE_BRIDGE_TOKEN=n),e},g=()=>o.join(r(),"weget-gateway-context.json"),u=async()=>{const e=d?.()||{};await t.mkdir(r(),{recursive:!0}),await t.writeFile(g(),JSON.stringify({activeSkills:Array.isArray(e.activeSkills)?e.activeSkills:[],hostMetadata:e.hostMetadata&&"object"==typeof e.hostMetadata?e.hostMetadata:{},botId:String(e.botId||"").trim(),platformApiBaseUrl:String(e.platformApiBaseUrl||"").trim(),accessToken:String(e.accessToken||"").trim(),fileBridgeDir:String(e.fileBridgeDir||"").trim(),fileBridgeToken:String(e.fileBridgeToken||"").trim(),logDir:n(),updatedAt:(new Date).toISOString()},null,2),"utf8")},m=async({target:e,payload:r})=>{const s=o.join(n(),"gateway-tests");await t.mkdir(s,{recursive:!0});const a=o.join(s,`${e}-${Date.now()}.log`);return await t.writeFile(a,`${JSON.stringify(r,null,2)}\n`,"utf8"),a},w=async({userPrompt:t,contextLabel:r})=>{const s=d?.()||{},o=Array.isArray(s.activeSkills)?s.activeSkills:[],a=s.hostMetadata&&"object"==typeof s.hostMetadata?s.hostMetadata:{},[n,c]=await Promise.all([p("weget-gateway"),f()]);return["You are running inside WeGet Bot Host.","","Active skills:",...o.length?o.map(t=>{const e=Array.isArray(t.tools)&&t.tools.length?` tools=${t.tools.join(", ")}`:"",r=String(t.version||"").trim()?` v${String(t.version||"").trim()}`:"";return`- ${String(t.name||"").trim()}${r}${e}`}):["- none"],"","Runtime status:",...[`- weget-gateway MCP: ${n.status}`,`- browser runtime: ${c.status}`,`- context: ${String(r||"").trim()||"general"}`,`- default model: ${String(a.aiModel||e()||i).trim()||i}`],"","Tool policy:","- Prefer direct reasoning for simple text-only requests.","- When the user asks about macro conditions, economic situation, calendar, or news, prefer the WeGet macro tools before summarizing.","- Use WeGet browser tools only for website access, page extraction, navigation, or screenshots.","- If browser runtime or gateway MCP is unavailable, state that limitation explicitly instead of pretending the task was executed.","- For trading or any action with side effects, do not claim execution unless a concrete tool call actually performed it.","- Keep the final answer plain text only.",...(t=>{const e=String(t||"").trim().toLowerCase();return!!e&&[/macro/,/economic/,/economy/,/calendar/,/news/,/usd\s*\/\s*jpy/,/usd_jpy/,/risk sentiment/,/economic situation/,/macro snapshot/,/经济/,/宏观/,/日历/,/新闻/,/形势/,/経済/,/マクロ/,/ニュース/,/カレンダー/,/相場/].some(t=>t.test(e))})(t)?["","Macro tool routing hint:","- This request matches macro intent.","- Before answering, use the WeGet macro tools to fetch snapshot, calendar, and news if available.","- Prefer gateway tool calls such as weget_macro_get_snapshot, weget_macro_list_calendar, and weget_macro_list_news.","- Base the summary on fetched tool results rather than unsupported memory."]:[],"","User request:",String(t||"").trim(),"","Respond as plain text only."].join("\n")},p=async t=>{const e=String(t||"").trim();if(!e)return{status:"unknown",detail:"MCP server name is required"};try{"weget-gateway"===e&&await u().catch(()=>{});const t=await O({cwd:r(),args:["mcp","get",e],extraEnv:l()}),s=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?/No MCP server named/i.test(s)?{status:"missing",detail:s}:{status:"unknown",detail:s||"codex mcp list failed"}:"playwright"!==e||(t=>{const e=String(t||"");return"win32"===process.platform?/command:\s+.*node(?:\.exe)?/i.test(e)&&/@playwright[\\\/]mcp[\\\/]cli\.js/i.test(e)&&/args:\s+.*--headless/i.test(e):/command:\s+npx/i.test(e)&&/@playwright\/mcp@latest/i.test(e)&&/args:\s+.*--headless/i.test(e)})(s)?"weget-gateway"!==e||$(s,r())?{status:"configured",detail:s||`${e} is configured`}:{status:"missing",detail:"WeGet Gateway MCP exists but points to an outdated launch path. Reinstall it from the host UI."}:{status:"missing",detail:"Playwright MCP exists but uses an unsupported Windows wrapper. Click Install Playwright MCP to repair it."}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},f=async()=>{try{const t="win32"===process.platform?"powershell.exe":"npx",e="win32"===process.platform?["-NoProfile","-Command",`& '${C().replace(/'/g,"''")}' playwright install --list`]:["playwright","install","--list"],s=await E({command:t,args:e,cwd:r()}),o=String(s.stdout||s.stderr||"").trim();return 0!==s.exitCode?{status:"unknown",detail:o||"playwright install --list failed"}:/chromium-\d+/i.test(o)||/\\ms-playwright\\chromium-/i.test(o)?{status:"installed",detail:o}:{status:"missing",detail:o||"Chromium is not installed for Playwright."}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}};return{runPrompt:async({prompt:a,model:d,contextLabel:m,onStatus:p})=>{const f=String(d||e()||i).trim()||i,h=r();await u().catch(()=>{});const y=S(n()),C=await t.mkdtemp(o.join(s.tmpdir(),"weget-codex-")),_=o.join(C,"last-message.txt"),P=await w({userPrompt:String(a||"").trim(),contextLabel:m});y.write("exec_start",{model:f,cwd:h,context:String(m||"").trim(),prompt:String(a||"").trim(),instruction:P,outputPath:_,tempDir:C,gatewayContextFile:g(),traceStartedAt:y.startedAt}),p?.("Preparing request"),c("[codex] exec start",{model:f,cwd:h,context:String(m||"").trim()});try{const e=await A({cwd:h,model:f,outputPath:_}),r=await O({cwd:h,extraEnv:l(),args:e,stdinText:P,onStdoutLine:t=>{y.write("stdout_line",{line:t});const e=x(t);if(e)return/^re-connecting/i.test(e)||/checking tools|tool use|using tool|calling tool/i.test(e)?(y.write("status",{source:"stdout",status:e}),void p?.(e)):void(/reading data|fetching|loading|snapshot|calendar|news/i.test(e)&&(y.write("status",{source:"stdout",status:e}),p?.(e)))},onStderrLine:t=>{y.write("stderr_line",{line:t});const e=String(t||"").match(/session id:\s*([a-zA-Z0-9-]+)/i);e?.[1]&&y.setSessionId(e[1]);const r=x(t);if(r&&!/^--------$/.test(r)&&!/^user$/i.test(r))return/^OpenAI Codex/i.test(r)?(y.write("status",{source:"stderr",status:"Starting Codex"}),void p?.("Starting Codex")):void(/workdir:|model:|provider:|approval:|sandbox:|reasoning effort:|reasoning summaries:|session id:/i.test(r)||(y.write("status",{source:"stderr",status:r}),p?.(r)))}});y.write("status",{source:"host",status:"Finalizing reply"}),p?.("Finalizing reply");const s=await t.readFile(_,"utf8").catch(()=>"");if(0!==r.exitCode)throw y.write("exec_error",{exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,lastMessage:s}),new Error((r.stderr||r.stdout||`codex exec failed with code ${r.exitCode}`).trim());const o=String(s||r.stdout||"").trim();if(!o)throw y.write("exec_error",{exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,lastMessage:s,error:"codex returned empty output"}),new Error("codex returned empty output");return y.write("exec_completed",{exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,lastMessage:s,finalAnswer:o}),c("[codex] exec completed",{model:f,context:String(m||"").trim()}),o}catch(t){throw y.write("exec_exception",{error:t instanceof Error?t.message:String(t)}),t}finally{const e=await y.flush().catch(()=>"");e&&c("[codex] session trace written",{context:String(m||"").trim(),tracePath:e}),await t.rm(C,{recursive:!0,force:!0}).catch(()=>{})}},getAuthStatus:async()=>{try{const t=await O({cwd:r(),args:["login","status"]}),e=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?{status:"unknown",detail:e||"codex login status failed"}:/logged in/i.test(e)?{status:"logged_in",detail:e}:/not logged in|logged out/i.test(e)?{status:"logged_out",detail:e}:{status:"unknown",detail:e||"unknown codex auth state"}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},startLogin:async()=>{const e=r();try{if("win32"===process.platform){const r=String(e||"").replace(/'/g,"''"),n=o.join(s.tmpdir(),`weget-codex-login-${Date.now()}.ps1`),i=["$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)","chcp 65001 > $null",'$env:NO_COLOR = "1"','$env:FORCE_COLOR = "0"','$env:CLICOLOR = "0"','$env:LANG = "en_US.UTF-8"','$env:LC_ALL = "en_US.UTF-8"',`Set-Location -LiteralPath '${r}'`,"$stripAnsi = {"," param([string]$line)",' if ($null -eq $line) { return "" }',' return ($line -replace "`e\\[[0-9;?]*[ -/]*[@-~]", "")',"}","codex login --device-auth 2>&1 | ForEach-Object { & $stripAnsi ([string]$_) }"].join(`${s.EOL}`);await t.writeFile(n,i,"utf8");const c=["$script = @()",'$script += "-NoExit"','$script += "-ExecutionPolicy"','$script += "Bypass"','$script += "-File"',`$script += '${n.replace(/'/g,"''")}'`,`Start-Process -FilePath 'powershell.exe' -WorkingDirectory '${r}' -ArgumentList $script`].join("; ");return a("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",c],{cwd:e,stdio:"ignore",windowsHide:!0}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in a new PowerShell window with UTF-8 output."}}if("darwin"===process.platform){const t=`cd ${JSON.stringify(e)} && codex login --device-auth`;return a("open",["-a","Terminal",t],{cwd:e,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in Terminal."}}const r=`cd ${JSON.stringify(e)} && codex login --device-auth; exec $SHELL`,n=[["x-terminal-emulator",["-e","bash","-lc",r]],["gnome-terminal",["--","bash","-lc",r]],["konsole",["-e","bash","-lc",r]]];for(const[t,r]of n)try{return a(t,r,{cwd:e,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:`Started \`codex login --device-auth\` using ${t}.`}}catch{}return{ok:!1,detail:"No supported terminal launcher was found. Run `codex login --device-auth` manually."}}catch(t){const e=t instanceof Error?t.message:String(t);return c("[codex] login launch failed",{error:e}),{ok:!1,detail:e}}},getMcpServerStatus:p,installWegetGatewayMcp:async()=>{try{await u();const t=await p("weget-gateway");if("configured"===t.status&&$(t.detail,r()))return{ok:!0,detail:t.detail||"WeGet Gateway MCP is already configured."};const e=await O({cwd:r(),args:["mcp","remove","weget-gateway"],extraEnv:l()});if(0!==e.exitCode){const t=String(e.stdout||e.stderr||"").trim().toLowerCase();if(!(t.includes("no mcp server named")||t.includes("not found")||t.includes("no server named")))return{ok:!1,detail:String(e.stdout||e.stderr||"").trim()||"Failed to remove existing WeGet Gateway MCP entry."}}const s=v(r()),o=await O({cwd:r(),args:["mcp","add","weget-gateway",s.command,...s.args],extraEnv:l()}),a=String(o.stdout||o.stderr||"").trim();if(0!==o.exitCode)return{ok:!1,detail:a||"Failed to add WeGet Gateway MCP to Codex."};const n=await p("weget-gateway");return"configured"===n.status?{ok:!0,detail:n.detail||"WeGet Gateway MCP configured."}:{ok:!0,detail:a||"WeGet Gateway MCP add command completed, but verification returned no explicit entry."}}catch(t){const e=t instanceof Error?t.message:String(t);return c("[codex] weget gateway mcp install failed",{error:e}),{ok:!1,detail:e}}},getPlaywrightBrowserStatus:f,getGatewayContextFilePath:g,runGatewaySelfTest:async e=>{if("codex_macro"===e){const a=(new Date).toISOString().slice(0,10);await u().catch(()=>{});const c=S(n()),d=await t.mkdtemp(o.join(s.tmpdir(),"weget-codex-test-")),w=o.join(d,"last-message.txt"),p=["You are running a WeGet gateway chain self-test.","Use WeGet gateway MCP tools only.",`Call weget_macro_get_snapshot, weget_macro_list_calendar with dateKey=${a}, and weget_macro_list_news with dateKey=${a}.`,"If all three tool calls succeed, reply with exactly: OK macro chain","If any tool call fails, reply with exactly: NG macro chain: <short reason>","Respond as plain text only."].join("\n");c.write("exec_start",{model:i,cwd:r(),context:"gateway_self_test:codex_macro",prompt:"codex macro chain self-test",instruction:p,outputPath:w,tempDir:d,gatewayContextFile:g(),traceStartedAt:c.startedAt});try{const s=await A({cwd:r(),model:i,outputPath:w}),o=await O({cwd:r(),extraEnv:l(),args:s,stdinText:p,onStdoutLine:t=>{c.write("stdout_line",{line:t})},onStderrLine:t=>{c.write("stderr_line",{line:t});const e=String(t||"").match(/session id:\s*([a-zA-Z0-9-]+)/i);e?.[1]&&c.setSessionId(e[1])}}),n=await t.readFile(w,"utf8").catch(()=>""),u=String(n||o.stdout||"").trim(),f=String(o.stderr||""),h=String(o.stdout||""),x=/tool weget-gateway\.weget_macro_get_snapshot\(\{\}\)/i.test(f),S=new RegExp(`tool weget-gateway\\.weget_macro_list_calendar\\(\\{"dateKey":"${k(a)}"\\}\\)`,"i").test(f),C=new RegExp(`tool weget-gateway\\.weget_macro_list_news\\(\\{"dateKey":"${k(a)}"\\}\\)`,"i").test(f),_=/weget-gateway\.weget_macro_get_snapshot\(\{\}\) success/i.test(f),P=/weget-gateway\.weget_macro_list_calendar\(\{\"dateKey\":\"\d{4}-\d{2}-\d{2}\"\}\) success/i.test(f),v=/weget-gateway\.weget_macro_list_news\(\{\"dateKey\":\"\d{4}-\d{2}-\d{2}\"\}\) success/i.test(f),$=0===o.exitCode&&/^OK macro chain$/i.test(u)&&x&&S&&C&&_&&P&&v;c.write($?"exec_completed":"exec_error",{exitCode:o.exitCode,stdout:h,stderr:f,lastMessage:n,finalAnswer:u,invokedSnapshot:x,invokedCalendar:S,invokedNews:C,succeededSnapshot:_,succeededCalendar:P,succeededNews:v});const E=await c.flush().catch(()=>""),j={target:e,ok:$,summary:$?"Codex gateway macro chain OK":"Codex gateway macro chain failed",contextFilePath:g(),tracePath:E,codex:{exitCode:o.exitCode,stdout:h,stderr:f,finalAnswer:u},assertions:{invokedSnapshot:x,invokedCalendar:S,invokedNews:C,succeededSnapshot:_,succeededCalendar:P,succeededNews:v},ts:(new Date).toISOString()},F=await m({target:e,payload:j});return await t.rm(d,{recursive:!0,force:!0}).catch(()=>{}),{ok:$,summary:$?"Codex gateway macro chain OK":y(u||f,"Codex gateway macro chain failed"),detail:u||f||h,logPath:F,result:j}}catch(r){c.write("exec_exception",{error:r instanceof Error?r.message:String(r)});const s=await c.flush().catch(()=>""),o=r instanceof Error?r.message:String(r),a=await m({target:e,payload:{target:e,ok:!1,summary:"Codex gateway macro chain failed",contextFilePath:g(),tracePath:s,error:o,ts:(new Date).toISOString()}});return await t.rm(d,{recursive:!0,force:!0}).catch(()=>{}),{ok:!1,summary:y(o,"Codex gateway macro chain failed"),detail:o,logPath:a}}}await u();const c=v(r());let d,w=null;try{d=await E({command:c.command,args:[...c.args,"--self-test","--test-target",e],cwd:r()}),"gateway"===e&&(w=await(async({command:t,args:e,cwd:r,timeoutMs:s=2500})=>new Promise((o,n)=>{const i=a(t,e,{cwd:r,stdio:["ignore","pipe","pipe"],windowsHide:!0,env:{...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"}}),c=[],d=[];let l=!1;const g=t=>{l||(l=!0,o(t))};i.stdout.on("data",t=>c.push(Buffer.from(t))),i.stderr.on("data",t=>d.push(Buffer.from(t))),i.on("error",t=>{l||(l=!0,n(t))}),i.on("close",t=>{g({ok:!1,exitCode:Number.isFinite(t)?Number(t):1,stdout:Buffer.concat(c).toString("utf8"),stderr:Buffer.concat(d).toString("utf8"),responseText:""})}),setTimeout(()=>{const t=i.stdin;t?.write(`${JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"weget-gateway-probe",version:"0.1.0"}}})}\n`)},150),setTimeout(()=>{if(l)return;const t=Buffer.concat(c).toString("utf8"),e=Buffer.concat(d).toString("utf8"),r=t.trim(),s=/"result"\s*:/.test(r)&&/"protocolVersion"\s*:/.test(r);i.kill(),g({ok:s,exitCode:null,stdout:t,stderr:e,responseText:r})},Math.max(250,s))}))({command:c.command,args:c.args,cwd:r()}))}catch(t){const r=t instanceof Error?t.message:String(t),s=await m({target:e,payload:{target:e,launch:c,contextFilePath:g(),failure:"spawn_exception",error:r,ts:(new Date).toISOString()}});return{ok:!1,summary:y(r,`${e} test failed`),detail:r,logPath:s}}const p=String(d.stdout||d.stderr||"").trim();let f;if(p)try{f=JSON.parse(p)}catch{}let h=0===d.exitCode&&Boolean(!f||!1!==f.ok),x=y(p,`${e} self-test completed`);"gateway"===e&&w&&!w.ok?(h=!1,x=y(w.stderr||w.stdout,"Gateway handshake probe failed.")):"gateway"===e&&w?.ok&&(x="Gateway MCP handshake OK");const C={target:e,ok:h,summary:x,launch:c,contextFilePath:g(),selfTest:{exitCode:d.exitCode,stdout:d.stdout,stderr:d.stderr,result:f??null},handshakeProbe:w?{ok:w.ok,exitCode:w.exitCode,stdout:w.stdout,stderr:w.stderr,responseText:w.responseText}:null,ts:(new Date).toISOString()};return{ok:h,summary:x,detail:p||x,logPath:await m({target:e,payload:C}),result:f}}}};
1
+ import t from"node:fs/promises";import{existsSync as e}from"node:fs";import{createRequire as r}from"node:module";import s from"node:os";import o from"node:path";import{spawn as n,spawnSync as a}from"node:child_process";const i="gpt-5.4",c=r(import.meta.url);let d=null,l=null,g=null,u=null,m=null,w=null;const p=async t=>{if(!t)return"";const e=[];for await(const r of t)e.push(Buffer.isBuffer(r)?r:Buffer.from(String(r)));return Buffer.concat(e).toString("utf8")},f=t=>String(t||"").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,""),h=t=>{let e="";return{push:r=>{if(!t)return;e+=f(Buffer.isBuffer(r)?r.toString("utf8"):String(r));const s=e.split(/\r?\n/);e=s.pop()||"";for(const e of s){const r=String(e||"").trim();r&&t(r)}},flush:()=>{if(!t)return;const r=String(e||"").trim();r&&t(r),e=""}}},x=t=>{const e=f(String(t||"")).trim();return e?/^[{}[\],:]+$/.test(e)||/^["']?[a-zA-Z0-9_-]+["']?\s*:\s*$/.test(e)||/^`{3,}|^#{1,6}\s*$/.test(e)||/^Assistant:?\s*$/i.test(e)||/^Warning: no last agent message/i.test(e)?"":/^ERROR:\s*unexpected status\s+\d+/i.test(e)?"Tool call failed":e:""},y=(t,e)=>{const r=f(String(t||"")).replace(/\s+/g," ").trim();return r?r.length>180?`${r.slice(0,177)}...`:r:e},S=e=>{const r=(new Date).toISOString(),s=`exec-${Date.now()}`;let n="",a="",i=!1,c=Promise.resolve();const d=[],l=async()=>{if(i)return a;const r=o.join(e,"codex-sessions");await t.mkdir(r,{recursive:!0});const c=String(n||s||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_");return a=o.join(r,`${c}.log`),i=!0,d.length>0&&(await t.appendFile(a,d.join(""),"utf8"),d.length=0),a},g=(e,r={})=>{(e=>{c=c.then(async()=>{if(!i&&!n)return void d.push(e);const r=await l();await t.appendFile(r,e,"utf8")}).catch(()=>{})})(`${JSON.stringify({ts:(new Date).toISOString(),event:e,...r},null,0)}\n`)};return{get startedAt(){return r},setSessionId:t=>{const e=String(t||"").trim();e&&n!==e&&(n=e,g("session_identified",{sessionId:e}))},write:g,flush:async()=>(i||await l(),await c,a)}},C=()=>{if(l)return l;const t=[o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","npx.cmd"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","npx.cmd"),o.join(String(process.env.APPDATA||""),"npm","npx.cmd")].filter(Boolean);for(const r of t)if(e(r))return l=r,r;const r=a("where.exe",["npx.cmd"],{windowsHide:!0,encoding:"utf8"}),s=String(r.stdout||"").split(/\r?\n/).map(t=>t.trim()).find(Boolean);return s?(l=s,s):"npx.cmd"},_=()=>{if(g)return g;const t=[o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","node.exe"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","node.exe")];for(const r of t)if(e(r))return g=r,r;return g="node",g},P=()=>{if(u)return u;try{const t=c.resolve("@ai.weget.jp/weget-gateway-mcp/dist/index.js");return u=t,t}catch{}const t=[o.join(String(process.env.APPDATA||""),"npm","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),o.join(String(process.env.ProgramFiles||"C:\\Program Files"),"nodejs","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),o.join(String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)"),"nodejs","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js")].filter(Boolean);for(const r of t)if(e(r))return u=r,r;return u=o.join(String(process.env.APPDATA||""),"npm","node_modules","@ai.weget.jp","weget-gateway-mcp","dist","index.js"),u},v=t=>{const e=o.join(t,"weget-gateway-context.json");return"win32"===process.platform?{command:_(),args:[P(),"--context-file",e]}:{command:"npx",args:["-y","@ai.weget.jp/weget-gateway-mcp@latest","--context-file",e]}},k=t=>String(t||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),$=(t,e)=>{const r=String(t||""),s=v(e);if("win32"===process.platform){const t=new RegExp(`command:\\s+${k(s.command)}`,"i"),e=new RegExp(k(String(s.args[0]||"")),"i"),o=new RegExp(k(String(s.args[2]||"")),"i");return t.test(r)&&e.test(r)&&o.test(r)}return new RegExp(`command:\\s+${k(s.command)}`,"i").test(r)&&r.includes(String(s.args[0]||""))},O=async({args:t,stdinText:r,cwd:s,extraEnv:i,onStdoutLine:c,onStderrLine:l})=>new Promise((g,u)=>{const m=(()=>{if(d)return d;if("win32"===process.platform){const t=[String(process.env.ProgramFiles||"C:\\Program Files"),String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)")];for(const r of t){const t=o.join(r,"nodejs","node.exe"),s=o.join(r,"nodejs","node_modules","@openai","codex","bin","codex.js");if(e(t)&&e(s))return d={command:t,baseArgs:[s]},d}const r=["codex.cmd","codex"];for(const t of r){const e=a("where.exe",[t],{windowsHide:!0,encoding:"utf8"}),r=String(e.stdout||"").split(/\r?\n/).map(t=>t.trim()).find(Boolean);if(r)return d={command:r,baseArgs:[]},d}}return d={command:"codex",baseArgs:[]},d})(),w={...process.env,...i||{},NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"},f=n(m.command,[...m.baseArgs,...t],{cwd:s,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:w});let x=!1;const y=t=>{x||(x=!0,t())},S=p(f.stdout),C=p(f.stderr),_=h(c),P=h(l);f.stdout?.on("data",t=>_.push(t)),f.stderr?.on("data",t=>P.push(t)),f.on("error",t=>{y(()=>u(t))}),f.on("close",async t=>{_.flush(),P.flush();const[e,r]=await Promise.all([S,C]);y(()=>g({stdout:e,stderr:r,exitCode:Number.isFinite(t)?Number(t):1}))}),"string"==typeof r&&f.stdin.write(r),f.stdin.end()}),E=async({command:t,args:e,cwd:r})=>new Promise((s,o)=>{const a=n(t,e,{cwd:r,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:{...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"}});let i=!1;const c=t=>{i||(i=!0,t())},d=p(a.stdout),l=p(a.stderr);a.on("error",t=>c(()=>o(t))),a.on("close",async t=>{const[e,r]=await Promise.all([d,l]);c(()=>s({stdout:e,stderr:r,exitCode:Number.isFinite(t)?Number(t):1}))})}),j=async t=>null!==m?m:(w||(w=(async(t,e)=>{const r=String(t||"").trim();if(!r)return!1;try{const t=await O({cwd:e,args:["features","list"]});return 0===t.exitCode&&f(`${t.stdout||""}\n${t.stderr||""}`).split(/\r?\n/).map(t=>t.trim()).some(t=>t.startsWith(`${r} `)||t===r)}catch{return!1}})("rmcp_client",t).then(t=>(m=t,t)).finally(()=>{w=null})),w),A=async({cwd:t,model:e,outputPath:r})=>{const s=["exec"];return await j(t)&&s.push("--enable","rmcp_client"),s.push("--dangerously-bypass-approvals-and-sandbox","-C",t,"--skip-git-repo-check","-m",e,"-o",r,"-"),s};export const createCodexService=({getModel:e,getWorkingDir:r,getLogDir:a,emitLog:c,getGatewayContext:d})=>{const l=()=>{const t=d?.()||{},e={WEGET_ACTIVE_SKILLS:JSON.stringify(Array.isArray(t.activeSkills)?t.activeSkills:[]),WEGET_HOST_METADATA:JSON.stringify(t.hostMetadata&&"object"==typeof t.hostMetadata?t.hostMetadata:{})},r=String(t.botId||"").trim(),s=String(t.platformApiBaseUrl||"").trim(),o=String(t.accessToken||"").trim(),n=String(t.fileBridgeDir||"").trim(),a=String(t.fileBridgeToken||"").trim();return r&&(e.WEGET_BOT_ID=r),s&&(e.WEGET_PLATFORM_API_BASE_URL=s),o&&(e.WEGET_PLATFORM_API_ACCESS_TOKEN=o),n&&(e.WEGET_FILE_BRIDGE_DIR=n),a&&(e.WEGET_FILE_BRIDGE_TOKEN=a),e},g=()=>o.join(r(),"weget-gateway-context.json"),u=async()=>{const e=d?.()||{};await t.mkdir(r(),{recursive:!0}),await t.writeFile(g(),JSON.stringify({activeSkills:Array.isArray(e.activeSkills)?e.activeSkills:[],hostMetadata:e.hostMetadata&&"object"==typeof e.hostMetadata?e.hostMetadata:{},botId:String(e.botId||"").trim(),platformApiBaseUrl:String(e.platformApiBaseUrl||"").trim(),accessToken:String(e.accessToken||"").trim(),fileBridgeDir:String(e.fileBridgeDir||"").trim(),fileBridgeToken:String(e.fileBridgeToken||"").trim(),logDir:a(),updatedAt:(new Date).toISOString()},null,2),"utf8")},m=async({target:e,payload:r})=>{const s=o.join(a(),"gateway-tests");await t.mkdir(s,{recursive:!0});const n=o.join(s,`${e}-${Date.now()}.log`);return await t.writeFile(n,`${JSON.stringify(r,null,2)}\n`,"utf8"),n},w=async({userPrompt:t,systemPrompt:r,contextLabel:s})=>{const o=d?.()||{},n=Array.isArray(o.activeSkills)?o.activeSkills:[],a=o.hostMetadata&&"object"==typeof o.hostMetadata?o.hostMetadata:{},[c,l]=await Promise.all([p("weget-gateway"),f()]),g=n.length?n.map(t=>{const e=Array.isArray(t.tools)&&t.tools.length?` tools=${t.tools.join(", ")}`:"",r=String(t.version||"").trim()?` v${String(t.version||"").trim()}`:"";return`- ${String(t.name||"").trim()}${r}${e}`}):["- none"],u=[`- weget-gateway MCP: ${c.status}`,`- browser runtime: ${l.status}`,`- context: ${String(s||"").trim()||"general"}`,`- default model: ${String(a.aiModel||e()||i).trim()||i}`],m=(t=>{const e=String(t||"").trim().toLowerCase();return!!e&&[/macro/,/economic/,/economy/,/calendar/,/news/,/usd\s*\/\s*jpy/,/usd_jpy/,/risk sentiment/,/economic situation/,/macro snapshot/,/经济/,/宏观/,/日历/,/新闻/,/形势/,/経済/,/マクロ/,/ニュース/,/カレンダー/,/相場/].some(t=>t.test(e))})(t)?["","Macro tool routing hint:","- This request matches macro intent.","- Before answering, use the WeGet macro tools to fetch snapshot, calendar, and news if available.","- Prefer gateway tool calls such as weget_macro_get_snapshot, weget_macro_list_calendar, and weget_macro_list_news.","- Base the summary on fetched tool results rather than unsupported memory."]:[],w=String(r||"").trim();return["You are running inside WeGet Bot Host.","","Active skills:",...g,"","Runtime status:",...u,"","Tool policy:","- Prefer direct reasoning for simple text-only requests.","- When the user asks about macro conditions, economic situation, calendar, or news, prefer the WeGet macro tools before summarizing.","- Use WeGet browser tools only for website access, page extraction, navigation, or screenshots.","- If browser runtime or gateway MCP is unavailable, state that limitation explicitly instead of pretending the task was executed.","- For trading or any action with side effects, do not claim execution unless a concrete tool call actually performed it.","- Keep the final answer plain text only.",...m,...w?["","System prompt:",w]:[],"","User request:",String(t||"").trim(),"","Respond as plain text only."].join("\n")},p=async t=>{const e=String(t||"").trim();if(!e)return{status:"unknown",detail:"MCP server name is required"};try{"weget-gateway"===e&&await u().catch(()=>{});const t=await O({cwd:r(),args:["mcp","get",e],extraEnv:l()}),s=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?/No MCP server named/i.test(s)?{status:"missing",detail:s}:{status:"unknown",detail:s||"codex mcp list failed"}:"playwright"!==e||(t=>{const e=String(t||"");return"win32"===process.platform?/command:\s+.*node(?:\.exe)?/i.test(e)&&/@playwright[\\\/]mcp[\\\/]cli\.js/i.test(e)&&/args:\s+.*--headless/i.test(e):/command:\s+npx/i.test(e)&&/@playwright\/mcp@latest/i.test(e)&&/args:\s+.*--headless/i.test(e)})(s)?"weget-gateway"!==e||$(s,r())?{status:"configured",detail:s||`${e} is configured`}:{status:"missing",detail:"WeGet Gateway MCP exists but points to an outdated launch path. Reinstall it from the host UI."}:{status:"missing",detail:"Playwright MCP exists but uses an unsupported Windows wrapper. Click Install Playwright MCP to repair it."}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},f=async()=>{try{const t="win32"===process.platform?"powershell.exe":"npx",e="win32"===process.platform?["-NoProfile","-Command",`& '${C().replace(/'/g,"''")}' playwright install --list`]:["playwright","install","--list"],s=await E({command:t,args:e,cwd:r()}),o=String(s.stdout||s.stderr||"").trim();return 0!==s.exitCode?{status:"unknown",detail:o||"playwright install --list failed"}:/chromium-\d+/i.test(o)||/\\ms-playwright\\chromium-/i.test(o)?{status:"installed",detail:o}:{status:"missing",detail:o||"Chromium is not installed for Playwright."}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}};return{runPrompt:async({prompt:n,model:d,systemPrompt:m,contextLabel:p,onStatus:f})=>{const h=String(d||e()||i).trim()||i,y=r();await u().catch(()=>{});const C=S(a()),_=await t.mkdtemp(o.join(s.tmpdir(),"weget-codex-")),P=o.join(_,"last-message.txt"),v=await w({userPrompt:String(n||"").trim(),systemPrompt:String(m||"").trim(),contextLabel:p});C.write("exec_start",{model:h,cwd:y,context:String(p||"").trim(),prompt:String(n||"").trim(),instruction:v,outputPath:P,tempDir:_,gatewayContextFile:g(),traceStartedAt:C.startedAt}),f?.("Preparing request"),c("[codex] exec start",{model:h,cwd:y,context:String(p||"").trim()});try{const e=await A({cwd:y,model:h,outputPath:P}),r=await O({cwd:y,extraEnv:l(),args:e,stdinText:v,onStdoutLine:t=>{C.write("stdout_line",{line:t});const e=x(t);if(e)return/^re-connecting/i.test(e)||/checking tools|tool use|using tool|calling tool/i.test(e)?(C.write("status",{source:"stdout",status:e}),void f?.(e)):void(/reading data|fetching|loading|snapshot|calendar|news/i.test(e)&&(C.write("status",{source:"stdout",status:e}),f?.(e)))},onStderrLine:t=>{C.write("stderr_line",{line:t});const e=String(t||"").match(/session id:\s*([a-zA-Z0-9-]+)/i);e?.[1]&&C.setSessionId(e[1]);const r=x(t);if(r&&!/^--------$/.test(r)&&!/^user$/i.test(r))return/^OpenAI Codex/i.test(r)?(C.write("status",{source:"stderr",status:"Starting Codex"}),void f?.("Starting Codex")):void(/workdir:|model:|provider:|approval:|sandbox:|reasoning effort:|reasoning summaries:|session id:/i.test(r)||(C.write("status",{source:"stderr",status:r}),f?.(r)))}});C.write("status",{source:"host",status:"Finalizing reply"}),f?.("Finalizing reply");const s=await t.readFile(P,"utf8").catch(()=>"");if(0!==r.exitCode)throw C.write("exec_error",{exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,lastMessage:s}),new Error((r.stderr||r.stdout||`codex exec failed with code ${r.exitCode}`).trim());const o=String(s||r.stdout||"").trim();if(!o)throw C.write("exec_error",{exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,lastMessage:s,error:"codex returned empty output"}),new Error("codex returned empty output");return C.write("exec_completed",{exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,lastMessage:s,finalAnswer:o}),c("[codex] exec completed",{model:h,context:String(p||"").trim()}),o}catch(t){throw C.write("exec_exception",{error:t instanceof Error?t.message:String(t)}),t}finally{const e=await C.flush().catch(()=>"");e&&c("[codex] session trace written",{context:String(p||"").trim(),tracePath:e}),await t.rm(_,{recursive:!0,force:!0}).catch(()=>{})}},getAuthStatus:async()=>{try{const t=await O({cwd:r(),args:["login","status"]}),e=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?{status:"unknown",detail:e||"codex login status failed"}:/logged in/i.test(e)?{status:"logged_in",detail:e}:/not logged in|logged out/i.test(e)?{status:"logged_out",detail:e}:{status:"unknown",detail:e||"unknown codex auth state"}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},startLogin:async()=>{const e=r();try{if("win32"===process.platform){const r=String(e||"").replace(/'/g,"''"),a=o.join(s.tmpdir(),`weget-codex-login-${Date.now()}.ps1`),i=["$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)","chcp 65001 > $null",'$env:NO_COLOR = "1"','$env:FORCE_COLOR = "0"','$env:CLICOLOR = "0"','$env:LANG = "en_US.UTF-8"','$env:LC_ALL = "en_US.UTF-8"',`Set-Location -LiteralPath '${r}'`,"$stripAnsi = {"," param([string]$line)",' if ($null -eq $line) { return "" }',' return ($line -replace "`e\\[[0-9;?]*[ -/]*[@-~]", "")',"}","codex login --device-auth 2>&1 | ForEach-Object { & $stripAnsi ([string]$_) }"].join(`${s.EOL}`);await t.writeFile(a,i,"utf8");const c=["$script = @()",'$script += "-NoExit"','$script += "-ExecutionPolicy"','$script += "Bypass"','$script += "-File"',`$script += '${a.replace(/'/g,"''")}'`,`Start-Process -FilePath 'powershell.exe' -WorkingDirectory '${r}' -ArgumentList $script`].join("; ");return n("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",c],{cwd:e,stdio:"ignore",windowsHide:!0}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in a new PowerShell window with UTF-8 output."}}if("darwin"===process.platform){const t=`cd ${JSON.stringify(e)} && codex login --device-auth`;return n("open",["-a","Terminal",t],{cwd:e,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in Terminal."}}const r=`cd ${JSON.stringify(e)} && codex login --device-auth; exec $SHELL`,a=[["x-terminal-emulator",["-e","bash","-lc",r]],["gnome-terminal",["--","bash","-lc",r]],["konsole",["-e","bash","-lc",r]]];for(const[t,r]of a)try{return n(t,r,{cwd:e,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:`Started \`codex login --device-auth\` using ${t}.`}}catch{}return{ok:!1,detail:"No supported terminal launcher was found. Run `codex login --device-auth` manually."}}catch(t){const e=t instanceof Error?t.message:String(t);return c("[codex] login launch failed",{error:e}),{ok:!1,detail:e}}},getMcpServerStatus:p,installWegetGatewayMcp:async()=>{try{await u();const t=await p("weget-gateway");if("configured"===t.status&&$(t.detail,r()))return{ok:!0,detail:t.detail||"WeGet Gateway MCP is already configured."};const e=await O({cwd:r(),args:["mcp","remove","weget-gateway"],extraEnv:l()});if(0!==e.exitCode){const t=String(e.stdout||e.stderr||"").trim().toLowerCase();if(!(t.includes("no mcp server named")||t.includes("not found")||t.includes("no server named")))return{ok:!1,detail:String(e.stdout||e.stderr||"").trim()||"Failed to remove existing WeGet Gateway MCP entry."}}const s=v(r()),o=await O({cwd:r(),args:["mcp","add","weget-gateway",s.command,...s.args],extraEnv:l()}),n=String(o.stdout||o.stderr||"").trim();if(0!==o.exitCode)return{ok:!1,detail:n||"Failed to add WeGet Gateway MCP to Codex."};const a=await p("weget-gateway");return"configured"===a.status?{ok:!0,detail:a.detail||"WeGet Gateway MCP configured."}:{ok:!0,detail:n||"WeGet Gateway MCP add command completed, but verification returned no explicit entry."}}catch(t){const e=t instanceof Error?t.message:String(t);return c("[codex] weget gateway mcp install failed",{error:e}),{ok:!1,detail:e}}},getPlaywrightBrowserStatus:f,getGatewayContextFilePath:g,runGatewaySelfTest:async e=>{if("codex_macro"===e){const n=(new Date).toISOString().slice(0,10);await u().catch(()=>{});const c=S(a()),d=await t.mkdtemp(o.join(s.tmpdir(),"weget-codex-test-")),w=o.join(d,"last-message.txt"),p=["You are running a WeGet gateway chain self-test.","Use WeGet gateway MCP tools only.",`Call weget_macro_get_snapshot, weget_macro_list_calendar with dateKey=${n}, and weget_macro_list_news with dateKey=${n}.`,"If all three tool calls succeed, reply with exactly: OK macro chain","If any tool call fails, reply with exactly: NG macro chain: <short reason>","Respond as plain text only."].join("\n");c.write("exec_start",{model:i,cwd:r(),context:"gateway_self_test:codex_macro",prompt:"codex macro chain self-test",instruction:p,outputPath:w,tempDir:d,gatewayContextFile:g(),traceStartedAt:c.startedAt});try{const s=await A({cwd:r(),model:i,outputPath:w}),o=await O({cwd:r(),extraEnv:l(),args:s,stdinText:p,onStdoutLine:t=>{c.write("stdout_line",{line:t})},onStderrLine:t=>{c.write("stderr_line",{line:t});const e=String(t||"").match(/session id:\s*([a-zA-Z0-9-]+)/i);e?.[1]&&c.setSessionId(e[1])}}),a=await t.readFile(w,"utf8").catch(()=>""),u=String(a||o.stdout||"").trim(),f=String(o.stderr||""),h=String(o.stdout||""),x=/tool weget-gateway\.weget_macro_get_snapshot\(\{\}\)/i.test(f),S=new RegExp(`tool weget-gateway\\.weget_macro_list_calendar\\(\\{"dateKey":"${k(n)}"\\}\\)`,"i").test(f),C=new RegExp(`tool weget-gateway\\.weget_macro_list_news\\(\\{"dateKey":"${k(n)}"\\}\\)`,"i").test(f),_=/weget-gateway\.weget_macro_get_snapshot\(\{\}\) success/i.test(f),P=/weget-gateway\.weget_macro_list_calendar\(\{\"dateKey\":\"\d{4}-\d{2}-\d{2}\"\}\) success/i.test(f),v=/weget-gateway\.weget_macro_list_news\(\{\"dateKey\":\"\d{4}-\d{2}-\d{2}\"\}\) success/i.test(f),$=0===o.exitCode&&/^OK macro chain$/i.test(u)&&x&&S&&C&&_&&P&&v;c.write($?"exec_completed":"exec_error",{exitCode:o.exitCode,stdout:h,stderr:f,lastMessage:a,finalAnswer:u,invokedSnapshot:x,invokedCalendar:S,invokedNews:C,succeededSnapshot:_,succeededCalendar:P,succeededNews:v});const E=await c.flush().catch(()=>""),j={target:e,ok:$,summary:$?"Codex gateway macro chain OK":"Codex gateway macro chain failed",contextFilePath:g(),tracePath:E,codex:{exitCode:o.exitCode,stdout:h,stderr:f,finalAnswer:u},assertions:{invokedSnapshot:x,invokedCalendar:S,invokedNews:C,succeededSnapshot:_,succeededCalendar:P,succeededNews:v},ts:(new Date).toISOString()},F=await m({target:e,payload:j});return await t.rm(d,{recursive:!0,force:!0}).catch(()=>{}),{ok:$,summary:$?"Codex gateway macro chain OK":y(u||f,"Codex gateway macro chain failed"),detail:u||f||h,logPath:F,result:j}}catch(r){c.write("exec_exception",{error:r instanceof Error?r.message:String(r)});const s=await c.flush().catch(()=>""),o=r instanceof Error?r.message:String(r),n=await m({target:e,payload:{target:e,ok:!1,summary:"Codex gateway macro chain failed",contextFilePath:g(),tracePath:s,error:o,ts:(new Date).toISOString()}});return await t.rm(d,{recursive:!0,force:!0}).catch(()=>{}),{ok:!1,summary:y(o,"Codex gateway macro chain failed"),detail:o,logPath:n}}}await u();const c=v(r());let d,w=null;try{d=await E({command:c.command,args:[...c.args,"--self-test","--test-target",e],cwd:r()}),"gateway"===e&&(w=await(async({command:t,args:e,cwd:r,timeoutMs:s=2500})=>new Promise((o,a)=>{const i=n(t,e,{cwd:r,stdio:["ignore","pipe","pipe"],windowsHide:!0,env:{...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"}}),c=[],d=[];let l=!1;const g=t=>{l||(l=!0,o(t))};i.stdout.on("data",t=>c.push(Buffer.from(t))),i.stderr.on("data",t=>d.push(Buffer.from(t))),i.on("error",t=>{l||(l=!0,a(t))}),i.on("close",t=>{g({ok:!1,exitCode:Number.isFinite(t)?Number(t):1,stdout:Buffer.concat(c).toString("utf8"),stderr:Buffer.concat(d).toString("utf8"),responseText:""})}),setTimeout(()=>{const t=i.stdin;t?.write(`${JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"weget-gateway-probe",version:"0.1.0"}}})}\n`)},150),setTimeout(()=>{if(l)return;const t=Buffer.concat(c).toString("utf8"),e=Buffer.concat(d).toString("utf8"),r=t.trim(),s=/"result"\s*:/.test(r)&&/"protocolVersion"\s*:/.test(r);i.kill(),g({ok:s,exitCode:null,stdout:t,stderr:e,responseText:r})},Math.max(250,s))}))({command:c.command,args:c.args,cwd:r()}))}catch(t){const r=t instanceof Error?t.message:String(t),s=await m({target:e,payload:{target:e,launch:c,contextFilePath:g(),failure:"spawn_exception",error:r,ts:(new Date).toISOString()}});return{ok:!1,summary:y(r,`${e} test failed`),detail:r,logPath:s}}const p=String(d.stdout||d.stderr||"").trim();let f;if(p)try{f=JSON.parse(p)}catch{}let h=0===d.exitCode&&Boolean(!f||!1!==f.ok),x=y(p,`${e} self-test completed`);"gateway"===e&&w&&!w.ok?(h=!1,x=y(w.stderr||w.stdout,"Gateway handshake probe failed.")):"gateway"===e&&w?.ok&&(x="Gateway MCP handshake OK");const C={target:e,ok:h,summary:x,launch:c,contextFilePath:g(),selfTest:{exitCode:d.exitCode,stdout:d.stdout,stderr:d.stderr,result:f??null},handshakeProbe:w?{ok:w.ok,exitCode:w.exitCode,stdout:w.stdout,stderr:w.stderr,responseText:w.responseText}:null,ts:(new Date).toISOString()};return{ok:h,summary:x,detail:p||x,logPath:await m({target:e,payload:C}),result:f}}}};
@@ -1 +1 @@
1
- import t from"ws";export class BotWsClient{config;hooks;ws;heartbeatTimer;reconnectTimer;pingTimer;pongTimeoutTimer;awaitingPong;taskSeqMap;session;shouldReconnect;reconnectAttempts;maxReconnectAttempts;currentStatus;constructor(t,s={}){this.config=t,this.hooks=s,this.ws=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.pingTimer=null,this.pongTimeoutTimer=null,this.awaitingPong=!1,this.taskSeqMap=new Map,this.session=null,this.shouldReconnect=!1,this.reconnectAttempts=0,this.maxReconnectAttempts=Number.isFinite(Number(this.config.reconnectMaxAttempts))?Math.max(0,Number(this.config.reconnectMaxAttempts)):10,this.currentStatus="disconnected"}botId(){return this.session?.botId||this.config.botId||""}nowIso(){return(new Date).toISOString()}log(t,s){this.hooks.onLog&&this.hooks.onLog(t,s)}setStatus(t){const s=String(t||"").trim().toLowerCase()||"disconnected";this.currentStatus!==s&&(this.currentStatus=s,this.hooks.onStatus&&this.hooks.onStatus(s))}getStatus(){return this.currentStatus}nextSeq(t){const s=(this.taskSeqMap.get(t)||0)+1;return this.taskSeqMap.set(t,s),s}buildWsUrl(){const t=new URL(this.config.wsUrl);return t.searchParams.set("bot_id",this.botId()),this.session?.userId&&t.searchParams.set("user_id",this.session.userId),this.session?.accessToken&&t.searchParams.set("token",this.session.accessToken),t.toString()}sendJson(s){this.ws&&this.ws.readyState===t.OPEN&&this.ws.send(JSON.stringify(s),t=>{if(t){this.log("[bot] ws send failed",{error:t.message});try{this.ws?.terminate()}catch{}}})}sanitizeLineReplyText(t){const s=String(t||"");return s?s.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/gi,"$1").replace(/https?:\/\/\S+/gi,"").replace(/\s{2,}/g," ").replace(/\n{3,}/g,"\n\n").trim():""}sendHello(){this.sendJson({action:"bot_hello",bot_id:this.botId(),user_id:this.session?.userId||void 0,version:this.config.version,capabilities:this.config.capabilities,ts:this.nowIso()})}sendHeartbeat(){this.sendJson({action:"task_event",bot_id:this.botId(),user_id:this.session?.userId||void 0,task_id:null,seq:0,event_type:"heartbeat",data_json:{bot_id:this.botId(),ts:this.nowIso()}})}sendTaskEvent(t,s,e){this.sendJson({action:"task_event",bot_id:this.botId(),user_id:this.session?.userId||void 0,task_id:t,seq:this.nextSeq(t),event_type:s,data_json:e})}sendBotState(t,s=""){const e=String(t||"").trim().toLowerCase();e&&this.sendTaskEvent("__bot_state__","bot_state",{state:e,reason:String(s||""),ts:this.nowIso()})}sendWsPing(){if(!this.ws||this.ws.readyState!==t.OPEN)return;if(this.awaitingPong){this.log("[bot] ws pong timeout, reconnecting");try{this.ws.terminate()}catch{}return}this.awaitingPong=!0,this.pongTimeoutTimer&&(clearTimeout(this.pongTimeoutTimer),this.pongTimeoutTimer=null);const s=1e3*Math.max(3,Number(this.config.wsPongTimeoutSec||12));this.pongTimeoutTimer=setTimeout(()=>{if(this.awaitingPong){this.log("[bot] ws pong timeout, reconnecting");try{this.ws?.terminate()}catch{}}},s);try{this.ws.ping()}catch(t){this.log("[bot] ws ping failed",{error:t instanceof Error?t.message:String(t)});try{this.ws.terminate()}catch{}}}async runTaskMock(t){const s=t.task_id;s?(this.sendTaskEvent(s,"task_received",{bot_id:this.botId(),received_at:this.nowIso()}),this.sendTaskEvent(s,"step_started",{step:"open_url",ts:this.nowIso()}),await new Promise(t=>setTimeout(t,600)),this.sendTaskEvent(s,"step_finished",{step:"open_url",ts:this.nowIso()}),this.sendTaskEvent(s,"ai_output",{summary:"mock result from electron bot",next_action:"wait_human_review",risk_flags:[]}),this.sendTaskEvent(s,"task_completed",{ts:this.nowIso()})):this.log("[bot] run_task missing task_id")}async onMessage(t){let s;try{const e=JSON.parse(t.toString("utf8"));if(!e||"object"!=typeof e)return void this.log("[bot] ignored non-object message");s=e}catch{return void this.log("[bot] ignored non-JSON message")}const e=s.type||s.action||"";if("hello_ack"!==e){if("run_task"===e){const t=s,e=String(t.task_id||"").trim();this.log("[bot] run_task received",{task_id:e});const i=Boolean(this.hooks.onRunTask);try{i?await(this.hooks.onRunTask?.(t)):await this.runTaskMock(t)}catch(t){!i&&e&&this.sendTaskEvent(e,"task_failed",{error:t instanceof Error?t.message:String(t),ts:this.nowIso()}),this.log("[bot] run_task failed",{task_id:e,error:t instanceof Error?t.message:String(t)})}return}if("task_control"===e){const t=s,e=String(t.task_id||"").trim(),i=String(t.action||"").trim();if(this.log("[bot] task_control received",{task_id:e,action:i}),this.hooks.onTaskControl)try{await this.hooks.onTaskControl(t)}catch(t){this.log("[bot] onTaskControl failed",{task_id:e,action:i,error:t instanceof Error?t.message:String(t)})}else e&&"cancel_task"===i&&this.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:this.nowIso()});return}if("line_message"===e){const t=s,e=`line-${s.line_event_id||Date.now()}`;if(this.log("[bot] line message received",{bot_id:s.bot_id||this.botId(),line_user_id:s.line_user_id||"",text:s.text||""}),this.sendTaskEvent(e,"line_message_received",{line_user_id:s.line_user_id||"",text:s.text||"",ts:this.nowIso()}),this.hooks.onLineMessage)try{const i=await this.hooks.onLineMessage(t,{taskId:e}),n=Array.isArray(i?.messages)?i?.messages.filter(t=>t&&"object"==typeof t):[],o=this.sanitizeLineReplyText(i?.answer||""),r=String(i?.imageUrl||"").trim(),a=String(i?.previewImageUrl||"").trim();(n.length>0||o||r)&&(this.sendJson({action:"line_reply",bot_id:this.botId(),user_id:this.session?.userId||void 0,line_user_id:s.line_user_id||"",line_reply_token:s.line_reply_token||"",text:n.length>0?void 0:o,image_url:r||void 0,preview_image_url:a||void 0,messages:n.length>0?n:void 0,ts:this.nowIso()}),this.sendTaskEvent(e,"line_message_ai_output",{line_user_id:s.line_user_id||"",answer:o,image_url:r||"",ts:this.nowIso()}))}catch(t){this.sendTaskEvent(e,"line_message_process_failed",{line_user_id:s.line_user_id||"",error:t instanceof Error?t.message:String(t),ts:this.nowIso()})}return}if("server_config_update"===e){const t=s;if(this.log("[bot] server_config_update received",{bot_id:s.bot_id||this.botId(),scopes:Array.isArray(s.scopes)?s.scopes:[]}),this.hooks.onServerConfigUpdate)try{await this.hooks.onServerConfigUpdate(t)}catch(t){this.log("[bot] onServerConfigUpdate failed",{error:t instanceof Error?t.message:String(t)})}return}"ping"===e&&this.sendJson({type:"pong",ts:this.nowIso()})}else this.log("[bot] hello ack",s)}clearTimers(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null),this.pongTimeoutTimer&&(clearTimeout(this.pongTimeoutTimer),this.pongTimeoutTimer=null),this.awaitingPong=!1}scheduleReconnect(){if(this.reconnectTimer||!this.session||!this.shouldReconnect)return;if(this.reconnectAttempts>=this.maxReconnectAttempts)return this.log("[bot] reconnect attempts exhausted",{attempts:this.reconnectAttempts,maxAttempts:this.maxReconnectAttempts}),void this.setStatus("reconnect_exhausted");const t=this.reconnectAttempts+1;this.log("[bot] scheduling reconnect",{attempt:t,maxAttempts:this.maxReconnectAttempts,delayMs:this.config.reconnectMs}),this.setStatus("reconnecting"),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.reconnectAttempts=t,this.connect()},this.config.reconnectMs)}connect(){if(!this.session||!this.shouldReconnect)return;if(this.ws&&(this.ws.readyState===t.OPEN||this.ws.readyState===t.CONNECTING))return;const s=this.buildWsUrl();this.log("[bot] connecting",{target:s}),this.setStatus("connecting"),this.ws=new t(s),this.ws.on("open",()=>{this.log("[bot] connected"),this.reconnectAttempts=0,this.sendHello(),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),1e3*this.config.heartbeatSec);const t=Math.max(5,Number(this.config.wsPingIntervalSec||25));this.pingTimer=setInterval(()=>this.sendWsPing(),1e3*t),this.awaitingPong=!1,this.setStatus("connected")}),this.ws.on("message",t=>{this.onMessage(t)}),this.ws.on("pong",()=>{this.awaitingPong=!1,this.pongTimeoutTimer&&(clearTimeout(this.pongTimeoutTimer),this.pongTimeoutTimer=null)}),this.ws.on("error",t=>{if(this.log("[bot] ws error",{message:t.message}),this.setStatus("error"),this.shouldReconnect)try{this.ws?.terminate()}catch{}}),this.ws.on("close",()=>{this.log("[bot] disconnected"),this.clearTimers(),this.ws=null,this.setStatus("disconnected"),this.scheduleReconnect()})}start(s){this.shouldReconnect=!0,this.reconnectAttempts=0,this.session=s,this.clearTimers(),this.ws&&this.ws.readyState===t.OPEN?this.ws.close():this.ws&&this.ws.readyState===t.CONNECTING||this.connect()}stop(){this.shouldReconnect=!1,this.reconnectAttempts=0,this.session=null,this.clearTimers(),this.ws?(this.ws.readyState!==t.OPEN&&this.ws.readyState!==t.CONNECTING||this.ws.close(),this.ws=null,this.setStatus("disconnected")):this.setStatus("disconnected")}}
1
+ import t from"ws";export class BotWsClient{config;hooks;ws;heartbeatTimer;reconnectTimer;pingTimer;pongTimeoutTimer;awaitingPong;taskSeqMap;session;shouldReconnect;reconnectAttempts;maxReconnectAttempts;currentStatus;constructor(t,s={}){this.config=t,this.hooks=s,this.ws=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.pingTimer=null,this.pongTimeoutTimer=null,this.awaitingPong=!1,this.taskSeqMap=new Map,this.session=null,this.shouldReconnect=!1,this.reconnectAttempts=0,this.maxReconnectAttempts=Number.isFinite(Number(this.config.reconnectMaxAttempts))?Math.max(0,Number(this.config.reconnectMaxAttempts)):10,this.currentStatus="disconnected"}botId(){return this.session?.botId||this.config.botId||""}nowIso(){return(new Date).toISOString()}log(t,s){this.hooks.onLog&&this.hooks.onLog(t,s)}setStatus(t){const s=String(t||"").trim().toLowerCase()||"disconnected";this.currentStatus!==s&&(this.currentStatus=s,this.hooks.onStatus&&this.hooks.onStatus(s))}getStatus(){return this.currentStatus}nextSeq(t){const s=(this.taskSeqMap.get(t)||0)+1;return this.taskSeqMap.set(t,s),s}buildWsUrl(){const t=new URL(this.config.wsUrl);return t.searchParams.set("bot_id",this.botId()),this.session?.userId&&t.searchParams.set("user_id",this.session.userId),this.session?.accessToken&&t.searchParams.set("token",this.session.accessToken),t.toString()}sendJson(s){this.ws&&this.ws.readyState===t.OPEN&&this.ws.send(JSON.stringify(s),t=>{if(t){this.log("[bot] ws send failed",{error:t.message});try{this.ws?.terminate()}catch{}}})}sanitizeLineReplyText(t){const s=String(t||"");return s?s.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/gi,"$1").replace(/https?:\/\/\S+/gi,"").replace(/\s{2,}/g," ").replace(/\n{3,}/g,"\n\n").trim():""}sendHello(){this.sendJson({action:"bot_hello",bot_id:this.botId(),user_id:this.session?.userId||void 0,version:this.config.version,capabilities:this.config.capabilities,ts:this.nowIso()})}sendHeartbeat(){this.sendTaskEventPayload({action:"task_event",bot_id:this.botId(),user_id:this.session?.userId||void 0,task_id:null,seq:0,event_type:"heartbeat",data_json:{bot_id:this.botId(),ts:this.nowIso()}})}sendTaskEvent(t,s,e){this.sendTaskEventPayload({action:"task_event",bot_id:this.botId(),user_id:this.session?.userId||void 0,task_id:t,seq:this.nextSeq(t),event_type:s,data_json:e})}runtimeCallbackBaseUrl(){const t=String(this.config.taskEventApiUrl||"").trim();return t?t.replace(/\/task-event\/?$/i,""):""}postRuntimeCallback(t,s){const e=this.runtimeCallbackBaseUrl(),i=String(this.session?.accessToken||"").trim();e&&i?fetch(`${e}${t}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify(s)}).then(async s=>{if(s.ok)return;const e=await s.text().catch(()=>"");this.log("[bot] runtime callback failed",{path:t,status:s.status,body:e})}).catch(s=>{this.log("[bot] runtime callback failed",{path:t,error:s instanceof Error?s.message:String(s)})}):this.log("[bot] runtime callback skipped",{path:t,reason:e?"missing_access_token":"missing_api_url"})}sendTaskEventPayload(t){const s=String(this.config.taskEventApiUrl||"").trim(),e=String(this.session?.accessToken||"").trim();s&&e?fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(t)}).then(async t=>{if(t.ok)return;const s=await t.text().catch(()=>"");this.log("[bot] task_event callback failed",{status:t.status,body:s})}).catch(t=>{this.log("[bot] task_event callback failed",{error:t instanceof Error?t.message:String(t)})}):this.log("[bot] task_event callback skipped",{reason:s?"missing_access_token":"missing_api_url",event_type:String(t.event_type||""),task_id:String(t.task_id||"")})}sendTaskResult(t,s,e={}){const i={bot_id:this.botId(),task_id:t,seq:this.nextSeq(t),status:s,summary:e.summary,error:e.error,data_json:e.data_json||{}};this.postRuntimeCallback("/result",i)}requestTaskInput(t,s){const e={bot_id:this.botId(),task_id:t,seq:this.nextSeq(t),prompt:s.prompt,input_type:s.input_type,data_json:s.data_json||{}};this.postRuntimeCallback("/input-request",e)}requestTaskApproval(t,s){const e={bot_id:this.botId(),task_id:t,seq:this.nextSeq(t),prompt:s.prompt,approval_type:s.approval_type,data_json:s.data_json||{}};this.postRuntimeCallback("/approval-request",e)}sendSkillStatus(t,s){const e={bot_id:this.botId(),task_id:t,seq:this.nextSeq(t),skill_id:s.skill_id,status:s.status,summary:s.summary,data_json:s.data_json||{}};this.postRuntimeCallback("/skill-status",e)}sendBotState(t,s=""){const e=String(t||"").trim().toLowerCase();e&&this.sendTaskEvent("__bot_state__","bot_state",{state:e,reason:String(s||""),ts:this.nowIso()})}sendWsPing(){if(!this.ws||this.ws.readyState!==t.OPEN)return;if(this.awaitingPong){this.log("[bot] ws pong timeout, reconnecting");try{this.ws.terminate()}catch{}return}this.awaitingPong=!0,this.pongTimeoutTimer&&(clearTimeout(this.pongTimeoutTimer),this.pongTimeoutTimer=null);const s=1e3*Math.max(3,Number(this.config.wsPongTimeoutSec||12));this.pongTimeoutTimer=setTimeout(()=>{if(this.awaitingPong){this.log("[bot] ws pong timeout, reconnecting");try{this.ws?.terminate()}catch{}}},s);try{this.ws.ping()}catch(t){this.log("[bot] ws ping failed",{error:t instanceof Error?t.message:String(t)});try{this.ws.terminate()}catch{}}}async runTaskMock(t){const s=t.task_id;s?(this.sendTaskEvent(s,"task_received",{bot_id:this.botId(),received_at:this.nowIso()}),this.sendTaskEvent(s,"step_started",{step:"open_url",ts:this.nowIso()}),await new Promise(t=>setTimeout(t,600)),this.sendTaskEvent(s,"step_finished",{step:"open_url",ts:this.nowIso()}),this.sendTaskEvent(s,"ai_output",{summary:"mock result from electron bot",next_action:"wait_human_review",risk_flags:[]}),this.sendTaskEvent(s,"task_completed",{ts:this.nowIso()})):this.log("[bot] run_task missing task_id")}async onMessage(t){let s;try{const e=JSON.parse(t.toString("utf8"));if(!e||"object"!=typeof e)return void this.log("[bot] ignored non-object message");s=e}catch{return void this.log("[bot] ignored non-JSON message")}const e=s.type||s.action||"";if("hello_ack"!==e){if("run_task"===e){const t=s,e=String(t.task_id||"").trim();this.log("[bot] run_task received",{task_id:e});const i=Boolean(this.hooks.onRunTask);try{i?await(this.hooks.onRunTask?.(t)):await this.runTaskMock(t)}catch(t){!i&&e&&this.sendTaskEvent(e,"task_failed",{error:t instanceof Error?t.message:String(t),ts:this.nowIso()}),this.log("[bot] run_task failed",{task_id:e,error:t instanceof Error?t.message:String(t)})}return}if("task_control"===e){const t=s,e=String(t.task_id||"").trim(),i=String(t.action||"").trim();if(this.log("[bot] task_control received",{task_id:e,action:i}),this.hooks.onTaskControl)try{await this.hooks.onTaskControl(t)}catch(t){this.log("[bot] onTaskControl failed",{task_id:e,action:i,error:t instanceof Error?t.message:String(t)})}else e&&"cancel_task"===i&&this.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:this.nowIso()});return}if("line_message"===e){const t=s,e=`line-${s.line_event_id||Date.now()}`;if(this.log("[bot] line message received",{bot_id:s.bot_id||this.botId(),line_user_id:s.line_user_id||"",text:s.text||""}),this.sendTaskEvent(e,"line_message_received",{line_user_id:s.line_user_id||"",text:s.text||"",ts:this.nowIso()}),this.hooks.onLineMessage)try{const i=await this.hooks.onLineMessage(t,{taskId:e}),n=Array.isArray(i?.messages)?i?.messages.filter(t=>t&&"object"==typeof t):[],o=this.sanitizeLineReplyText(i?.answer||""),a=String(i?.imageUrl||"").trim(),r=String(i?.previewImageUrl||"").trim();(n.length>0||o||a)&&(this.sendJson({action:"line_reply",bot_id:this.botId(),user_id:this.session?.userId||void 0,line_user_id:s.line_user_id||"",line_reply_token:s.line_reply_token||"",text:n.length>0?void 0:o,image_url:a||void 0,preview_image_url:r||void 0,messages:n.length>0?n:void 0,ts:this.nowIso()}),this.sendTaskEvent(e,"line_message_ai_output",{line_user_id:s.line_user_id||"",answer:o,image_url:a||"",ts:this.nowIso()}))}catch(t){this.sendTaskEvent(e,"line_message_process_failed",{line_user_id:s.line_user_id||"",error:t instanceof Error?t.message:String(t),ts:this.nowIso()})}return}if("server_config_update"===e){const t=s;if(this.log("[bot] server_config_update received",{bot_id:s.bot_id||this.botId(),scopes:Array.isArray(s.scopes)?s.scopes:[]}),this.hooks.onServerConfigUpdate)try{await this.hooks.onServerConfigUpdate(t)}catch(t){this.log("[bot] onServerConfigUpdate failed",{error:t instanceof Error?t.message:String(t)})}return}"ping"===e&&this.sendJson({type:"pong",ts:this.nowIso()})}else this.log("[bot] hello ack",s)}clearTimers(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null),this.pongTimeoutTimer&&(clearTimeout(this.pongTimeoutTimer),this.pongTimeoutTimer=null),this.awaitingPong=!1}scheduleReconnect(){if(this.reconnectTimer||!this.session||!this.shouldReconnect)return;if(this.reconnectAttempts>=this.maxReconnectAttempts)return this.log("[bot] reconnect attempts exhausted",{attempts:this.reconnectAttempts,maxAttempts:this.maxReconnectAttempts}),void this.setStatus("reconnect_exhausted");const t=this.reconnectAttempts+1;this.log("[bot] scheduling reconnect",{attempt:t,maxAttempts:this.maxReconnectAttempts,delayMs:this.config.reconnectMs}),this.setStatus("reconnecting"),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.reconnectAttempts=t,this.connect()},this.config.reconnectMs)}connect(){if(!this.session||!this.shouldReconnect)return;if(this.ws&&(this.ws.readyState===t.OPEN||this.ws.readyState===t.CONNECTING))return;const s=this.buildWsUrl();this.log("[bot] connecting",{target:s}),this.setStatus("connecting"),this.ws=new t(s),this.ws.on("open",()=>{this.log("[bot] connected"),this.reconnectAttempts=0,this.sendHello(),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),1e3*this.config.heartbeatSec);const t=Math.max(5,Number(this.config.wsPingIntervalSec||25));this.pingTimer=setInterval(()=>this.sendWsPing(),1e3*t),this.awaitingPong=!1,this.setStatus("connected")}),this.ws.on("message",t=>{this.onMessage(t)}),this.ws.on("pong",()=>{this.awaitingPong=!1,this.pongTimeoutTimer&&(clearTimeout(this.pongTimeoutTimer),this.pongTimeoutTimer=null)}),this.ws.on("error",t=>{if(this.log("[bot] ws error",{message:t.message}),this.setStatus("error"),this.shouldReconnect)try{this.ws?.terminate()}catch{}}),this.ws.on("close",()=>{this.log("[bot] disconnected"),this.clearTimers(),this.ws=null,this.setStatus("disconnected"),this.scheduleReconnect()})}start(s){this.shouldReconnect=!0,this.reconnectAttempts=0,this.session=s,this.clearTimers(),this.ws&&this.ws.readyState===t.OPEN?this.ws.close():this.ws&&this.ws.readyState===t.CONNECTING||this.connect()}stop(){this.shouldReconnect=!1,this.reconnectAttempts=0,this.session=null,this.clearTimers(),this.ws?(this.ws.readyState!==t.OPEN&&this.ws.readyState!==t.CONNECTING||this.ws.close(),this.ws=null,this.setStatus("disconnected")):this.setStatus("disconnected")}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai.weget.jp/bot",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "WeGet bot host for Codex-centered skill runtime ",
@@ -28,12 +28,12 @@
28
28
  "build": "npm run build:ts"
29
29
  },
30
30
  "dependencies": {
31
- "@ai.weget.jp/skill-browser": ">=0.1.1 <1",
32
- "@ai.weget.jp/skill-gmo-coin": ">=0.1.3 <1",
33
- "@ai.weget.jp/skill-gmo-fx": ">=0.1.3 <1",
34
- "@ai.weget.jp/skill-macro-economy": ">=0.1.1 <1",
35
- "@ai.weget.jp/skill-sdk": ">=0.1.2 <1",
36
- "@ai.weget.jp/weget-gateway-mcp": ">=0.1.1 <1",
31
+ "@ai.weget.jp/skill-browser": ">=0.1.2 <1",
32
+ "@ai.weget.jp/skill-gmo-coin": ">=0.1.4 <1",
33
+ "@ai.weget.jp/skill-gmo-fx": ">=0.1.4 <1",
34
+ "@ai.weget.jp/skill-macro-economy": ">=0.1.2 <1",
35
+ "@ai.weget.jp/skill-sdk": ">=0.1.3 <1",
36
+ "@ai.weget.jp/weget-gateway-mcp": ">=0.1.2 <1",
37
37
  "electron": "^31.7.7",
38
38
  "ws": "^8.18.3"
39
39
  },