@ai.weget.jp/bot 0.1.8 → 0.1.9

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.
Files changed (33) hide show
  1. package/README.md +13 -4
  2. package/dist/src/config/systemConfig.js +1 -0
  3. package/dist/src/ipc/registerHandlers.js +1 -1
  4. package/dist/src/main.js +1 -1
  5. package/dist/src/preload.cjs +26 -8
  6. package/dist/src/renderer/app.js +1 -1
  7. package/dist/src/renderer/index.html +317 -13
  8. package/dist/src/renderer/styles.css +451 -2
  9. package/dist/src/services/aiChatService.js +1 -0
  10. package/dist/src/services/authApiService.js +1 -1
  11. package/dist/src/services/trade/gmoCoinGateway.js +1 -0
  12. package/dist/src/services/trade/gmoKlineService.js +1 -0
  13. package/dist/src/services/trade/gmoWindowService.js +1 -0
  14. package/dist/src/services/trade/tradeConfigService.js +1 -0
  15. package/dist/src/services/trade/tradeStatusService.js +1 -0
  16. package/dist/src/services/trade/types.js +1 -0
  17. package/dist/src/services/windowManagerService.js +1 -1
  18. package/dist/src/wsClient.js +1 -1
  19. package/package.json +1 -7
  20. package/dist/src/renderer/chat.css +0 -128
  21. package/dist/src/renderer/chat.html +0 -27
  22. package/dist/src/renderer/chat.js +0 -1
  23. package/dist/src/services/aiMemoryService.js +0 -1
  24. package/dist/src/services/commandOrchestratorService.js +0 -1
  25. package/dist/src/services/debugLogService.js +0 -1
  26. package/dist/src/services/taskExecutorService.js +0 -1
  27. package/dist/src/services/taskParsingService.js +0 -1
  28. package/dist/src/services/textService.js +0 -1
  29. package/dist/src/services/workflowService.js +0 -1
  30. package/scripts/backtest-loop.cjs +0 -137
  31. package/scripts/prepare-playwright-browsers.cjs +0 -181
  32. package/scripts/run-workflow.cjs +0 -381
  33. package/scripts/workflows/kaitori-login.json +0 -17
@@ -11,6 +11,31 @@ body {
11
11
  padding: 0 12px 16px;
12
12
  }
13
13
 
14
+ .user-notice {
15
+ position: fixed;
16
+ top: 14px;
17
+ left: 50%;
18
+ transform: translateX(-50%);
19
+ z-index: 1200;
20
+ width: min(560px, calc(100vw - 24px));
21
+ border-radius: 10px;
22
+ padding: 10px 12px;
23
+ font-size: 14px;
24
+ box-shadow: 0 10px 22px rgba(20, 25, 45, 0.18);
25
+ }
26
+
27
+ .user-notice.notice-error {
28
+ background: #fee2e2;
29
+ color: #7f1d1d;
30
+ border: 1px solid #fecaca;
31
+ }
32
+
33
+ .user-notice.notice-success {
34
+ background: #dcfce7;
35
+ color: #14532d;
36
+ border: 1px solid #bbf7d0;
37
+ }
38
+
14
39
  .login-screen {
15
40
  min-height: 100vh;
16
41
  display: grid;
@@ -144,6 +169,320 @@ body {
144
169
  box-shadow: 0 8px 20px rgba(20, 25, 45, 0.08);
145
170
  }
146
171
 
172
+ .market-shell {
173
+ background: #eef2f6;
174
+ }
175
+
176
+ .market-layout {
177
+ display: grid;
178
+ grid-template-columns: 1fr 340px;
179
+ gap: 12px;
180
+ }
181
+
182
+ .market-main {
183
+ display: grid;
184
+ gap: 12px;
185
+ }
186
+
187
+ .market-side {
188
+ display: grid;
189
+ align-content: start;
190
+ gap: 12px;
191
+ }
192
+
193
+ .market-panel-header {
194
+ display: flex;
195
+ justify-content: space-between;
196
+ align-items: center;
197
+ margin-bottom: 8px;
198
+ }
199
+
200
+ .quote-table {
201
+ width: 100%;
202
+ border-collapse: collapse;
203
+ background: #fff;
204
+ }
205
+
206
+ .quote-table th {
207
+ background: #273445;
208
+ color: #fff;
209
+ font-weight: 600;
210
+ padding: 10px;
211
+ border-right: 1px solid #52657a;
212
+ }
213
+
214
+ .quote-table td {
215
+ padding: 10px;
216
+ border-bottom: 1px solid #e4e9f0;
217
+ text-align: right;
218
+ font-variant-numeric: tabular-nums;
219
+ }
220
+
221
+ .quote-table tbody tr {
222
+ cursor: pointer;
223
+ }
224
+
225
+ .quote-table tbody tr:hover {
226
+ background: #f6f9fd;
227
+ }
228
+
229
+ .quote-table td:first-child,
230
+ .quote-table th:first-child {
231
+ text-align: left;
232
+ }
233
+
234
+ .quote-table tr.selected {
235
+ outline: 2px solid #f4b400;
236
+ outline-offset: -2px;
237
+ }
238
+
239
+ .quote-table td.up {
240
+ color: #16924f;
241
+ }
242
+
243
+ .up {
244
+ color: #16924f;
245
+ }
246
+
247
+ .order-panel {
248
+ background: #1f3042;
249
+ color: #fff;
250
+ }
251
+
252
+ .side-title-row h3 {
253
+ margin: 0;
254
+ }
255
+
256
+ .side-price-box {
257
+ display: grid;
258
+ grid-template-columns: 1fr 1fr;
259
+ gap: 0;
260
+ margin-bottom: 10px;
261
+ border-radius: 8px;
262
+ overflow: hidden;
263
+ }
264
+
265
+ .side-bid,
266
+ .side-ask {
267
+ padding: 10px;
268
+ display: grid;
269
+ gap: 4px;
270
+ }
271
+
272
+ .side-price-box {
273
+ position: relative;
274
+ }
275
+
276
+ .side-spread {
277
+ position: absolute;
278
+ left: 50%;
279
+ top: 50%;
280
+ transform: translate(-50%, -50%);
281
+ width: 52px;
282
+ height: 52px;
283
+ border-radius: 50%;
284
+ background: #082744;
285
+ border: 2px solid #0c3f66;
286
+ color: #ffffff;
287
+ display: grid;
288
+ place-items: center;
289
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
290
+ }
291
+
292
+ .side-spread strong {
293
+ font-size: 14px;
294
+ line-height: 1;
295
+ font-variant-numeric: tabular-nums;
296
+ }
297
+
298
+ .side-bid {
299
+ background: #1f9d55;
300
+ }
301
+
302
+ .side-ask {
303
+ background: #e24646;
304
+ text-align: right;
305
+ justify-items: end;
306
+ }
307
+
308
+ .side-label {
309
+ width: 48px;
310
+ color: #d8e4f2;
311
+ align-self: center;
312
+ }
313
+
314
+ .order-qty-row {
315
+ display: grid;
316
+ grid-template-columns: 1fr 1fr;
317
+ gap: 0;
318
+ width: 100%;
319
+ }
320
+
321
+ .order-qty-row .side-label {
322
+ width: auto;
323
+ justify-self: start;
324
+ }
325
+
326
+ .qty-input-wrap {
327
+ display: flex;
328
+ justify-content: flex-end;
329
+ }
330
+
331
+ .qty-input-wrap input {
332
+ width: 66.666%;
333
+ min-width: 140px;
334
+ }
335
+
336
+ .market-icon {
337
+ width: 22px;
338
+ height: 22px;
339
+ border-radius: 50%;
340
+ background: #fff;
341
+ border: 1px solid #d8e1f0;
342
+ }
343
+
344
+ .order-panel input {
345
+ background: #fff;
346
+ color: #1c2a3b;
347
+ }
348
+
349
+ .required-amount-box {
350
+ margin-top: 8px;
351
+ background: #5f99cc;
352
+ border-radius: 8px;
353
+ padding: 8px 10px;
354
+ display: grid;
355
+ gap: 4px;
356
+ }
357
+
358
+ .required-amount-box span {
359
+ color: #eef5ff;
360
+ font-size: 13px;
361
+ }
362
+
363
+ .required-amount-box strong {
364
+ color: #ffffff;
365
+ font-size: 24px;
366
+ line-height: 1;
367
+ font-variant-numeric: tabular-nums;
368
+ justify-self: end;
369
+ }
370
+
371
+ .buying-power-head {
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: space-between;
375
+ }
376
+
377
+ .buying-power-refresh-btn {
378
+ width: 28px;
379
+ height: 28px;
380
+ border-radius: 6px;
381
+ padding: 0;
382
+ display: inline-flex;
383
+ align-items: center;
384
+ justify-content: center;
385
+ font-size: 15px;
386
+ line-height: 1;
387
+ }
388
+
389
+ .order-account-info {
390
+ margin-top: 12px;
391
+ background: #f3f6fb;
392
+ border: 1px solid #b9c9df;
393
+ border-radius: 8px;
394
+ padding: 8px;
395
+ display: grid;
396
+ gap: 6px;
397
+ }
398
+
399
+ .order-account-info .buying-power-head span {
400
+ color: #2b3f5d;
401
+ font-weight: 600;
402
+ }
403
+
404
+ .info-row {
405
+ display: flex;
406
+ justify-content: space-between;
407
+ align-items: center;
408
+ gap: 12px;
409
+ }
410
+
411
+ .info-row span {
412
+ color: #3f526f;
413
+ font-size: 13px;
414
+ }
415
+
416
+ .info-row strong {
417
+ color: #243a57;
418
+ font-size: 36px;
419
+ line-height: 1;
420
+ font-variant-numeric: tabular-nums;
421
+ }
422
+
423
+ .order-account-info .info-row strong {
424
+ font-size: 18px;
425
+ }
426
+
427
+ .position-panel {
428
+ padding: 10px;
429
+ }
430
+
431
+ .position-head {
432
+ display: flex;
433
+ justify-content: space-between;
434
+ align-items: center;
435
+ margin-bottom: 8px;
436
+ }
437
+
438
+ .position-head h3 {
439
+ margin: 0;
440
+ font-size: 24px;
441
+ color: #1a2a44;
442
+ }
443
+
444
+ .position-table {
445
+ width: 100%;
446
+ border-collapse: collapse;
447
+ background: #fff;
448
+ font-size: 13px;
449
+ }
450
+
451
+ .position-table th {
452
+ background: #273445;
453
+ color: #fff;
454
+ padding: 8px 6px;
455
+ border-right: 1px solid #4d6278;
456
+ text-align: center;
457
+ line-height: 1.1;
458
+ }
459
+
460
+ .position-table td {
461
+ border-bottom: 1px solid #e4e9f0;
462
+ padding: 8px 6px;
463
+ text-align: center;
464
+ }
465
+
466
+ .empty-cell {
467
+ color: #5f7089;
468
+ }
469
+
470
+ .close-btn {
471
+ border: 1px solid #9aa8bd;
472
+ background: #fff;
473
+ color: #334862;
474
+ padding: 3px 8px;
475
+ border-radius: 6px;
476
+ font-size: 12px;
477
+ }
478
+
479
+ .positions-grid {
480
+ display: grid;
481
+ grid-template-columns: 1fr 1fr;
482
+ gap: 12px;
483
+ margin-top: 12px;
484
+ }
485
+
147
486
  h2 {
148
487
  margin: 0 0 10px;
149
488
  }
@@ -184,12 +523,100 @@ textarea {
184
523
  gap: 10px;
185
524
  }
186
525
 
526
+ .inline-input-row {
527
+ display: grid;
528
+ grid-template-columns: 1fr auto;
529
+ gap: 0;
530
+ }
531
+
532
+ .inline-input-row input {
533
+ border-top-right-radius: 0;
534
+ border-bottom-right-radius: 0;
535
+ }
536
+
537
+ .inline-input-row button {
538
+ border-top-left-radius: 0;
539
+ border-bottom-left-radius: 0;
540
+ min-width: 64px;
541
+ }
542
+
187
543
  .config-actions {
188
544
  margin: 10px 0;
189
545
  align-items: center;
190
546
  flex-wrap: wrap;
191
547
  }
192
548
 
549
+ .order-action-row {
550
+ display: grid;
551
+ grid-template-columns: 1fr 1fr;
552
+ gap: 0;
553
+ width: 100%;
554
+ margin-top: 8px;
555
+ }
556
+
557
+ .order-action-row .order-buy-btn,
558
+ .order-action-row .order-sell-btn {
559
+ width: 100%;
560
+ min-height: 40px;
561
+ border-radius: 0;
562
+ }
563
+
564
+ .order-action-row .order-buy-btn {
565
+ background: #1f9d55;
566
+ color: #ffffff;
567
+ border-top-left-radius: 8px;
568
+ border-bottom-left-radius: 8px;
569
+ }
570
+
571
+ .order-action-row .order-sell-btn {
572
+ background: #e24646;
573
+ color: #ffffff;
574
+ border-top-right-radius: 8px;
575
+ border-bottom-right-radius: 8px;
576
+ }
577
+
578
+ .market-pair-row {
579
+ justify-content: flex-start;
580
+ gap: 8px;
581
+ margin-left: 0;
582
+ }
583
+
584
+ .kline-toolbar {
585
+ display: flex;
586
+ gap: 8px;
587
+ margin-bottom: 6px;
588
+ }
589
+
590
+ .kline-toolbar .interval-btn {
591
+ background: #e6edf8;
592
+ color: #223451;
593
+ border: 1px solid #c7d4ea;
594
+ padding: 7px 12px;
595
+ }
596
+
597
+ .kline-toolbar .interval-btn.is-active {
598
+ background: #2359d1;
599
+ color: #fff;
600
+ border-color: #2359d1;
601
+ }
602
+
603
+ #coin-kline-canvas,
604
+ #fx-kline-canvas {
605
+ width: 100%;
606
+ border: 1px solid #dde4f0;
607
+ border-radius: 10px;
608
+ background: #ffffff;
609
+ margin: 6px 0 10px;
610
+ }
611
+
612
+ .fx-theme #fx-kline-canvas {
613
+ background: #f7fbff;
614
+ }
615
+
616
+ .coin-theme #coin-kline-canvas {
617
+ background: #fffdf7;
618
+ }
619
+
193
620
  button {
194
621
  border: none;
195
622
  border-radius: 8px;
@@ -261,8 +688,8 @@ button.is-loading::before {
261
688
  border: 1px solid #dde4f0;
262
689
  border-radius: 12px;
263
690
  padding: 12px;
264
- min-height: 320px;
265
- max-height: 420px;
691
+ min-height: 280px;
692
+ max-height: none;
266
693
  overflow: auto;
267
694
  margin-bottom: 10px;
268
695
  }
@@ -299,6 +726,22 @@ button.is-loading::before {
299
726
  justify-self: end;
300
727
  }
301
728
 
729
+ #tab-ai .panel {
730
+ display: flex;
731
+ flex-direction: column;
732
+ height: calc(100vh - 180px);
733
+ min-height: 520px;
734
+ }
735
+
736
+ #tab-ai .messages {
737
+ flex: 1 1 auto;
738
+ margin-bottom: 8px;
739
+ }
740
+
741
+ #tab-ai .composer {
742
+ flex: 0 0 auto;
743
+ }
744
+
302
745
  .logs-list {
303
746
  background: #10141f;
304
747
  color: #ddedff;
@@ -377,3 +820,9 @@ button.is-loading::before {
377
820
  .json-value-null {
378
821
  color: #9aa6bf;
379
822
  }
823
+
824
+ @media (max-width: 980px) {
825
+ .positions-grid {
826
+ grid-template-columns: 1fr;
827
+ }
828
+ }
@@ -0,0 +1 @@
1
+ import e from"openai";export const createAiChatService=({getApiKey:t,getModel:i})=>({sendChat:async n=>{const r=String(t()||"").trim();if(!r)throw new Error("OpenAI API Key is not configured");const o=String(i()||"gpt-4.1-mini").trim()||"gpt-4.1-mini",c=new e({apiKey:r}),s=await c.chat.completions.create({model:o,messages:[{role:"user",content:String(n||"")}]});return String(s.choices?.[0]?.message?.content||"").trim()}});
@@ -1 +1 @@
1
- const o=o=>o&&"object"==typeof o?o:{};export const createAuthApiService=({env:r,emitLog:e,getCurrentSession:t})=>{const i=async r=>{const e=await r.text();if(!e)return{};try{const r=JSON.parse(e);return o(r)}catch{return{raw:e}}};return{signInWithLoginApi:async(t,n)=>{if(!r.loginApiUrl)throw new Error("Missing BOT_LOGIN_API_URL in bot/.env");const l=new AbortController,a=setTimeout(()=>l.abort(),15e3);let s;e("[auth] login api request",{url:r.loginApiUrl,email:t});try{s=await fetch(r.loginApiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t,password:n,client:"bot"}),signal:l.signal})}catch(r){const e=o(o(r).cause),t=e?`${String(e.code||"")} ${String(e.syscall||"")} ${String(e.hostname||"")}`.trim():"";throw new Error("login fetch failed"+(t?`: ${t}`:""))}finally{clearTimeout(a)}e("[auth] login api response",{status:s.status});const p=await i(s);if(!s.ok)throw new Error(String(p.error||p.message||p.msg||"Login failed"));return((r,e)=>{const t=o(r),i=o(t.data),n=o(t.user),l=o(i.user),a=t.access_token||t.token||t.jwt||i.access_token||i.token;if(!a)throw new Error("Login API response missing token field (access_token/token/jwt)");const s=t.user_id||n.id||t.sub||i.user_id||l.id||"",p=t.email||n.email||i.email||l.email||e,c=t.chat_api_key||i.chat_api_key||"",g=t.chat_model||i.chat_model||"gpt-4.1-mini",u=t.chat_system_prompt||i.chat_system_prompt||"";return{accessToken:String(a),userId:String(s),email:String(p),chatApiKey:String(c),chatModel:String(g),chatSystemPrompt:String(u)}})(p,t)},callBotApi:async(o,r)=>{if(!o)throw new Error("memory api url is missing");const e=t();if(!e?.accessToken)throw new Error("bot session access token missing");const n=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.accessToken}`},body:JSON.stringify(r||{})}),l=await i(n);if(!n.ok)throw new Error(String(l.error||l.message||`api failed: ${n.status}`));return l},requestBotImageUploadUrl:async({filename:o,contentType:e})=>{const n=r.botUploadApiUrl?r.botUploadApiUrl:r.loginApiUrl?r.loginApiUrl.replace(/\/login\/?$/i,"/bot/upload-url"):"";if(!n)throw new Error("Missing BOT_UPLOAD_API_URL and BOT_LOGIN_API_URL");const l=t();if(!l?.accessToken)throw new Error("bot session access token missing");const a=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l.accessToken}`},body:JSON.stringify({bot_id:l.botId||"",filename:o,content_type:e})}),s=await i(a);if(!a.ok)throw new Error(String(s.error||s.message||"failed to request bot upload url"));if(!s.upload_url||!s.preview_url)throw new Error("upload url api response missing upload_url or preview_url");return{upload_url:String(s.upload_url),preview_url:String(s.preview_url),file_url:s.file_url?String(s.file_url):void 0}},uploadImageToSignedUrl:async({uploadUrl:o,contentType:r,bytes:e})=>{const t=await fetch(o,{method:"PUT",headers:{"Content-Type":r},body:e});if(!t.ok){const o=await t.text();throw new Error(`s3 upload failed: ${t.status} ${o}`)}},getBotMemoryUploadApiUrl:()=>r.botMemoryUploadApiUrl?r.botMemoryUploadApiUrl:r.loginApiUrl?r.loginApiUrl.replace(/\/login\/?$/i,"/bot/memory/upload"):"",getBotMemoryArchivesApiUrl:()=>r.botMemoryArchivesApiUrl?r.botMemoryArchivesApiUrl:r.loginApiUrl?r.loginApiUrl.replace(/\/login\/?$/i,"/bot/memory/archives"):"",getBotMemoryDownloadApiUrl:()=>r.botMemoryDownloadApiUrl?r.botMemoryDownloadApiUrl:r.loginApiUrl?r.loginApiUrl.replace(/\/login\/?$/i,"/bot/memory/download"):"",getBotWorkflowListApiUrl:()=>r.botWorkflowListApiUrl?r.botWorkflowListApiUrl:r.loginApiUrl?r.loginApiUrl.replace(/\/login\/?$/i,"/bot/workflow/list"):"",getBotRuntimeConfigApiUrl:()=>r.botRuntimeConfigApiUrl?r.botRuntimeConfigApiUrl:r.loginApiUrl?r.loginApiUrl.replace(/\/login\/?$/i,"/bot/runtime-config"):""}};
1
+ const o=o=>o&&"object"==typeof o?o:{},t=(o,r="")=>{if(null==o)return o;const e=String(r||"");if("string"==typeof o)return/(password|token|authorization|api_?key|secret|cookie|jwt)/i.test(e)?o.length<=8?"****":`${o.slice(0,3)}****${o.slice(-3)}`:o.length>2e3?`${o.slice(0,2e3)}...(truncated)`:o;if("number"==typeof o||"boolean"==typeof o)return o;if(Array.isArray(o)){const r=o.slice(0,50).map(o=>t(o,e));return o.length>50&&r.push(`...(${o.length-50} more items)`),r}if("object"==typeof o){const r={};for(const[e,i]of Object.entries(o))r[e]=t(i,e);return r}return String(o)};export const createAuthApiService=({getEnv:r,emitLog:e,getCurrentSession:i})=>{const n=async t=>{const r=await t.text();if(!r)return{};try{const t=JSON.parse(r);return o(t)}catch{return{raw:r}}};return{signInWithLoginApi:async(t,i)=>{const l=r();if(!l.loginApiUrl)throw new Error("Missing loginApiUrl in local config");const s=new AbortController,a=setTimeout(()=>s.abort(),15e3);let p;e("[auth] login api request",{url:l.loginApiUrl,email:t});try{p=await fetch(l.loginApiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t,password:i,client:"bot"}),signal:s.signal})}catch(t){const r=o(o(t).cause),e=r?`${String(r.code||"")} ${String(r.syscall||"")} ${String(r.hostname||"")}`.trim():"";throw new Error("login fetch failed"+(e?`: ${e}`:""))}finally{clearTimeout(a)}e("[auth] login api response",{status:p.status});const c=await n(p);if(!p.ok)throw new Error(String(c.error||c.message||c.msg||"Login failed"));return((t,r)=>{const e=o(t),i=o(e.data),n=o(e.user),l=o(i.user),s=e.access_token||e.token||e.jwt||i.access_token||i.token;if(!s)throw new Error("Login API response missing token field (access_token/token/jwt)");const a=e.user_id||n.id||e.sub||i.user_id||l.id||"",p=e.email||n.email||i.email||l.email||r,c=e.chat_api_key||i.chat_api_key||"",u=e.chat_model||i.chat_model||"gpt-4.1-mini",g=e.chat_system_prompt||i.chat_system_prompt||"";return{accessToken:String(s),userId:String(a),email:String(p),chatApiKey:String(c),chatModel:String(u),chatSystemPrompt:String(g)}})(c,t)},callBotApi:async(o,r)=>{if(!o)throw new Error("memory api url is missing");const l=i();if(!l?.accessToken)throw new Error("bot session access token missing");e("[bot-api] request",{url:o,bot_id:l.botId||"",body:t(r)});const s=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l.accessToken}`},body:JSON.stringify(r||{})}),a=await n(s);if(e("[bot-api] response",{url:o,status:s.status,ok:s.ok,body:t(a)}),!s.ok)throw new Error(String(a.error||a.message||`api failed: ${s.status}`));return a},requestBotImageUploadUrl:async({filename:o,contentType:l})=>{const s=(()=>{const o=r();return o.botUploadApiUrl?o.botUploadApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/upload-url"):""})();if(!s)throw new Error("Missing upload/login api url in local config");const a=i();if(!a?.accessToken)throw new Error("bot session access token missing");e("[bot-api] request",{url:s,bot_id:a.botId||"",body:t({bot_id:a.botId||"",filename:o,content_type:l})});const p=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a.accessToken}`},body:JSON.stringify({bot_id:a.botId||"",filename:o,content_type:l})}),c=await n(p);if(e("[bot-api] response",{url:s,status:p.status,ok:p.ok,body:t(c)}),!p.ok)throw new Error(String(c.error||c.message||"failed to request bot upload url"));if(!c.upload_url||!c.preview_url)throw new Error("upload url api response missing upload_url or preview_url");return{upload_url:String(c.upload_url),preview_url:String(c.preview_url),file_url:c.file_url?String(c.file_url):void 0}},uploadImageToSignedUrl:async({uploadUrl:o,contentType:t,bytes:r})=>{const e=await fetch(o,{method:"PUT",headers:{"Content-Type":t},body:r});if(!e.ok){const o=await e.text();throw new Error(`s3 upload failed: ${e.status} ${o}`)}},getBotMemoryUploadApiUrl:()=>{const o=r();return o.botMemoryUploadApiUrl?o.botMemoryUploadApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/memory/upload"):""},getBotMemorySyncApiUrl:()=>{const o=r();return o.botMemorySyncApiUrl?o.botMemorySyncApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/memory/sync"):""},getBotMemoryQueryApiUrl:()=>{const o=r();return o.botMemoryQueryApiUrl?o.botMemoryQueryApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/memory/query"):""},getBotWorkflowListApiUrl:()=>{const o=r();return o.botWorkflowListApiUrl?o.botWorkflowListApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/workflow/list"):""},getBotWorkflowQueryApiUrl:()=>{const o=r();return o.botWorkflowQueryApiUrl?o.botWorkflowQueryApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/workflow/query"):""},getBotWorkflowVerificationUpdateApiUrl:()=>{const o=r();return o.botWorkflowVerificationUpdateApiUrl?o.botWorkflowVerificationUpdateApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/workflow/verification/update"):""},getBotRuntimeConfigApiUrl:()=>{const o=r();return o.botRuntimeConfigApiUrl?o.botRuntimeConfigApiUrl:o.loginApiUrl?o.loginApiUrl.replace(/\/login\/?$/i,"/bot/runtime-config"):""}}};
@@ -0,0 +1 @@
1
+ import t from"node:crypto";export class GmoGatewayError extends Error{code;status;constructor(t,e,r=0){super(e),this.name="GmoGatewayError",this.code=t,this.status=r}}const e=t=>t&&"object"==typeof t?t:{},r=(t,e=0)=>{const r="string"==typeof t?t.trim().replace(/,/g,"").replace(/[^\d.+-]/g,""):t,a=Number(r);return Number.isFinite(a)?a:e};export const createGmoCoinGateway=a=>{let o=()=>a,s=()=>({apiKey:"",apiSecret:""}),i=()=>{};const n=async({method:r,path:a,body:n,query:d,market:c="crypto"})=>{const p=s(c);if(!p.apiKey||!p.apiSecret)throw i("[trade] private api skipped: credentials missing",{market:c,method:r,path:a}),new GmoGatewayError("API_KEY_MISSING","GMO API key/secret is missing");const m=o(),l=(t=>{const e=String(t||"").trim();return e?e.startsWith("/")?e:`/${e}`:"/"})(a),u="fx"===c?m.fxApiBaseUrl:m.cryptoApiBaseUrl,y=String(u||"").trim().replace(/\/$/,""),h=d?Object.entries(d).filter(([,t])=>null!=t&&""!==String(t)).map(([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(String(e))}`).join("&"):"",g=h?`${y}${l}?${h}`:`${y}${l}`,b=n?JSON.stringify(n):"",A=Date.now().toString(),w=t.createHmac("sha256",p.apiSecret).update(`${A}${r}${l}${b}`).digest("hex");let f;i("[trade] private api request",{market:c,method:r,path:l,query:d||null});try{f=await fetch(g,{method:r,headers:{"Content-Type":"application/json","API-KEY":p.apiKey,"API-TIMESTAMP":A,"API-SIGN":w},body:"POST"===r?b:void 0})}catch(t){throw i("[trade] private api network error",{market:c,method:r,path:l,error:t instanceof Error?t.message:String(t)}),new GmoGatewayError("NETWORK_ERROR",t instanceof Error?t.message:String(t))}const T=await(async t=>{const r=await t.text();if(!r)return{};try{return e(JSON.parse(r))}catch{return{raw:r}}})(f),I=(t=>{const r=t.messages;if(Array.isArray(r)){const t=r.map(t=>e(t)).map(t=>{const e=String(t.message_code||"").trim(),r=String(t.message_string||"").trim();return e&&r?`${e}: ${r}`:e||r}).filter(Boolean);if(t.length)return t.join(" | ")}return String(t.message||t.messages||"gmo error")})(T);if(i("[trade] private api response",{market:c,method:r,path:l,query:d||null,status:f.status,ok:f.ok,gmoStatus:T.status??null,messages:I}),!(t=>{const e=t.status;return null==e||""===e||"0"===String(e)})(T))throw i("[trade] private api business error",{market:c,method:r,path:l,query:d||null,gmoStatus:T.status??null,messages:I}),new GmoGatewayError("HTTP_ERROR",String(I||"gmo business error"),f.status||200);if(401===f.status||403===f.status)throw i("[trade] private api auth error",{market:c,method:r,path:l,query:d||null,status:f.status,message:I}),new GmoGatewayError("AUTH_ERROR",I,f.status);if(!f.ok)throw i("[trade] private api http error",{market:c,method:r,path:l,query:d||null,status:f.status,message:I}),new GmoGatewayError("HTTP_ERROR",String(I||`http error: ${f.status}`),f.status);return T},d=async()=>{const t=o(),e=await n({method:"GET",path:t.assetsPath,market:"crypto"});return Array.isArray(e.data)?e.data:[]};return{getCredentialsState:(t="crypto")=>{const e=s(t);return e.apiKey&&e.apiSecret?"ready":"api_key_missing"},setRuntimeConfigProvider:t=>{o=t},setCredentialsProvider:t=>{s=t},setLogEmitter:t=>{i=t},getAssets:d,getOpenPositions:async({market:t="crypto",symbol:r,page:a,prevId:s,count:d}={})=>{const c=o(),p="fx"===t?{symbol:r,prevId:s,count:d}:{symbol:r,page:a,count:d},m=await n({method:"GET",path:c.positionsPath,market:t,query:p}),l=e(m.data),u=Array.isArray(m.data)?m.data:Array.isArray(l.list)?l.list:[];return i("[trade] open positions parsed",{market:t,symbol:r||null,page:a??null,prevId:s??null,count:d??null,rows:u.length}),u},testConnection:async()=>({ok:!0,assetRows:(await d()).length}),getBuyingPower:async t=>{const a=o();if("fx"===t){const o=a.fxAssetsPath||a.assetsPath||"/v1/account/assets",s=await n({method:"GET",path:o,market:"fx"}),d=(t=>{const a=e(t.data);if(Array.isArray(t.data)){const a=e(t.data[0]);return r(a.availableAmount||0)}return r(a.availableAmount||0)})(s);return i("[trade] buying power parsed",{market:t,path:o,availableJpy:d,dataType:Array.isArray(s.data)?"array":typeof s.data,dataKeys:Object.keys(e(s.data||{}))}),d}const s=a.coinMarginPath||"/v1/account/margin",d=await n({method:"GET",path:s,market:"crypto"}),c=(t=>{const a=e(t.data),o=e(t);return r(a.availableAmount||o.availableAmount||0)})(d);return i("[trade] buying power parsed",{market:t,path:s,availableJpy:c,dataKeys:Object.keys(e(d.data||{}))}),c},getAccountMetrics:async t=>{const a=o();if("fx"===t){const o=a.fxAssetsPath||a.assetsPath||"/v1/account/assets",s=await n({method:"GET",path:o,market:"fx"}),d=e(s.data),c=Array.isArray(s.data)?e(s.data[0]):d,p=r(c.availableAmount||0),m=r(c.margin||0),l=r(c.marginRatio||0),u=r(c.positionLossGain||0)+r(c.totalSwap||0);return i("[trade] account metrics parsed",{market:t,availableAmount:p,margin:m,marginRatio:l,pnlWithSwap:u}),{market:t,availableAmount:p,margin:m,marginRatio:l,pnlWithSwap:u}}const s=a.coinMarginPath||"/v1/account/margin",d=await n({method:"GET",path:s,market:"crypto"}),c=e(d.data),p=r(c.availableAmount||0),m=r(c.margin||0),l=r(c.marginRatio||0),u=r(c.profitLoss||0);return i("[trade] account metrics parsed",{market:t,availableAmount:p,margin:m,marginRatio:l,pnlWithSwap:u}),{market:t,availableAmount:p,margin:m,marginRatio:l,pnlWithSwap:u}},getPositionSummary:async({market:t,symbol:r})=>{const a="fx"===t?"fx":"crypto",s=o().positionSummaryPath||"/v1/positionSummary",d=await n({method:"GET",path:s,market:a,query:{symbol:r||void 0}}),c=e(d.data),p=Array.isArray(c.list)?c.list:Array.isArray(d.data)?d.data:[];return i("[trade] position summary parsed",{market:t,symbol:r||null,rows:p.length}),p.map(t=>e(t))},placeFxOrder:async t=>{const r=o().fxOrderPath||"/v1/order",a={symbol:t.symbol,side:t.side,size:t.size,executionType:t.executionType||"MARKET"};t.clientOrderId&&(a.clientOrderId=t.clientOrderId),t.limitPrice&&(a.limitPrice=t.limitPrice),t.stopPrice&&(a.stopPrice=t.stopPrice),t.lowerBound&&(a.lowerBound=t.lowerBound),t.upperBound&&(a.upperBound=t.upperBound);const s=await n({method:"POST",path:r,market:"fx",body:a}),d=Array.isArray(s.data)?s.data:[],c=e(d[0]);return i("[trade] fx order placed",{symbol:t.symbol,side:t.side,size:t.size,executionType:t.executionType||"MARKET",orderId:c.orderId??null,rootOrderId:c.rootOrderId??null,status:c.status??null}),c},placeCoinOrder:async t=>{const e={symbol:t.symbol,side:t.side,executionType:t.executionType||"MARKET",size:t.size};t.timeInForce&&(e.timeInForce=t.timeInForce),t.price&&(e.price=t.price),t.losscutPrice&&(e.losscutPrice=t.losscutPrice),"boolean"==typeof t.cancelBefore&&(e.cancelBefore=t.cancelBefore);const r=o().fxOrderPath||"/v1/order",a=await n({method:"POST",path:r,market:"crypto",body:e}),s=String(a.data??"").trim();return i("[trade] coin order placed",{symbol:t.symbol,side:t.side,size:t.size,executionType:t.executionType||"MARKET",orderId:s||null}),{orderId:s,rawData:a.data??null}},placeFxCloseOrder:async t=>{const r=o().fxCloseOrderPath||"/v1/closeOrder",a={symbol:t.symbol,side:t.side,executionType:t.executionType||"MARKET",settlePosition:[{positionId:t.positionId,size:t.size}]},s=await n({method:"POST",path:r,market:"fx",body:a}),d=Array.isArray(s.data)?s.data:[],c=e(d[0]);return i("[trade] fx close order placed",{symbol:t.symbol,side:t.side,positionId:t.positionId,size:t.size,executionType:t.executionType||"MARKET",orderId:c.orderId??null,rootOrderId:c.rootOrderId??null,status:c.status??null}),c},placeCoinCloseOrder:async t=>{const e=o().coinCloseOrderPath||"/v1/closeOrder",r={symbol:t.symbol,side:t.side,executionType:t.executionType||"MARKET",settlePosition:[{positionId:t.positionId,size:t.size}]};t.timeInForce&&(r.timeInForce=t.timeInForce),t.price&&(r.price=t.price),"boolean"==typeof t.cancelBefore&&(r.cancelBefore=t.cancelBefore);const a=await n({method:"POST",path:e,market:"crypto",body:r}),s=String(a.data??"").trim();return i("[trade] coin close order placed",{symbol:t.symbol,side:t.side,positionId:t.positionId,size:t.size,executionType:t.executionType||"MARKET",orderId:s||null}),{orderId:s,rawData:a.data??null}}}};
@@ -0,0 +1 @@
1
+ const t=(t,e=0)=>{const r=Number(t);return Number.isFinite(r)?r:e},e=t=>{const e=Number(t);return Number.isFinite(e)?e:null};export const createGmoKlineService=({getCoinPublicBaseUrl:r,getFxPublicBaseUrl:a})=>{const n=t=>("fx"===t?a():r()).replace(/\/$/,"");return{fetchQuotes:async({market:t="coin",symbols:r})=>{const a=`${n(t)}/v1/ticker`,i=await fetch(a,{method:"GET"}),s=await i.json().catch(()=>({}));if(!i.ok)throw new Error(String(s.message||s.messages||`ticker http error: ${i.status}`));const o=Array.isArray(s.data)?s.data:[],m=new Set((r||[]).map(t=>String(t||"").trim().toUpperCase()).filter(Boolean));return{market:t,quotes:o.map(t=>{const r=t&&"object"==typeof t?t:{},a=String(r.symbol||"").trim().toUpperCase();if(!a)return null;const n=e(r.bid),i=e(r.ask),s=e(r.last),o=e(r.open);return{symbol:a,bid:n,ask:i,spread:null!==i&&null!==n?i-n:null,changePct:null!==s&&null!==o&&0!==o?(s-o)/o*100:null}}).filter(t=>Boolean(t)).filter(t=>0===m.size||m.has(t.symbol))}},fetchKline:async({symbol:e,interval:r,market:a="coin"})=>{const i=String(e||"BTC_JPY").trim().toUpperCase(),s=(t=>{const e=String(t||"1m").trim().toLowerCase();return"1m"===e?"1min":"5m"===e?"5min":"10m"===e?"10min":"15m"===e?"15min":"30m"===e?"30min":"1h"===e?"1hour":"4h"===e?"4hour":"8h"===e?"8hour":"12h"===e?"12hour":"1d"===e?"1day":e})(r),o=n(a),m=(()=>{const t=new Date;return`${t.getUTCFullYear()}${String(t.getUTCMonth()+1).padStart(2,"0")}${String(t.getUTCDate()).padStart(2,"0")}`})(),l=new URLSearchParams({symbol:i,interval:s,date:m});"fx"===a&&l.set("priceType","ASK");const c=`${o}/v1/klines?${l.toString()}`,u=await fetch(c,{method:"GET"}),h=await u.json().catch(()=>({}));if(!u.ok)throw new Error(String(h.message||h.messages||`kline http error: ${u.status}`));return{symbol:i,interval:s,market:a,candles:(Array.isArray(h.data)?h.data:[]).map(e=>{const r=e&&"object"==typeof e?e:{};return{time:t(r.openTime||r.timestamp||0),open:t(r.open),high:t(r.high),low:t(r.low),close:t(r.close),volume:t(r.volume)}}).filter(t=>t.time>0&&t.high>=t.low).slice(-120)}},fetchSymbolRules:async({market:t="coin"})=>{const e=`${n(t)}/v1/symbols`,r=await fetch(e,{method:"GET"}),a=await r.json().catch(()=>({}));if(!r.ok)throw new Error(String(a.message||a.messages||`symbols http error: ${r.status}`));return{market:t,rules:(Array.isArray(a.data)?a.data:[]).map(t=>t&&"object"==typeof t?t:{}).map(t=>({symbol:String(t.symbol||"").trim().toUpperCase(),minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||""),tickSize:String(t.tickSize||"")})).filter(t=>t.symbol)}}}};
@@ -0,0 +1 @@
1
+ const e=e=>String(e||"BTC_JPY").trim().toUpperCase(),n=e=>String(e||"1m").trim().toLowerCase();export const createGmoWindowService=({BrowserWindow:o,preloadPath:t,getMainWindow:r,emitLog:i,getKlineBaseUrl:l})=>{let s=null;return{ensureKlineWindow:async({symbol:a,interval:d})=>{if(s&&!s.isDestroyed())return s.focus(),{url:s.webContents.getURL(),reused:!0};const c=((o,t)=>{const r=e(o),i=n(t),s=String(l()||"").trim().replace(/\/$/,"");return s?`${s}/${encodeURIComponent(r)}?interval=${encodeURIComponent(i)}`:`https://coin.z.com/jp/corp/chart/${encodeURIComponent(r)}?interval=${encodeURIComponent(i)}`})(a,d),u=r()||void 0;return s=new o({width:1300,height:900,autoHideMenuBar:!0,parent:u,modal:!1,webPreferences:{preload:t,contextIsolation:!0,nodeIntegration:!1}}),s.on("closed",()=>{s=null}),await s.loadURL(c),i("[trade] gmo kline window opened",{symbol:e(a),interval:n(d),url:c}),{url:c,reused:!1}},closeAllTradeWindows:()=>{s&&!s.isDestroyed()&&s.close(),s=null}}};
@@ -0,0 +1 @@
1
+ import i from"node:fs/promises";export const defaultTradeApiConfig=()=>({errorLogDir:"",riskDailyLossLimitJpy:5e4,cryptoApiKey:"",cryptoApiSecret:"",fxApiKey:"",fxApiSecret:"",aiModel:"gpt-4.1-mini",openAiApiKey:""});const e=i=>{const e=(i=>i&&"object"==typeof i?i:{})(i),r={errorLogDir:"",riskDailyLossLimitJpy:5e4,cryptoApiKey:"",cryptoApiSecret:"",fxApiKey:"",fxApiSecret:"",aiModel:"gpt-4.1-mini",openAiApiKey:""},t=Number(e.riskDailyLossLimitJpy);return{errorLogDir:String(e.errorLogDir||"").trim(),riskDailyLossLimitJpy:Number.isFinite(t)&&t>0?t:r.riskDailyLossLimitJpy,cryptoApiKey:String(e.cryptoApiKey||"").trim(),cryptoApiSecret:String(e.cryptoApiSecret||"").trim(),fxApiKey:String(e.fxApiKey||"").trim(),fxApiSecret:String(e.fxApiSecret||"").trim(),aiModel:String(e.aiModel||"gpt-4.1-mini").trim()||"gpt-4.1-mini",openAiApiKey:String(e.openAiApiKey||"").trim()}};export const createTradeConfigService=({configPath:r,emitLog:t})=>{let p={errorLogDir:"",riskDailyLossLimitJpy:5e4,cryptoApiKey:"",cryptoApiSecret:"",fxApiKey:"",fxApiSecret:"",aiModel:"gpt-4.1-mini",openAiApiKey:""};return{load:async()=>{try{const t=await i.readFile(r,"utf8");return p=e(JSON.parse(t)),p}catch(i){return"ENOENT"!==(i?.code||"")&&t("[trade-config] load failed",{error:i instanceof Error?i.message:String(i)}),p={errorLogDir:"",riskDailyLossLimitJpy:5e4,cryptoApiKey:"",cryptoApiSecret:"",fxApiKey:"",fxApiSecret:"",aiModel:"gpt-4.1-mini",openAiApiKey:""},p}},save:async o=>{const y=e(o);return p=y,await i.writeFile(r,JSON.stringify({...y,updatedAt:(new Date).toISOString()},null,2),"utf8"),t("[trade-config] saved",{hasCryptoKey:Boolean(y.cryptoApiKey),hasCryptoSecret:Boolean(y.cryptoApiSecret),hasFxKey:Boolean(y.fxApiKey),hasFxSecret:Boolean(y.fxApiSecret)}),p},get:()=>p,getCredentials:i=>"fx"===i?{apiKey:p.fxApiKey,apiSecret:p.fxApiSecret}:{apiKey:p.cryptoApiKey,apiSecret:p.cryptoApiSecret}}};
@@ -0,0 +1 @@
1
+ import{GmoGatewayError as e}from"./gmoCoinGateway.js";const r=e=>e&&"object"==typeof e?e:{},i=(e,r=0)=>{const i=Number(e);return Number.isFinite(i)?i:r},t=e=>Math.round(100*e)/100,a=({marginRatio:e,drawdownPct:r})=>e>0&&e<120?"high":e>0&&e<200?"medium":r>=15?"high":r>=8?"medium":"low";export const createTradeStatusService=({gateway:s,env:o,emitLog:n})=>{let p="unknown";const l=r=>r instanceof e?"API_KEY_MISSING"===r.code?"api_key_missing":"AUTH_ERROR"===r.code?"auth_error":"HTTP_ERROR"===r.code?"http_error":"network_error":"network_error";return{buildSnapshot:async()=>{const e=Number(o.getRiskDailyLossLimitJpy()||0);if("api_key_missing"===s.getCredentialsState())return p="api_key_missing",{broker:"gmocoin",as_of:(new Date).toISOString(),asset:{equity_jpy:0,available_jpy:0,margin_ratio:0},positions:{count:0,gross_exposure_jpy:0,items:[]},risk:{risk_level:"low",daily_pnl_jpy:0,max_daily_loss_limit_jpy:t(e),drawdown_pct:0,alerts:["GMO API key/secret is missing"]},api_state:p};try{const[o,n]=await Promise.all([s.getAssets(),s.getOpenPositions()]),l=Array.isArray(o)?o:[],_=Array.isArray(n)?n:[],y=_.map(e=>{const t=r(e),a=String(t.side||t.positionSide||"BUY").toUpperCase();return{symbol:String(t.symbol||t.symbolName||t.currencyPair||""),side:"SELL"===a?"SELL":"BUY",size:i(t.size||t.positionSize||t.orderSize),entryPrice:i(t.price||t.entryPrice),markPrice:i(t.currentPrice||t.markPrice||t.price),unrealizedPnlJpy:i(t.lossGain||t.unrealizedProfitLoss||0)}}),c=l.map(r).find(e=>"JPY"===String(e.symbol||e.currency||"").toUpperCase()),m=i(c?.amount||c?.equity||0),d=i(c?.available||c?.availableAmount||m),u=i(c?.marginRatio||0),g=t(y.reduce((e,r)=>e+i(r.unrealizedPnlJpy),0)),w=0,S=t(y.reduce((e,r)=>e+Math.abs(i(r.markPrice)*i(r.size)),0)),k=[];return u>0&&u<120&&k.push("Margin ratio below warning threshold"),g<0&&Math.abs(g)>=e&&k.push("Daily loss limit exceeded"),p="ready",{broker:"gmocoin",as_of:(new Date).toISOString(),asset:{equity_jpy:t(m),available_jpy:t(d),margin_ratio:t(u)},positions:{count:y.length,gross_exposure_jpy:S,items:y},risk:{risk_level:a({marginRatio:u,drawdownPct:w}),daily_pnl_jpy:g,max_daily_loss_limit_jpy:t(e),drawdown_pct:t(w),alerts:k},api_state:p}}catch(r){p=l(r);const i=r instanceof Error?r.message:String(r);return n("[trade] gmo snapshot failed",{apiState:p,error:i}),{broker:"gmocoin",as_of:(new Date).toISOString(),asset:{equity_jpy:0,available_jpy:0,margin_ratio:0},positions:{count:0,gross_exposure_jpy:0,items:[]},risk:{risk_level:"medium",daily_pnl_jpy:0,max_daily_loss_limit_jpy:t(e),drawdown_pct:0,alerts:[i]},api_state:p}}},testApi:async()=>{try{const e=await s.testConnection();return p="ready",{ok:e.ok,apiState:p,assetRows:e.assetRows}}catch(e){return p=l(e),{ok:!1,apiState:p,error:e instanceof Error?e.message:String(e)}}},getApiState:()=>p}};
@@ -0,0 +1 @@
1
+ export{};
@@ -1 +1 @@
1
- import e from"node:path";export const createWindowManager=({BrowserWindow:t,preloadPath:n,rendererDir:o,windowIconPath:i=""})=>{let a=null,d=null;return{getMainWindow:()=>a,getChatWindow:()=>d,createMainWindow:()=>(a=new t({width:980,height:700,icon:i||void 0,autoHideMenuBar:!0,webPreferences:{preload:n,contextIsolation:!0,nodeIntegration:!1}}),a.loadFile(e.join(o,"index.html")),a.setMenuBarVisibility(!1),a),createChatWindow:()=>d?(d.focus(),d):(d=new t({width:980,height:760,icon:i||void 0,autoHideMenuBar:!0,webPreferences:{preload:n,contextIsolation:!0,nodeIntegration:!1}}),d.loadFile(e.join(o,"chat.html")),d.setMenuBarVisibility(!1),d.on("closed",()=>{d=null}),d),closeChatWindow:()=>{d&&(d.close(),d=null)},emitChatEvent:e=>{a&&!a.isDestroyed()&&a.webContents.send("chat:event",e),d&&!d.isDestroyed()&&d.webContents.send("chat:event",e)},emitBotLog:e=>{a&&!a.isDestroyed()&&a.webContents.send("bot:log",e)},emitBotStatus:e=>{a&&!a.isDestroyed()&&a.webContents.send("bot:status",e)}}};
1
+ import e from"node:path";export const createWindowManager=({BrowserWindow:t,preloadPath:n,rendererDir:o,windowIconPath:i=""})=>{let r=null;return{getMainWindow:()=>r,createMainWindow:()=>(r=new t({width:1024,height:860,minWidth:1e3,minHeight:820,icon:i||void 0,autoHideMenuBar:!0,webPreferences:{preload:n,contextIsolation:!0,nodeIntegration:!1}}),r.loadFile(e.join(o,"index.html")),r.setMenuBarVisibility(!1),r),emitBotLog:e=>{r&&!r.isDestroyed()&&r.webContents.send("bot:log",e)},emitBotStatus:e=>{r&&!r.isDestroyed()&&r.webContents.send("bot:status",e)},emitChatEvent:e=>{r&&!r.isDestroyed()&&r.webContents.send("chat:event",e)}}};
@@ -1 +1 @@
1
- import t from"ws";export class BotWsClient{config;hooks;ws;heartbeatTimer;reconnectTimer;taskSeqMap;session;shouldReconnect;reconnectAttempts;maxReconnectAttempts;constructor(t,s={}){this.config=t,this.hooks=s,this.ws=null,this.heartbeatTimer=null,this.reconnectTimer=null,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}botId(){return this.session?.botId||this.config.botId||""}nowIso(){return(new Date).toISOString()}log(t,s){this.hooks.onLog&&this.hooks.onLog(t,s)}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))}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()})}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("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}),o=this.sanitizeLineReplyText(i?.answer||""),n=String(i?.imageUrl||"").trim(),r=String(i?.previewImageUrl||"").trim();(o||n)&&(this.sendJson({action:"line_reply",bot_id:this.botId(),user_id:this.session?.userId||void 0,line_user_id:s.line_user_id||"",text:o,image_url:n||void 0,preview_image_url:r||void 0,ts:this.nowIso()}),this.sendTaskEvent(e,"line_message_ai_output",{line_user_id:s.line_user_id||"",answer:o,image_url:n||"",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}if("workflow_execute"===e){const t=s;if(this.log("[bot] workflow_execute received",{bot_id:s.bot_id||this.botId(),workflow_id:s.workflow_id||"",trigger_source:s.trigger_source||""}),this.hooks.onWorkflowExecute)try{await this.hooks.onWorkflowExecute(t)}catch(t){this.log("[bot] onWorkflowExecute 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)}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.hooks.onStatus&&this.hooks.onStatus("reconnect_exhausted"));const t=this.reconnectAttempts+1;this.log("[bot] scheduling reconnect",{attempt:t,maxAttempts:this.maxReconnectAttempts,delayMs:this.config.reconnectMs}),this.hooks.onStatus&&this.hooks.onStatus("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.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),this.hooks.onStatus&&this.hooks.onStatus("connected")}),this.ws.on("message",t=>{this.onMessage(t)}),this.ws.on("error",t=>{if(this.log("[bot] ws error",{message:t.message}),this.hooks.onStatus&&this.hooks.onStatus("error"),this.shouldReconnect)try{this.ws?.terminate()}catch{}}),this.ws.on("close",()=>{this.log("[bot] disconnected"),this.clearTimers(),this.ws=null,this.hooks.onStatus&&this.hooks.onStatus("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)}}
1
+ import t from"ws";export class BotWsClient{config;hooks;ws;heartbeatTimer;reconnectTimer;pingTimer;pongTimeoutTimer;awaitingPong;taskSeqMap;session;shouldReconnect;reconnectAttempts;maxReconnectAttempts;currentStatus;constructor(t,e={}){this.config=t,this.hooks=e,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,e){this.hooks.onLog&&this.hooks.onLog(t,e)}setStatus(t){const e=String(t||"").trim().toLowerCase()||"disconnected";this.currentStatus!==e&&(this.currentStatus=e,this.hooks.onStatus&&this.hooks.onStatus(e))}getStatus(){return this.currentStatus}nextSeq(t){const e=(this.taskSeqMap.get(t)||0)+1;return this.taskSeqMap.set(t,e),e}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(e){this.ws&&this.ws.readyState===t.OPEN&&this.ws.send(JSON.stringify(e),t=>{if(t){this.log("[bot] ws send failed",{error:t.message});try{this.ws?.terminate()}catch{}}})}sanitizeLineReplyText(t){const e=String(t||"");return e?e.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,e,s){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:e,data_json:s})}sendBotState(t,e=""){const s=String(t||"").trim().toLowerCase();s&&this.sendTaskEvent("__bot_state__","bot_state",{state:s,reason:String(e||""),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 e=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{}}},e);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 e=t.task_id;e?(this.sendTaskEvent(e,"task_received",{bot_id:this.botId(),received_at:this.nowIso()}),this.sendTaskEvent(e,"step_started",{step:"open_url",ts:this.nowIso()}),await new Promise(t=>setTimeout(t,600)),this.sendTaskEvent(e,"step_finished",{step:"open_url",ts:this.nowIso()}),this.sendTaskEvent(e,"ai_output",{summary:"mock result from electron bot",next_action:"wait_human_review",risk_flags:[]}),this.sendTaskEvent(e,"task_completed",{ts:this.nowIso()})):this.log("[bot] run_task missing task_id")}async onMessage(t){let e;try{const s=JSON.parse(t.toString("utf8"));if(!s||"object"!=typeof s)return void this.log("[bot] ignored non-object message");e=s}catch{return void this.log("[bot] ignored non-JSON message")}const s=e.type||e.action||"";if("hello_ack"!==s){if("run_task"===s){const t=e,s=String(t.task_id||"").trim();this.log("[bot] run_task received",{task_id:s});const i=Boolean(this.hooks.onRunTask);try{i?await(this.hooks.onRunTask?.(t)):await this.runTaskMock(t)}catch(t){!i&&s&&this.sendTaskEvent(s,"task_failed",{error:t instanceof Error?t.message:String(t),ts:this.nowIso()}),this.log("[bot] run_task failed",{task_id:s,error:t instanceof Error?t.message:String(t)})}return}if("line_message"===s){const t=e,s=`line-${e.line_event_id||Date.now()}`;if(this.log("[bot] line message received",{bot_id:e.bot_id||this.botId(),line_user_id:e.line_user_id||"",text:e.text||""}),this.sendTaskEvent(s,"line_message_received",{line_user_id:e.line_user_id||"",text:e.text||"",ts:this.nowIso()}),this.hooks.onLineMessage)try{const i=await this.hooks.onLineMessage(t,{taskId:s}),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:e.line_user_id||"",line_reply_token:e.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(s,"line_message_ai_output",{line_user_id:e.line_user_id||"",answer:o,image_url:r||"",ts:this.nowIso()}))}catch(t){this.sendTaskEvent(s,"line_message_process_failed",{line_user_id:e.line_user_id||"",error:t instanceof Error?t.message:String(t),ts:this.nowIso()})}return}if("server_config_update"===s){const t=e;if(this.log("[bot] server_config_update received",{bot_id:e.bot_id||this.botId(),scopes:Array.isArray(e.scopes)?e.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}if("workflow_execute"===s){const t=e;if(this.log("[bot] workflow_execute received",{bot_id:e.bot_id||this.botId(),workflow_id:e.workflow_id||"",trigger_source:e.trigger_source||""}),this.hooks.onWorkflowExecute)try{await this.hooks.onWorkflowExecute(t)}catch(t){this.log("[bot] onWorkflowExecute failed",{error:t instanceof Error?t.message:String(t)})}return}"ping"===s&&this.sendJson({type:"pong",ts:this.nowIso()})}else this.log("[bot] hello ack",e)}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 e=this.buildWsUrl();this.log("[bot] connecting",{target:e}),this.setStatus("connecting"),this.ws=new t(e),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(e){this.shouldReconnect=!0,this.reconnectAttempts=0,this.session=e,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.8",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Weget bot client for ai.weget.jp",
@@ -33,19 +33,13 @@
33
33
  "start": "npm run build:ts && electron dist/src/main.js",
34
34
  "dev": "npm run build:ts && electron dist/src/main.js",
35
35
  "prepack": "npm run build:ts",
36
- "postinstall": "node scripts/prepare-playwright-browsers.cjs",
37
- "run:script": "node scripts/run-workflow.cjs",
38
- "backtest:loop": "node scripts/backtest-loop.cjs",
39
36
  "build:ts": "node scripts/build-ts.cjs",
40
37
  "check": "npm run build:ts && tsc --noEmit -p tsconfig.json",
41
- "test": "tsx --test test/**/*.test.js",
42
38
  "build": "npm run build:ts"
43
39
  },
44
40
  "dependencies": {
45
- "dotenv": "^17.2.3",
46
41
  "electron": "^31.7.7",
47
42
  "openai": "^6.22.0",
48
- "playwright": "^1.55.0",
49
43
  "ws": "^8.18.3"
50
44
  },
51
45
  "devDependencies": {