vite-plugin-opencode-assistant 1.0.12 → 1.0.14

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.
@@ -40,7 +40,28 @@ var __async = (__this, __arguments, generator) => {
40
40
  import { createApp, ref, watch, onMounted, h, computed } from "vue";
41
41
  import { OpenCodeWidget } from "@vite-plugin-opencode-assistant/components";
42
42
  import "@vite-plugin-opencode-assistant/components/style.css";
43
- import { CONFIG_DATA_ATTR } from "@vite-plugin-opencode-assistant/shared";
43
+ import {
44
+ CONFIG_DATA_ATTR,
45
+ SERVICE_STARTUP_TASKS
46
+ } from "@vite-plugin-opencode-assistant/shared";
47
+ function parseHotkey(hotkeyStr) {
48
+ if (!hotkeyStr) return { ctrl: true, shift: false, alt: false, key: "k" };
49
+ const parts = hotkeyStr.toLowerCase().split("+");
50
+ const key = parts.pop();
51
+ return {
52
+ ctrl: parts.includes("ctrl") || parts.includes("cmd") || parts.includes("meta"),
53
+ shift: parts.includes("shift"),
54
+ alt: parts.includes("alt"),
55
+ key: key || "k"
56
+ };
57
+ }
58
+ function matchHotkey(e, hotkeyConfig) {
59
+ const ctrlMatch = hotkeyConfig.ctrl ? e.ctrlKey || e.metaKey : !(e.ctrlKey || e.metaKey);
60
+ const shiftMatch = hotkeyConfig.shift ? e.shiftKey : !e.shiftKey;
61
+ const altMatch = hotkeyConfig.alt ? e.altKey : !e.altKey;
62
+ const keyMatch = e.key.toLowerCase() === hotkeyConfig.key.toLowerCase();
63
+ return ctrlMatch && shiftMatch && altMatch && keyMatch;
64
+ }
44
65
  function utf8ToBase64(str) {
45
66
  const bytes = new TextEncoder().encode(str);
46
67
  const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("");
@@ -83,13 +104,40 @@ const App = {
83
104
  const sessions = ref([]);
84
105
  const selectedElements = ref([]);
85
106
  const widgetRef = ref(null);
107
+ const chromeMcpFailed = ref(false);
108
+ const currentTask = ref("");
109
+ const serviceStatus = ref("idle");
110
+ const loadingText = computed(() => {
111
+ if (iframeLoading.value) return "\u52A0\u8F7D\u4E2D...";
112
+ if (!currentTask.value) return "\u52A0\u8F7D\u4E2D...";
113
+ return SERVICE_STARTUP_TASKS[currentTask.value] || "\u52A0\u8F7D\u4E2D...";
114
+ });
115
+ const retryingWarmup = ref(false);
116
+ const retryWarmup = () => __async(null, null, function* () {
117
+ retryingWarmup.value = true;
118
+ try {
119
+ const res = yield fetch("/__opencode_warmup__", { method: "POST" });
120
+ const data = yield res.json();
121
+ if (data.success) {
122
+ chromeMcpFailed.value = false;
123
+ serviceStatus.value = "ready";
124
+ showNotification("Chrome DevTools MCP \u8FDE\u63A5\u6210\u529F");
125
+ } else {
126
+ showNotification(data.error || "\u91CD\u8BD5\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4 Chrome \u8FDC\u7A0B\u8C03\u8BD5\u5DF2\u5F00\u542F");
127
+ }
128
+ } catch (e) {
129
+ console.error("[OpenCode] Retry warmup failed:", e);
130
+ showNotification("\u91CD\u8BD5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5");
131
+ } finally {
132
+ retryingWarmup.value = false;
133
+ }
134
+ });
86
135
  const {
87
136
  position = "bottom-right",
88
137
  theme: initialTheme = "auto",
89
138
  open: autoOpen = false,
90
- sessionUrl: initialSessionUrl = "",
139
+ // sessionUrl 不再从配置读取,完全依赖 SSE 状态同步
91
140
  proxyUrl: configProxyUrl = "",
92
- lazy = false,
93
141
  hotkey = "ctrl+k",
94
142
  cwd = ""
95
143
  } = config;
@@ -97,18 +145,16 @@ const App = {
97
145
  proxyUrl = configProxyUrl;
98
146
  }
99
147
  const theme = ref(initialTheme);
100
- const isWaitingForSession = ref(!initialSessionUrl);
101
- const computedLoading = computed(() => loading.value || isWaitingForSession.value);
102
- let servicesStarted = !lazy;
148
+ const showSessionListSkeleton = computed(() => serviceStatus.value === "starting");
149
+ const iframeLoading = ref(false);
150
+ const computedLoading = computed(() => {
151
+ return serviceStatus.value === "starting" || iframeLoading.value;
152
+ });
103
153
  const extractSessionId = (url) => {
104
154
  if (!url) return null;
105
155
  const match = url.match(/\/session\/([^/?]+)/);
106
156
  return match ? match[1] : null;
107
157
  };
108
- currentSessionId.value = extractSessionId(initialSessionUrl);
109
- if (servicesStarted && initialSessionUrl) {
110
- iframeSrc.value = toProxyUrl(initialSessionUrl);
111
- }
112
158
  try {
113
159
  const stored = sessionStorage.getItem("__opencode_selected_elements__");
114
160
  if (stored) {
@@ -182,38 +228,96 @@ const App = {
182
228
  const selectSession = (session) => {
183
229
  if (currentSessionId.value === session.id) return;
184
230
  currentSessionId.value = session.id;
185
- loading.value = true;
231
+ iframeLoading.value = true;
186
232
  iframeSrc.value = `${proxyUrl}/${utf8ToBase64(cwd)}/session/${session.id}`;
187
- setTimeout(() => {
188
- loading.value = false;
189
- }, 500);
190
233
  };
191
234
  let sseConnection = null;
235
+ let sseRetryCount = 0;
236
+ const MAX_SSE_RETRIES = 10;
237
+ const SSE_RETRY_DELAY = 1e3;
192
238
  const setupSSE = () => {
193
- if (!servicesStarted || sseConnection) return;
194
- sseConnection = new EventSource("/__opencode_events__");
195
- sseConnection.onmessage = (event) => {
196
- try {
197
- const data = JSON.parse(event.data);
198
- if (data.type === "CONNECTED") {
199
- updateContext(true);
200
- } else if (data.type === "SESSION_READY") {
201
- if (data.sessionUrl && !iframeSrc.value) {
202
- iframeSrc.value = toProxyUrl(data.sessionUrl);
203
- currentSessionId.value = extractSessionId(data.sessionUrl);
239
+ if (sseConnection) return;
240
+ try {
241
+ sseConnection = new EventSource("/__opencode_events__");
242
+ sseConnection.onmessage = (event) => {
243
+ try {
244
+ const data = JSON.parse(event.data);
245
+ if (data.type === "CONNECTED") {
246
+ updateContext(true);
247
+ sseRetryCount = 0;
248
+ } else if (data.type === "STATUS_SYNC") {
249
+ if (data.isStarted !== void 0) {
250
+ if (data.isStarted && serviceStatus.value === "idle") {
251
+ serviceStatus.value = "starting";
252
+ }
253
+ }
254
+ if (data.task) {
255
+ currentTask.value = data.task;
256
+ if (data.task === "ready") {
257
+ serviceStatus.value = "ready";
258
+ chromeMcpFailed.value = false;
259
+ if (data.sessionUrl && !iframeSrc.value) {
260
+ iframeSrc.value = toProxyUrl(data.sessionUrl);
261
+ currentSessionId.value = extractSessionId(data.sessionUrl);
262
+ }
263
+ } else if (data.task === "chrome_mcp_failed") {
264
+ serviceStatus.value = "partial";
265
+ chromeMcpFailed.value = true;
266
+ } else if (data.task === "session_creation_failed" || data.task === "opencode_not_installed" || data.task === "web_start_timeout") {
267
+ serviceStatus.value = "failed";
268
+ } else if (serviceStatus.value === "idle") {
269
+ serviceStatus.value = "starting";
270
+ }
271
+ }
272
+ if (serviceStatus.value !== "idle") {
273
+ loadSessions();
274
+ }
275
+ } else if (data.type === "TASK_UPDATE") {
276
+ currentTask.value = data.task;
277
+ if (data.task === "ready") {
278
+ serviceStatus.value = "ready";
279
+ chromeMcpFailed.value = false;
280
+ if (data.sessionUrl && !iframeSrc.value) {
281
+ iframeSrc.value = toProxyUrl(data.sessionUrl);
282
+ currentSessionId.value = extractSessionId(data.sessionUrl);
283
+ }
284
+ } else if (data.task === "chrome_mcp_failed") {
285
+ serviceStatus.value = "partial";
286
+ chromeMcpFailed.value = true;
287
+ } else if (data.task === "session_creation_failed" || data.task === "opencode_not_installed" || data.task === "web_start_timeout") {
288
+ serviceStatus.value = "failed";
289
+ } else if (serviceStatus.value === "idle") {
290
+ serviceStatus.value = "starting";
291
+ }
292
+ } else if (data.type === "CLEAR_ELEMENTS") {
293
+ selectedElements.value = [];
204
294
  }
205
- isWaitingForSession.value = false;
206
- } else if (data.type === "CLEAR_ELEMENTS") {
207
- selectedElements.value = [];
295
+ } catch (e) {
296
+ }
297
+ };
298
+ sseConnection.onerror = () => {
299
+ sseConnection == null ? void 0 : sseConnection.close();
300
+ sseConnection = null;
301
+ if (serviceStatus.value === "ready" || serviceStatus.value === "partial") {
302
+ serviceStatus.value = "starting";
208
303
  }
209
- } catch (e) {
304
+ if (sseRetryCount < MAX_SSE_RETRIES) {
305
+ sseRetryCount++;
306
+ setTimeout(setupSSE, SSE_RETRY_DELAY * sseRetryCount);
307
+ }
308
+ };
309
+ } catch (e) {
310
+ sseConnection = null;
311
+ if (sseRetryCount < MAX_SSE_RETRIES) {
312
+ sseRetryCount++;
313
+ setTimeout(setupSSE, SSE_RETRY_DELAY * sseRetryCount);
210
314
  }
211
- };
315
+ }
212
316
  };
213
317
  let currentPageUrl = "";
214
318
  let currentPageTitle = "";
215
319
  const updateContext = (force = false) => {
216
- if (!servicesStarted) return;
320
+ if (serviceStatus.value === "idle") return;
217
321
  const newUrl = window.location.href;
218
322
  const newTitle = document.title;
219
323
  if (force || newUrl !== currentPageUrl || newTitle !== currentPageTitle) {
@@ -232,17 +336,12 @@ const App = {
232
336
  }
233
337
  };
234
338
  const ensureServicesStarted = () => __async(null, null, function* () {
235
- if (servicesStarted) return true;
339
+ if (serviceStatus.value !== "idle") return true;
236
340
  try {
237
341
  const res = yield fetch("/__opencode_start__");
238
342
  const data = yield res.json();
239
343
  if (data.success) {
240
- servicesStarted = true;
241
- if (data.sessionUrl) {
242
- iframeSrc.value = toProxyUrl(data.sessionUrl);
243
- currentSessionId.value = extractSessionId(data.sessionUrl);
244
- isWaitingForSession.value = false;
245
- }
344
+ serviceStatus.value = "starting";
246
345
  setupSSE();
247
346
  return true;
248
347
  }
@@ -250,38 +349,62 @@ const App = {
250
349
  }
251
350
  return false;
252
351
  });
352
+ const mainHotkey = parseHotkey(hotkey);
353
+ const selectHotkey = parseHotkey("ctrl+p");
253
354
  onMounted(() => {
254
- if (servicesStarted) {
355
+ if (serviceStatus.value !== "idle") {
255
356
  loadSessions();
256
357
  setupSSE();
257
358
  updateContext(true);
258
359
  }
259
- if (autoOpen && servicesStarted) {
360
+ if (autoOpen && serviceStatus.value !== "idle") {
260
361
  setTimeout(() => {
261
362
  open.value = true;
262
363
  }, 1e3);
263
364
  }
264
365
  const originalPushState = history.pushState;
265
366
  const originalReplaceState = history.replaceState;
367
+ const scheduleContextUpdate = () => {
368
+ requestAnimationFrame(() => updateContext());
369
+ };
266
370
  history.pushState = function(...args) {
267
371
  originalPushState.apply(this, args);
268
- setTimeout(updateContext, 0);
372
+ scheduleContextUpdate();
269
373
  };
270
374
  history.replaceState = function(...args) {
271
375
  originalReplaceState.apply(this, args);
272
- setTimeout(updateContext, 0);
376
+ scheduleContextUpdate();
273
377
  };
274
- window.addEventListener("popstate", () => setTimeout(updateContext, 0));
275
- window.addEventListener("hashchange", () => setTimeout(updateContext, 0));
378
+ window.addEventListener("popstate", scheduleContextUpdate);
379
+ window.addEventListener("hashchange", scheduleContextUpdate);
276
380
  const titleObserver = new MutationObserver(() => {
277
381
  if (document.title !== currentPageTitle) updateContext();
278
382
  });
279
383
  if (document.head) {
280
384
  titleObserver.observe(document.head, { childList: true, subtree: true });
281
385
  }
386
+ const handleKeydown = (e) => {
387
+ if (matchHotkey(e, mainHotkey)) {
388
+ e.preventDefault();
389
+ handleToggle(!open.value);
390
+ }
391
+ if (matchHotkey(e, selectHotkey)) {
392
+ e.preventDefault();
393
+ const win = window;
394
+ if (win.__VUE_INSPECTOR__) {
395
+ selectMode.value = !selectMode.value;
396
+ } else {
397
+ showNotification("Vue Inspector \u672A\u52A0\u8F7D\uFF0C\u65E0\u6CD5\u4F7F\u7528\u5143\u7D20\u9009\u62E9\u529F\u80FD");
398
+ }
399
+ }
400
+ };
401
+ document.addEventListener("keydown", handleKeydown);
402
+ return () => {
403
+ document.removeEventListener("keydown", handleKeydown);
404
+ };
282
405
  });
283
406
  const handleToggle = (val) => __async(null, null, function* () {
284
- if (lazy && !servicesStarted && val) {
407
+ if (serviceStatus.value === "idle" && val) {
285
408
  loading.value = true;
286
409
  const started = yield ensureServicesStarted();
287
410
  loading.value = false;
@@ -292,6 +415,9 @@ const App = {
292
415
  }
293
416
  open.value = val;
294
417
  if (val) updateContext();
418
+ if (val) {
419
+ iframeLoading.value = false;
420
+ }
295
421
  });
296
422
  const handleSelectNode = (element) => {
297
423
  const exists = selectedElements.value.some(
@@ -311,45 +437,116 @@ const App = {
311
437
  showNotification("\u5DF2\u6E05\u9664\u6240\u6709\u9009\u4E2D\u5143\u7D20");
312
438
  };
313
439
  return () => {
314
- return h(OpenCodeWidget, {
315
- ref: widgetRef,
316
- position,
317
- theme: theme.value,
318
- open: open.value,
319
- selectMode: selectMode.value,
320
- sessionListCollapsed: sessionListCollapsed.value,
321
- loading: computedLoading.value,
322
- loadingSessionList: loadingSessionList.value,
323
- iframeSrc: iframeSrc.value,
324
- currentSessionId: currentSessionId.value,
325
- sessions: sessions.value,
326
- sessionKey: "id",
327
- selectedElements: selectedElements.value,
328
- hotkeyLabel: hotkey,
329
- "onUpdate:open": handleToggle,
330
- "onUpdate:selectMode": (val) => {
331
- selectMode.value = val;
332
- },
333
- "onUpdate:sessionListCollapsed": (val) => {
334
- sessionListCollapsed.value = val;
335
- },
336
- "onUpdate:theme": (val) => {
337
- theme.value = val;
338
- },
339
- "onToggle-theme": (val) => {
340
- theme.value = val;
341
- },
342
- "onCreate-session": createSession,
343
- "onDelete-session": deleteSession,
344
- "onSelect-session": selectSession,
345
- "onClick-selected-node": handleSelectNode,
346
- "onClear-selected-nodes": handleClearSelected,
347
- "onRemove-selected-node": ({ index }) => {
348
- selectedElements.value.splice(index, 1);
349
- updateContext(true);
440
+ return h(
441
+ OpenCodeWidget,
442
+ {
443
+ ref: widgetRef,
444
+ position,
445
+ theme: theme.value,
446
+ open: open.value,
447
+ selectMode: selectMode.value,
448
+ sessionListCollapsed: sessionListCollapsed.value,
449
+ frameLoading: computedLoading.value,
450
+ loadingSessionList: loadingSessionList.value,
451
+ showSessionListSkeleton: showSessionListSkeleton.value,
452
+ showError: chromeMcpFailed.value,
453
+ iframeSrc: iframeSrc.value,
454
+ currentSessionId: currentSessionId.value,
455
+ sessions: sessions.value,
456
+ sessionKey: "id",
457
+ selectedElements: selectedElements.value,
458
+ hotkeyLabel: hotkey,
459
+ "onUpdate:open": handleToggle,
460
+ "onUpdate:selectMode": (val) => {
461
+ selectMode.value = val;
462
+ if (!val && !open.value) {
463
+ open.value = true;
464
+ }
465
+ },
466
+ "onUpdate:sessionListCollapsed": (val) => {
467
+ sessionListCollapsed.value = val;
468
+ },
469
+ "onUpdate:theme": (val) => {
470
+ theme.value = val;
471
+ },
472
+ "onToggle-theme": (val) => {
473
+ theme.value = val;
474
+ },
475
+ "onCreate-session": createSession,
476
+ "onDelete-session": deleteSession,
477
+ "onSelect-session": selectSession,
478
+ "onClick-selected-node": handleSelectNode,
479
+ "onClear-selected-nodes": handleClearSelected,
480
+ "onRemove-selected-node": ({ index }) => {
481
+ selectedElements.value.splice(index, 1);
482
+ updateContext(true);
483
+ },
484
+ "onEmpty-action": createSession,
485
+ "onFrame-loaded": () => {
486
+ iframeLoading.value = false;
487
+ }
350
488
  },
351
- "onEmpty-action": createSession
352
- });
489
+ {
490
+ loading: () => h("div", { class: "opencode-custom-loading" }, [
491
+ h("div", { class: "opencode-loading-spinner" }),
492
+ h("div", { class: "opencode-loading-text" }, loadingText.value)
493
+ ]),
494
+ error: () => chromeMcpFailed.value ? h("div", { class: "opencode-chrome-warmup-failed" }, [
495
+ h("div", { class: "opencode-chrome-warmup-failed-icon" }, [
496
+ h(
497
+ "svg",
498
+ {
499
+ viewBox: "0 0 24 24",
500
+ width: "48",
501
+ height: "48",
502
+ fill: "none",
503
+ stroke: "currentColor",
504
+ strokeWidth: "1.5"
505
+ },
506
+ [
507
+ h("path", {
508
+ strokeLinecap: "round",
509
+ strokeLinejoin: "round",
510
+ d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
511
+ })
512
+ ]
513
+ )
514
+ ]),
515
+ h(
516
+ "div",
517
+ { class: "opencode-chrome-warmup-failed-title" },
518
+ "Chrome DevTools MCP \u8FDE\u63A5\u5931\u8D25"
519
+ ),
520
+ h("div", { class: "opencode-chrome-warmup-failed-text" }, [
521
+ h("p", {}, "\u8BF7\u6309\u4EE5\u4E0B\u6B65\u9AA4\u5F00\u542F Chrome \u8FDC\u7A0B\u8C03\u8BD5\uFF1A"),
522
+ h("ol", { class: "opencode-chrome-warmup-steps" }, [
523
+ h("li", {}, [
524
+ "\u5728 Chrome \u5730\u5740\u680F\u8F93\u5165 ",
525
+ h(
526
+ "code",
527
+ { class: "opencode-chrome-warmup-code" },
528
+ "chrome://inspect/#remote-debugging"
529
+ )
530
+ ]),
531
+ h("li", {}, "\u52FE\u9009 'Allow remote debugging for this browser instance' \u9009\u9879"),
532
+ h("li", {}, "\u91CD\u65B0\u542F\u52A8\u6D4F\u89C8\u5668"),
533
+ h("li", {}, "\u5B8C\u6210\u540E\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u91CD\u8BD5")
534
+ ])
535
+ ]),
536
+ h("div", { class: "opencode-chrome-warmup-failed-actions" }, [
537
+ h(
538
+ "button",
539
+ {
540
+ class: "opencode-chrome-warmup-failed-btn primary",
541
+ disabled: retryingWarmup.value,
542
+ onClick: retryWarmup
543
+ },
544
+ retryingWarmup.value ? "\u8FDE\u63A5\u4E2D..." : "\u91CD\u8BD5\u8FDE\u63A5"
545
+ )
546
+ ])
547
+ ]) : null
548
+ }
549
+ );
353
550
  };
354
551
  }
355
552
  };
@@ -358,5 +555,148 @@ if (!window[INIT_MARKER]) {
358
555
  window[INIT_MARKER] = true;
359
556
  const container = document.createElement("div");
360
557
  document.body.appendChild(container);
361
- createApp(App).mount(container);
558
+ const app = createApp(App);
559
+ app.mount(container);
560
+ window.__OPENCODE_CLEANUP__ = () => {
561
+ app.unmount();
562
+ container.remove();
563
+ window[INIT_MARKER] = false;
564
+ };
565
+ }
566
+ const style = document.createElement("style");
567
+ style.textContent = `
568
+ .opencode-custom-loading {
569
+ display: flex;
570
+ flex-direction: column;
571
+ align-items: center;
572
+ justify-content: center;
573
+ padding: 20px;
574
+ }
575
+
576
+ .opencode-loading-spinner {
577
+ width: 32px;
578
+ height: 32px;
579
+ border: 3px solid var(--oc-border);
580
+ border-top-color: var(--oc-primary);
581
+ border-radius: 50%;
582
+ animation: opencode-spin 0.8s linear infinite;
583
+ }
584
+
585
+ @keyframes opencode-spin {
586
+ to { transform: rotate(360deg); }
587
+ }
588
+
589
+ .opencode-loading-text {
590
+ margin-top: 12px;
591
+ color: var(--oc-text-secondary);
592
+ font-size: 14px;
593
+ }
594
+
595
+ .opencode-chrome-warmup-failed {
596
+ position: absolute;
597
+ top: 0;
598
+ left: 0;
599
+ right: 0;
600
+ bottom: 0;
601
+ background: var(--oc-bg-secondary);
602
+ display: flex;
603
+ flex-direction: column;
604
+ align-items: center;
605
+ justify-content: center;
606
+ z-index: 15;
607
+ }
608
+
609
+ .opencode-chrome-warmup-failed-icon {
610
+ color: var(--oc-warning, #f59e0b);
611
+ margin-bottom: 16px;
612
+ }
613
+
614
+ .opencode-chrome-warmup-failed-title {
615
+ color: var(--oc-text-primary);
616
+ font-size: 18px;
617
+ font-weight: 600;
618
+ margin-bottom: 8px;
619
+ }
620
+
621
+ .opencode-chrome-warmup-failed-text {
622
+ color: var(--oc-text-secondary);
623
+ font-size: 14px;
624
+ margin-bottom: 24px;
625
+ text-align: left;
626
+ max-width: 400px;
627
+ line-height: 1.6;
628
+ text-align: center;
629
+ }
630
+
631
+ .opencode-chrome-warmup-failed-text p {
632
+ margin: 0 0 12px 0;
633
+ font-weight: 500;
634
+ color: var(--oc-text-primary);
635
+ }
636
+
637
+ .opencode-chrome-warmup-steps {
638
+ margin: 0;
639
+ padding-left: 20px;
640
+ }
641
+
642
+ .opencode-chrome-warmup-steps li {
643
+ margin-bottom: 8px;
644
+ color: var(--oc-text-secondary);
645
+ font-size: 13px;
646
+ line-height: 1.5;
647
+ }
648
+
649
+ .opencode-chrome-warmup-steps li:last-child {
650
+ margin-bottom: 0;
651
+ }
652
+
653
+ .opencode-chrome-warmup-code {
654
+ display: inline-block;
655
+ background: var(--oc-bg-tertiary);
656
+ color: var(--oc-primary);
657
+ padding: 2px 6px;
658
+ border-radius: 4px;
659
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
660
+ font-size: 12px;
661
+ font-weight: 500;
662
+ word-break: break-all;
663
+ margin: 0 2px;
664
+ }
665
+
666
+ .opencode-chrome-warmup-failed-actions {
667
+ display: flex;
668
+ gap: 12px;
669
+ }
670
+
671
+ .opencode-chrome-warmup-failed-btn {
672
+ padding: 10px 24px;
673
+ border-radius: 8px;
674
+ border: none;
675
+ font-size: 14px;
676
+ font-weight: 500;
677
+ cursor: pointer;
678
+ transition: all 0.2s;
679
+ }
680
+
681
+ .opencode-chrome-warmup-failed-btn.primary {
682
+ background: var(--oc-primary);
683
+ color: white;
684
+ box-shadow: var(--oc-shadow-primary);
685
+ }
686
+
687
+ .opencode-chrome-warmup-failed-btn.primary:hover:not(:disabled) {
688
+ background: var(--oc-primary-hover);
689
+ transform: translateY(-1px);
690
+ box-shadow: var(--oc-shadow-primary-hover);
691
+ }
692
+
693
+ .opencode-chrome-warmup-failed-btn.primary:active:not(:disabled) {
694
+ transform: translateY(0);
695
+ }
696
+
697
+ .opencode-chrome-warmup-failed-btn:disabled {
698
+ opacity: 0.6;
699
+ cursor: not-allowed;
362
700
  }
701
+ `;
702
+ document.head.appendChild(style);
package/es/core/api.d.ts CHANGED
@@ -11,4 +11,5 @@ export declare class OpenCodeAPI {
11
11
  getToolIds(retries?: number): Promise<string[]>;
12
12
  warmupChromeMcp(viteOrigin?: string): Promise<void>;
13
13
  getOrCreateSession(): Promise<string>;
14
+ retryWarmupChromeMcp(viteOrigin?: string): Promise<boolean>;
14
15
  }