deckide 3.5.38 → 3.5.40

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/dist/server.js CHANGED
@@ -51,6 +51,14 @@ export async function createServer() {
51
51
  const decks = new Map();
52
52
  const terminals = new Map();
53
53
  const browserService = new AgentBrowserService();
54
+ // 共有ブラウザを起動時から常駐させ、BrowserPane を開いていなくても
55
+ // Codex/Claude の chrome MCP(--browserUrl 127.0.0.1:9222)が常に使えるようにする。
56
+ // 失敗してもサーバ起動は継続(Chrome 未導入の環境などを考慮)。AGENT_BROWSER_AUTOSTART=false で無効化可。
57
+ if (process.env.AGENT_BROWSER_AUTOSTART !== 'false') {
58
+ void browserService.start().catch((err) => {
59
+ console.error('[browser] 共有ブラウザの自動起動に失敗:', err instanceof Error ? err.message : err);
60
+ });
61
+ }
54
62
  loadPersistedState(db, workspaces, workspacePathIndex, decks);
55
63
  // Create Hono app
56
64
  const app = new Hono();
@@ -280,6 +280,13 @@ export class AgentBrowserService {
280
280
  launching = null;
281
281
  port = null;
282
282
  executablePath = null;
283
+ // 共有ブラウザを常駐させるための状態。keepAlive が true の間はクラッシュ時に
284
+ // 自動再起動し、9222 を常時稼働させる(BrowserPane を開いていなくても
285
+ // chrome MCP が使えるようにするため)。明示的な stop() で false にする。
286
+ keepAlive = false;
287
+ restartTimer = null;
288
+ restartAttempts = 0;
289
+ lastStartTime = 0;
283
290
  // Browser-level CDP connection used only to discover tabs (page targets) and
284
291
  // drive their lifecycle (create/close/activate). One per running Chrome.
285
292
  browserCdp = null;
@@ -333,6 +340,8 @@ export class AgentBrowserService {
333
340
  };
334
341
  }
335
342
  async start() {
343
+ // 一度起動を要求されたら常駐させる(クラッシュ時は自動再起動)。
344
+ this.keepAlive = true;
336
345
  if (this.isRunning()) {
337
346
  await this.ensureSession();
338
347
  if (this.clients.size > 0) {
@@ -350,6 +359,7 @@ export class AgentBrowserService {
350
359
  this.launching = this.launch();
351
360
  try {
352
361
  await this.launching;
362
+ this.lastStartTime = Date.now();
353
363
  }
354
364
  catch (error) {
355
365
  this.lastError = error instanceof Error ? error.message : String(error);
@@ -364,6 +374,12 @@ export class AgentBrowserService {
364
374
  }
365
375
  }
366
376
  async stop() {
377
+ // 明示的な停止 → 自動再起動を無効化。
378
+ this.keepAlive = false;
379
+ if (this.restartTimer) {
380
+ clearTimeout(this.restartTimer);
381
+ this.restartTimer = null;
382
+ }
367
383
  this.disconnectActiveCdp();
368
384
  this.closeBrowserCdp();
369
385
  const proc = this.chromeProcess;
@@ -438,6 +454,33 @@ export class AgentBrowserService {
438
454
  await this.refreshActivePageInfo();
439
455
  await this.broadcastStatus();
440
456
  }
457
+ // クラッシュ後に共有ブラウザを自動再起動する(9222 常時稼働の維持)。
458
+ // 直近の起動が短命なら連続再起動を抑制し、5 回連続で短命なら諦める。
459
+ scheduleAutoRestart() {
460
+ if (this.restartTimer || !this.keepAlive) {
461
+ return;
462
+ }
463
+ // 30 秒以上動いていたら安定とみなしてカウンタをリセット。
464
+ if (Date.now() - this.lastStartTime > 30_000) {
465
+ this.restartAttempts = 0;
466
+ }
467
+ if (this.restartAttempts >= 5) {
468
+ console.error('[agent-browser] 自動再起動を中止(短時間に連続クラッシュ)');
469
+ this.keepAlive = false;
470
+ return;
471
+ }
472
+ this.restartAttempts++;
473
+ const delay = Math.min(1000 * this.restartAttempts, 10_000);
474
+ this.restartTimer = setTimeout(() => {
475
+ this.restartTimer = null;
476
+ if (!this.keepAlive)
477
+ return;
478
+ this.start().catch((e) => {
479
+ console.error('[agent-browser] 自動再起動に失敗:', e instanceof Error ? e.message : e);
480
+ });
481
+ }, delay);
482
+ this.restartTimer.unref?.();
483
+ }
441
484
  // 履歴を delta だけ移動(-1=戻る, +1=進む)。端なら何もしない。
442
485
  async goHistory(delta) {
443
486
  await this.start();
@@ -541,6 +584,10 @@ export class AgentBrowserService {
541
584
  this.closeLogStream();
542
585
  this.lastError = code === 0 ? undefined : `Browser exited (${signal ?? code ?? 'unknown'})`;
543
586
  void this.broadcastStatus();
587
+ // 明示停止でなく落ちた(クラッシュ)なら、常駐維持のため自動再起動。
588
+ if (this.keepAlive) {
589
+ this.scheduleAutoRestart();
590
+ }
544
591
  }
545
592
  });
546
593
  child.once('error', (error) => {
@@ -1322,7 +1369,34 @@ function getWindowsVirtualKeyCode(key) {
1322
1369
  return 111 + Number(functionKey[1]);
1323
1370
  }
1324
1371
  if (key.length === 1) {
1325
- return key.toUpperCase().charCodeAt(0);
1372
+ // 英字・数字は ASCII コードがそのまま VK コードと一致する。
1373
+ if (/[a-zA-Z0-9]/.test(key)) {
1374
+ return key.toUpperCase().charCodeAt(0);
1375
+ }
1376
+ // 記号は charCodeAt だと VK コードと衝突する(例: '.' は ASCII 46 = VK_DELETE
1377
+ // で、Delete キー扱いになり文字が消える)。US 配列の OEM コードに明示マップする。
1378
+ // shift 併用の記号(! @ # ... > など)も対応する物理キーの VK にマップ。
1379
+ const symbolToVk = {
1380
+ ')': 48, '!': 49, '@': 50, '#': 51, '$': 52,
1381
+ '%': 53, '^': 54, '&': 55, '*': 56, '(': 57,
1382
+ ';': 186, ':': 186,
1383
+ '=': 187, '+': 187,
1384
+ ',': 188, '<': 188,
1385
+ '-': 189, '_': 189,
1386
+ '.': 190, '>': 190,
1387
+ '/': 191, '?': 191,
1388
+ '`': 192, '~': 192,
1389
+ '[': 219, '{': 219,
1390
+ '\\': 220, '|': 220,
1391
+ ']': 221, '}': 221,
1392
+ "'": 222, '"': 222,
1393
+ };
1394
+ if (key in symbolToVk) {
1395
+ return symbolToVk[key];
1396
+ }
1397
+ // 不明な1文字は 0 を返す。text は別途渡しているので文字入力自体は成立し、
1398
+ // 特殊キーとして誤解釈されることもない。
1399
+ return 0;
1326
1400
  }
1327
1401
  return 0;
1328
1402
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deckide",
3
- "version": "3.5.38",
3
+ "version": "3.5.40",
4
4
  "description": "Deck IDE - Browser-based IDE with terminal, file explorer, and git integration",
5
5
  "type": "module",
6
6
  "bin": {