vite-plugin-opencode-assistant 1.0.13 → 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.
package/lib/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
  }
package/lib/core/api.js CHANGED
@@ -61,13 +61,36 @@ function sleep(ms) {
61
61
  function base64Encode(str) {
62
62
  return Buffer.from(str).toString("base64");
63
63
  }
64
+ function extractTextFromResponse(data) {
65
+ if (!data || typeof data !== "object") return null;
66
+ const obj = data;
67
+ if (obj.parts && Array.isArray(obj.parts)) {
68
+ const textParts = obj.parts.filter(
69
+ (p) => p && typeof p === "object" && p.type === "text"
70
+ ).map((p) => p.text).filter(Boolean);
71
+ if (textParts.length > 0) return textParts.join("");
72
+ }
73
+ if (obj.text && typeof obj.text === "string") {
74
+ return obj.text;
75
+ }
76
+ if (obj.content && typeof obj.content === "string") {
77
+ return obj.content;
78
+ }
79
+ if (obj.message && typeof obj.message === "string") {
80
+ return obj.message;
81
+ }
82
+ if (typeof data === "string") {
83
+ return data;
84
+ }
85
+ return null;
86
+ }
64
87
  class OpenCodeAPI {
65
88
  constructor(hostname, getPort, warmupChromeMcpConfig = false) {
66
89
  __publicField(this, "hostname", hostname);
67
90
  __publicField(this, "getPort", getPort);
68
91
  __publicField(this, "warmupChromeMcpConfig", warmupChromeMcpConfig);
69
92
  }
70
- createHttpRequest(options, body) {
93
+ createHttpRequest(options, body, timeout) {
71
94
  const timer = new import_shared.PerformanceTimer("HTTP Request", {
72
95
  operation: `${options.method || "GET"} ${options.path}`
73
96
  });
@@ -90,6 +113,13 @@ class OpenCodeAPI {
90
113
  timer.end("\u274C Request failed");
91
114
  reject(e);
92
115
  });
116
+ if (timeout) {
117
+ req.setTimeout(timeout, () => {
118
+ timer.end("\u274C Request timeout");
119
+ req.destroy();
120
+ reject(new Error(`Request timeout after ${timeout}ms`));
121
+ });
122
+ }
93
123
  if (body) req.write(body);
94
124
  req.end();
95
125
  });
@@ -243,24 +273,16 @@ class OpenCodeAPI {
243
273
  try {
244
274
  const warmupSession = yield this.createSession(import_shared.DEFAULT_RETRIES, "__chrome_mcp_warmup__");
245
275
  warmupSessionId = warmupSession.id;
246
- let chromeToolIds;
247
- try {
248
- const toolIds = yield this.getToolIds();
249
- chromeToolIds = toolIds.filter((toolId) => /chrome[-_]?devtools/i.test(toolId));
250
- log.debug("Resolved Chrome MCP tool ids", {
251
- chromeToolIds
252
- });
253
- } catch (e) {
254
- log.debug("Failed to resolve Chrome MCP tool ids", { error: e });
255
- }
256
276
  const prompt = [
257
277
  "Call the browser tool list_pages immediately to establish the Chrome DevTools MCP connection.",
258
278
  viteOrigin ? `If there are no pages, call new_page with ${viteOrigin}.` : "If there are no pages, call new_page with about:blank.",
259
279
  "Do not read or modify project files.",
260
280
  "Do not use any non-browser tools.",
261
- "After the tool call is complete, reply with exactly: ready"
281
+ "After the tool call is complete, reply with exactly: ready",
282
+ "If the tool call fails, reply with exactly: fail"
262
283
  ].join(" ");
263
- yield this.createHttpRequest(
284
+ const WARMUP_TIMEOUT = 3e4;
285
+ const data = yield this.createHttpRequest(
264
286
  {
265
287
  hostname: this.hostname,
266
288
  port: this.getPort(),
@@ -270,14 +292,19 @@ class OpenCodeAPI {
270
292
  },
271
293
  JSON.stringify({
272
294
  system: "You are warming up Chrome DevTools MCP during startup. You must use the available browser tools immediately before replying.",
273
- tools: (chromeToolIds == null ? void 0 : chromeToolIds.length) ? chromeToolIds : void 0,
274
295
  parts: [{ type: "text", text: prompt }]
275
- })
296
+ }),
297
+ WARMUP_TIMEOUT
276
298
  );
299
+ const responseText = extractTextFromResponse(data);
300
+ if (!(responseText == null ? void 0 : responseText.toLowerCase().includes("ready"))) {
301
+ throw new Error(`Chrome MCP warmup failed: ${responseText || "No response"}`);
302
+ }
277
303
  timer.end("Chrome MCP warmed up");
278
304
  } catch (e) {
279
305
  log.warn("Failed to warm up Chrome MCP", { error: e });
280
306
  timer.end("Chrome MCP warmup skipped");
307
+ throw e;
281
308
  } finally {
282
309
  if (warmupSessionId) {
283
310
  try {
@@ -314,6 +341,61 @@ class OpenCodeAPI {
314
341
  return url;
315
342
  });
316
343
  }
344
+ retryWarmupChromeMcp(viteOrigin) {
345
+ return __async(this, null, function* () {
346
+ const timer = log.timer("retryWarmupChromeMcp", { viteOrigin });
347
+ let warmupSessionId = null;
348
+ try {
349
+ const warmupSession = yield this.createSession(import_shared.DEFAULT_RETRIES, "__chrome_mcp_warmup__");
350
+ warmupSessionId = warmupSession.id;
351
+ const prompt = [
352
+ "Call the browser tool list_pages immediately to establish the Chrome DevTools MCP connection.",
353
+ viteOrigin ? `If there are no pages, call new_page with ${viteOrigin}.` : "If there are no pages, call new_page with about:blank.",
354
+ "Do not read or modify project files.",
355
+ "Do not use any non-browser tools.",
356
+ "After the tool call is complete, reply with exactly: ready",
357
+ "If the tool call fails, reply with exactly: fail"
358
+ ].join(" ");
359
+ const WARMUP_TIMEOUT = 6e4;
360
+ const data = yield this.createHttpRequest(
361
+ {
362
+ hostname: this.hostname,
363
+ port: this.getPort(),
364
+ path: `/session/${warmupSessionId}/message`,
365
+ method: "POST",
366
+ headers: { "Content-Type": "application/json" }
367
+ },
368
+ JSON.stringify({
369
+ system: "You are warming up Chrome DevTools MCP during startup. You must use the available browser tools immediately before replying.",
370
+ parts: [{ type: "text", text: prompt }]
371
+ }),
372
+ WARMUP_TIMEOUT
373
+ );
374
+ log.debug("Chrome MCP warmup response:", { data });
375
+ const responseText = extractTextFromResponse(data);
376
+ if (!(responseText == null ? void 0 : responseText.toLowerCase().includes("ready"))) {
377
+ throw new Error(`Chrome MCP warmup failed: ${responseText || "No response"}`);
378
+ }
379
+ timer.end("Chrome MCP warmed up successfully");
380
+ return true;
381
+ } catch (e) {
382
+ log.warn("Failed to retry warm up Chrome MCP", { error: e });
383
+ timer.end("Chrome MCP warmup retry failed");
384
+ return false;
385
+ } finally {
386
+ if (warmupSessionId) {
387
+ try {
388
+ yield this.deleteSession(warmupSessionId, 5);
389
+ } catch (e) {
390
+ log.warn("Failed to delete warmup session after retries", {
391
+ error: e,
392
+ warmupSessionId
393
+ });
394
+ }
395
+ }
396
+ }
397
+ });
398
+ }
317
399
  }
318
400
  // Annotate the CommonJS export names for ESM import in node:
319
401
  0 && (module.exports = {
@@ -1,6 +1,6 @@
1
1
  import type { ResultPromise } from "execa";
2
2
  import type http from "http";
3
- import type { OpenCodeOptions } from "@vite-plugin-opencode-assistant/shared";
3
+ import type { OpenCodeOptions, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
4
4
  import type { OpenCodeAPI } from "./api.js";
5
5
  export declare class OpenCodeService {
6
6
  private config;
@@ -15,7 +15,14 @@ export declare class OpenCodeService {
15
15
  private startPromise;
16
16
  sessionUrl: string | null;
17
17
  private proxyServer;
18
+ chromeMcpWarmupFailed: boolean;
19
+ currentTask: {
20
+ task: ServiceStartupTask;
21
+ data?: Record<string, unknown>;
22
+ } | null;
18
23
  constructor(config: Required<OpenCodeOptions>, api: OpenCodeAPI, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
24
+ private sendTaskUpdate;
19
25
  start(corsOrigins?: string[], contextApiUrl?: string, viteOrigin?: string): Promise<void>;
26
+ retryWarmupChromeMcp(viteOrigin?: string): Promise<boolean>;
20
27
  stop(): Promise<void>;
21
28
  }
@@ -1,8 +1,21 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
4
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
7
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
6
19
  var __export = (target, all) => {
7
20
  for (var name in all)
8
21
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -61,10 +74,24 @@ class OpenCodeService {
61
74
  __publicField(this, "startPromise", null);
62
75
  __publicField(this, "sessionUrl", null);
63
76
  __publicField(this, "proxyServer", null);
77
+ __publicField(this, "chromeMcpWarmupFailed", false);
78
+ __publicField(this, "currentTask", null);
64
79
  var _a;
65
80
  this.actualWebPort = config.webPort;
66
81
  this.actualProxyPort = (_a = config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT;
67
82
  }
83
+ sendTaskUpdate(task, data) {
84
+ this.currentTask = __spreadValues({ task }, data);
85
+ this.sseClients.forEach((client) => {
86
+ try {
87
+ client.write(`data: ${JSON.stringify(__spreadValues({ type: "TASK_UPDATE", task }, data))}
88
+
89
+ `);
90
+ } catch (e) {
91
+ log.debug("Failed to send TASK_UPDATE event", { error: e });
92
+ }
93
+ });
94
+ }
68
95
  start(corsOrigins, contextApiUrl, viteOrigin) {
69
96
  return __async(this, null, function* () {
70
97
  if (this.isStarted && this.webProcess) {
@@ -87,6 +114,7 @@ class OpenCodeService {
87
114
  if (orphanCount > 0) {
88
115
  log.debug(`Killed ${orphanCount} orphan OpenCode process(es)`);
89
116
  }
117
+ this.sendTaskUpdate("checking_opencode");
90
118
  if (!(yield (0, import_system.checkOpenCodeInstalled)())) {
91
119
  log.error(`OpenCode is not installed!
92
120
 
@@ -106,10 +134,13 @@ Please install OpenCode first:
106
134
  mise use -g opencode # Any OS
107
135
  nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
108
136
  `);
137
+ this.sendTaskUpdate("opencode_not_installed");
138
+ this.startPromise = null;
109
139
  timer.end("\u274C OpenCode not installed");
110
140
  return;
111
141
  }
112
142
  timer.checkpoint("OpenCode installation verified");
143
+ this.sendTaskUpdate("allocating_port");
113
144
  this.actualWebPort = yield (0, import_system.findAvailablePort)(this.config.webPort, this.config.hostname);
114
145
  this.onPortAllocated(this.actualWebPort);
115
146
  if (this.actualWebPort !== this.config.webPort) {
@@ -118,8 +149,10 @@ Please install OpenCode first:
118
149
  log.debug(`Using port ${this.actualWebPort}`);
119
150
  }
120
151
  timer.checkpoint("Port allocated");
152
+ this.sendTaskUpdate("preparing_runtime");
121
153
  const configDir = (0, import_opencode.prepareOpenCodeRuntime)(process.cwd());
122
154
  timer.checkpoint("Plugin setup complete");
155
+ this.sendTaskUpdate("starting_web");
123
156
  log.debug("Starting OpenCode Web process...", {
124
157
  port: this.actualWebPort,
125
158
  hostname: this.config.hostname,
@@ -137,8 +170,18 @@ Please install OpenCode first:
137
170
  timer.checkpoint("Web process started");
138
171
  const webUrl = `http://${this.config.hostname}:${this.actualWebPort}`;
139
172
  log.info(`Waiting for OpenCode Web to become ready at ${webUrl}...`);
140
- yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT);
141
- log.info(`OpenCode Web started at ${webUrl}`);
173
+ this.sendTaskUpdate("waiting_web_ready");
174
+ try {
175
+ yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT);
176
+ log.info(`OpenCode Web started at ${webUrl}`);
177
+ } catch (e) {
178
+ log.error("OpenCode Web failed to start", { error: e });
179
+ this.sendTaskUpdate("web_start_timeout");
180
+ this.startPromise = null;
181
+ timer.end("\u274C Web start timeout");
182
+ return;
183
+ }
184
+ this.sendTaskUpdate("starting_proxy");
142
185
  this.actualProxyPort = yield (0, import_system.findAvailablePort)(
143
186
  (_a = this.config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT,
144
187
  this.config.hostname
@@ -157,33 +200,57 @@ Please install OpenCode first:
157
200
  settings: this.config.settings
158
201
  });
159
202
  timer.checkpoint("Proxy server started");
160
- yield this.api.warmupChromeMcp(viteOrigin);
161
- timer.checkpoint("Chrome MCP warmup complete");
203
+ this.sendTaskUpdate("warming_up_chrome");
204
+ let warmupFailed = false;
205
+ try {
206
+ yield this.api.warmupChromeMcp(viteOrigin);
207
+ timer.checkpoint("Chrome MCP warmup complete");
208
+ } catch (e) {
209
+ log.warn("Chrome MCP warmup failed", { error: e });
210
+ this.chromeMcpWarmupFailed = true;
211
+ warmupFailed = true;
212
+ }
213
+ this.sendTaskUpdate("creating_session");
214
+ let sessionFailed = false;
162
215
  try {
163
216
  this.sessionUrl = yield this.api.getOrCreateSession();
164
217
  timer.checkpoint("Session created");
165
218
  log.debug(`Session URL: ${this.sessionUrl}`);
166
- this.sseClients.forEach((client) => {
167
- try {
168
- client.write(
169
- `data: ${JSON.stringify({ type: "SESSION_READY", sessionUrl: this.sessionUrl })}
170
-
171
- `
172
- );
173
- } catch (e) {
174
- log.debug("Failed to send SESSION_READY event", { error: e });
175
- }
176
- });
177
219
  } catch (e) {
178
220
  log.warn("Failed to get/create session", { error: e });
221
+ sessionFailed = true;
222
+ }
223
+ if (sessionFailed) {
224
+ this.sendTaskUpdate("session_creation_failed");
225
+ this.isStarted = false;
226
+ this.startPromise = null;
227
+ } else if (warmupFailed) {
228
+ this.sendTaskUpdate("chrome_mcp_failed", { sessionUrl: this.sessionUrl });
229
+ this.isStarted = true;
230
+ } else {
231
+ this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
232
+ }
233
+ if (!sessionFailed) {
234
+ this.isStarted = true;
235
+ } else {
236
+ this.sessionUrl = null;
179
237
  }
180
- this.isStarted = true;
181
238
  log.debug(`OpenCode services started successfully: ${this.sessionUrl || webUrl}`);
182
239
  timer.end("\u2713 Services started successfully");
183
240
  }))();
184
241
  return this.startPromise;
185
242
  });
186
243
  }
244
+ retryWarmupChromeMcp(viteOrigin) {
245
+ return __async(this, null, function* () {
246
+ const success = yield this.api.retryWarmupChromeMcp(viteOrigin);
247
+ if (success) {
248
+ this.chromeMcpWarmupFailed = false;
249
+ this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
250
+ }
251
+ return success;
252
+ });
253
+ }
187
254
  stop() {
188
255
  return __async(this, null, function* () {
189
256
  const timer = log.timer("stopServices");
@@ -26,6 +26,7 @@ var import_context = require("./context.js");
26
26
  var import_start = require("./start.js");
27
27
  var import_sse = require("./sse.js");
28
28
  var import_sessions = require("./sessions.js");
29
+ var import_warmup = require("./warmup.js");
29
30
  __reExport(endpoints_exports, require("./types.js"), module.exports);
30
31
  function setupMiddlewares(server, ctx) {
31
32
  (0, import_widget.setupWidgetEndpoints)(server, ctx);
@@ -33,6 +34,7 @@ function setupMiddlewares(server, ctx) {
33
34
  (0, import_start.setupStartEndpoint)(server, ctx);
34
35
  (0, import_sse.setupSseEndpoint)(server, ctx);
35
36
  (0, import_sessions.setupSessionsEndpoint)(server, ctx);
37
+ (0, import_warmup.setupWarmupEndpoint)(server, ctx);
36
38
  }
37
39
  // Annotate the CommonJS export names for ESM import in node:
38
40
  0 && (module.exports = {
@@ -57,13 +57,22 @@ function setupSseEndpoint(server, ctx) {
57
57
  res.write(`data: ${JSON.stringify({ type: "CONNECTED" })}
58
58
 
59
59
  `);
60
+ const statusPayload = { type: "STATUS_SYNC" };
61
+ if (ctx.isServiceStarted !== void 0) {
62
+ statusPayload.isStarted = ctx.isServiceStarted;
63
+ }
64
+ if (ctx.currentTask) {
65
+ statusPayload.task = ctx.currentTask.task;
66
+ if (ctx.currentTask.data) {
67
+ Object.assign(statusPayload, ctx.currentTask.data);
68
+ }
69
+ }
60
70
  if (ctx.sessionUrl) {
61
- res.write(
62
- `data: ${JSON.stringify({ type: "SESSION_READY", sessionUrl: ctx.sessionUrl })}
63
-
64
- `
65
- );
71
+ statusPayload.sessionUrl = ctx.sessionUrl;
66
72
  }
73
+ res.write(`data: ${JSON.stringify(statusPayload)}
74
+
75
+ `);
67
76
  req.on("close", () => {
68
77
  ctx.sseClients.delete(res);
69
78
  log.debug("SSE client disconnected", {
@@ -1,4 +1,4 @@
1
- import type { PageContext, SessionInfo } from "@vite-plugin-opencode-assistant/shared";
1
+ import type { PageContext, SessionInfo, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
2
2
  import type http from "http";
3
3
  export interface EndpointContext {
4
4
  get sessionUrl(): string | null;
@@ -6,9 +6,15 @@ export interface EndpointContext {
6
6
  get sseClients(): Set<http.ServerResponse>;
7
7
  get pageContext(): PageContext;
8
8
  set pageContext(ctx: PageContext);
9
+ get isServiceStarted(): boolean;
10
+ get currentTask(): {
11
+ task: ServiceStartupTask;
12
+ data?: Record<string, unknown>;
13
+ } | null;
9
14
  getSessions: () => Promise<SessionInfo[]>;
10
15
  createSession: () => Promise<SessionInfo>;
11
16
  deleteSession: (id: string) => Promise<void>;
12
17
  resolveWidgetPath: () => string;
13
18
  resolveWidgetStylePath: () => string;
19
+ retryWarmupChromeMcp: () => Promise<boolean>;
14
20
  }
@@ -0,0 +1,3 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types.js";
3
+ export declare function setupWarmupEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
@@ -0,0 +1,68 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var __async = (__this, __arguments, generator) => {
19
+ return new Promise((resolve, reject) => {
20
+ var fulfilled = (value) => {
21
+ try {
22
+ step(generator.next(value));
23
+ } catch (e) {
24
+ reject(e);
25
+ }
26
+ };
27
+ var rejected = (value) => {
28
+ try {
29
+ step(generator.throw(value));
30
+ } catch (e) {
31
+ reject(e);
32
+ }
33
+ };
34
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
35
+ step((generator = generator.apply(__this, __arguments)).next());
36
+ });
37
+ };
38
+ var warmup_exports = {};
39
+ __export(warmup_exports, {
40
+ setupWarmupEndpoint: () => setupWarmupEndpoint
41
+ });
42
+ module.exports = __toCommonJS(warmup_exports);
43
+ var import_shared = require("@vite-plugin-opencode-assistant/shared");
44
+ const log = (0, import_shared.createLogger)("Endpoints:Warmup");
45
+ function setupWarmupEndpoint(server, ctx) {
46
+ server.middlewares.use("/__opencode_warmup__", (req, res) => __async(null, null, function* () {
47
+ if (req.method !== "POST") {
48
+ res.writeHead(405);
49
+ res.end("Method not allowed");
50
+ return;
51
+ }
52
+ try {
53
+ const success = yield ctx.retryWarmupChromeMcp();
54
+ res.setHeader("Content-Type", "application/json");
55
+ res.writeHead(200);
56
+ res.end(JSON.stringify({ success }));
57
+ } catch (e) {
58
+ log.error("Failed to retry warmup", { error: e });
59
+ res.setHeader("Content-Type", "application/json");
60
+ res.writeHead(500);
61
+ res.end(JSON.stringify({ success: false, error: String(e) }));
62
+ }
63
+ }));
64
+ }
65
+ // Annotate the CommonJS export names for ESM import in node:
66
+ 0 && (module.exports = {
67
+ setupWarmupEndpoint
68
+ });
package/lib/index.js CHANGED
@@ -114,6 +114,8 @@ function createOpenCodePlugin(options = {}) {
114
114
  return __async(this, null, function* () {
115
115
  var _a2, _b2;
116
116
  const timer = log.timer("configureServer");
117
+ let viteOrigin = "";
118
+ const getViteOrigin = () => viteOrigin;
117
119
  (0, import_endpoints.setupMiddlewares)(server, {
118
120
  get sessionUrl() {
119
121
  return service.sessionUrl;
@@ -130,11 +132,18 @@ function createOpenCodePlugin(options = {}) {
130
132
  set pageContext(ctx) {
131
133
  pageContext = ctx;
132
134
  },
135
+ get isServiceStarted() {
136
+ return service.isStarted;
137
+ },
138
+ get currentTask() {
139
+ return service.currentTask;
140
+ },
133
141
  getSessions: () => api.getSessions(),
134
142
  createSession: () => api.createSession(),
135
143
  deleteSession: (id) => api.deleteSession(id),
136
144
  resolveWidgetPath: import_paths.resolveWidgetPath,
137
- resolveWidgetStylePath: import_paths.resolveWidgetStylePath
145
+ resolveWidgetStylePath: import_paths.resolveWidgetStylePath,
146
+ retryWarmupChromeMcp: () => service.retryWarmupChromeMcp(getViteOrigin())
138
147
  });
139
148
  (_a2 = server.httpServer) == null ? void 0 : _a2.on("listening", () => __async(null, null, function* () {
140
149
  var _a3;
@@ -155,7 +164,7 @@ function createOpenCodePlugin(options = {}) {
155
164
  vitePort = server.config.server.port || 5173;
156
165
  viteHost = typeof host === "string" && host !== "0.0.0.0" && host !== "::" && host !== "::1" ? host : "localhost";
157
166
  }
158
- const viteOrigin = `http://${viteHost}:${vitePort}`;
167
+ viteOrigin = `http://${viteHost}:${vitePort}`;
159
168
  const contextApiUrl = `http://${viteHost}:${vitePort}${import_shared.CONTEXT_API_PATH}`;
160
169
  log.debug("Vite server ready", {
161
170
  vitePort,
@@ -194,7 +203,7 @@ function createOpenCodePlugin(options = {}) {
194
203
  open: config.open,
195
204
  autoReload: config.autoReload,
196
205
  cwd: process.cwd(),
197
- sessionUrl: service.sessionUrl || void 0,
206
+ // 不再注入 sessionUrl,客户端完全依赖 SSE 状态同步
198
207
  hotkey: config.hotkey
199
208
  });
200
209
  timer.end();
package/lib/style.css CHANGED
@@ -1 +1 @@
1
- .opencode-widget{--oc-bg-main: #ffffff;--oc-bg-secondary: #f8f9fa;--oc-bg-tertiary: #f3f4f6;--oc-overlay-bg: rgba(255, 255, 255, .9);--oc-bg-inverse: #1e1e1e;--oc-text-primary: #282828;--oc-text-secondary: #4b5563;--oc-text-tertiary: #6b7280;--oc-text-placeholder: #9ca3af;--oc-text-inverse: #ffffff;--oc-border-primary: #e5e7eb;--oc-border-secondary: #d1d5db;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .1);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(0, 0, 0, .5);--oc-tooltip-bg: #1e1e1e;--oc-dialog-overlay: rgba(0, 0, 0, .5);--oc-skeleton-bg: #e5e7eb;--oc-skeleton-gradient: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .1);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .15);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .12);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .3);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .2);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .3);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .3);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .4);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .6);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .4);position:fixed;z-index:999999;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.opencode-widget.opencode-theme-dark{--oc-bg-main: #1a1a1a;--oc-bg-secondary: #1e1e1e;--oc-bg-tertiary: #282828;--oc-overlay-bg: rgba(26, 26, 26, .9);--oc-bg-inverse: #ffffff;--oc-text-primary: #f3f4f6;--oc-text-secondary: #d1d5db;--oc-text-tertiary: #9ca3af;--oc-text-placeholder: #6b7280;--oc-text-inverse: #282828;--oc-border-primary: #282828;--oc-border-secondary: #4b5563;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .15);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(26, 26, 26, .9);--oc-tooltip-bg: #282828;--oc-dialog-overlay: rgba(0, 0, 0, .7);--oc-skeleton-bg: #151515;--oc-skeleton-gradient: linear-gradient(90deg, #282828 25%, #4b5563 50%, #282828 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .3);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .4);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .6);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .3);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .4);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .4);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .5);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .7);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .5)}.opencode-widget.bottom-right{bottom:20px;right:20px}.opencode-widget.bottom-left{bottom:20px;left:20px}.opencode-widget.top-right{top:20px;right:20px}.opencode-widget.top-left{top:20px;left:20px}.opencode-chat{position:absolute;width:700px;height:86vh;background:var(--oc-bg-main);border-radius:16px;box-shadow:var(--oc-shadow-lg);overflow:hidden;opacity:0;visibility:hidden;transform:translateY(20px) scale(.95);transition:all .3s ease;display:flex;flex-direction:column}.opencode-chat-content{display:flex;flex:1;overflow:hidden}.opencode-widget.bottom-right .opencode-chat{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-chat{bottom:56px;left:0}.opencode-widget.top-right .opencode-chat{top:56px;right:0}.opencode-widget.top-left .opencode-chat{top:56px;left:0}.opencode-widget.bottom-right .opencode-selected-bubbles{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-selected-bubbles{bottom:56px;left:0}.opencode-widget.top-right .opencode-selected-bubbles{top:56px;bottom:auto;right:0}.opencode-widget.top-left .opencode-selected-bubbles{top:56px;bottom:auto;left:0}.opencode-chat.open{opacity:1;visibility:visible;transform:translateY(0) scale(1)}.opencode-notification{position:absolute;top:20px;left:50%;transform:translate(-50%);padding:12px 24px;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;border-radius:10px;font-size:14px;font-weight:500;box-shadow:0 4px 16px #3b82f666,0 0 0 2px #3b82f633;animation:slideDown .3s ease;z-index:10000000;display:flex;align-items:center;gap:10px}.opencode-notification:before{content:"💡";font-size:16px}.opencode-dialog-overlay{position:fixed;inset:0;background:var(--oc-dialog-overlay);display:flex;align-items:center;justify-content:center;z-index:9999999;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.opencode-dialog{background:var(--oc-bg-main);border-radius:12px;padding:24px;min-width:320px;max-width:400px;box-shadow:var(--oc-shadow-xl);animation:scaleIn .2s ease}@keyframes scaleIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.opencode-dialog-content{margin-bottom:20px}.opencode-dialog-message{font-size:15px;color:var(--oc-text-primary);line-height:1.5}.opencode-dialog-actions{display:flex;gap:12px;justify-content:flex-end}.opencode-dialog-btn{padding:10px 20px;border-radius:8px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s}.opencode-dialog-btn.cancel{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-dialog-btn.cancel:hover{background:var(--oc-text-primary);color:var(--oc-bg-main)}.opencode-dialog-btn.confirm{background:var(--oc-danger);color:#fff}.opencode-dialog-btn.confirm:hover{background:var(--oc-danger-hover)}@keyframes slideDown{0%{transform:translate(-50%) translateY(-100%);opacity:0}to{transform:translate(-50%) translateY(0);opacity:1}}.opencode-element-highlight{position:fixed;pointer-events:none;z-index:999998;display:none;transition:all .1s ease;border-radius:4px}#vue-inspector-container{display:none!important}.opencode-element-tooltip{position:fixed;background:var(--oc-tooltip-bg);color:#fff;padding:8px 12px;border-radius:6px;font-size:12px;z-index:9999998;display:none;box-shadow:var(--oc-shadow-md);max-width:300px;pointer-events:none}.opencode-tooltip-tag{font-weight:500;margin-bottom:4px;word-break:break-all}.opencode-tooltip-file{font-size:11px;color:var(--oc-text-placeholder);word-break:break-all}.opencode-element-highlight-temp{position:absolute;pointer-events:none;z-index:999998;border-radius:4px;animation:highlight-pulse 2s ease-out forwards}@keyframes highlight-pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(1.02)}to{opacity:0;transform:scale(1)}}@media (max-width:768px){.opencode-chat{width:calc(100vw - 40px);height:calc(100vh - 100px)}}.opencode-iframe-container{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;margin-top:-42px}.opencode-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:10;transition:opacity .3s ease}.opencode-loading-overlay.visible{display:flex}.opencode-loading-spinner{width:40px;height:40px;border:3px solid var(--oc-border-primary);border-top-color:var(--oc-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.opencode-loading-text{margin-top:12px;font-size:14px;color:var(--oc-text-placeholder)}.opencode-empty-state-overlay{position:absolute;inset:0;background:var(--oc-bg-secondary);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:5;transition:opacity .3s ease;margin-top:42px}.opencode-empty-state-overlay.visible{display:flex}.opencode-empty-state-icon{color:var(--oc-text-placeholder);margin-bottom:16px}.opencode-empty-state-text{color:var(--oc-text-primary);font-size:16px;font-weight:500;margin-bottom:24px}.opencode-empty-state-btn{padding:10px 24px;border-radius:8px;border:none;background:var(--oc-primary);color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;box-shadow:var(--oc-shadow-primary)}.opencode-empty-state-btn:hover{background:var(--oc-primary-hover);transform:translateY(-1px);box-shadow:var(--oc-shadow-primary-hover)}.opencode-empty-state-btn:active{transform:translateY(0)}.opencode-iframe{width:100%;height:100%;border:none}.opencode-chat-header{position:relative;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 12px;height:40px;background:var(--oc-bg-secondary);border-bottom:1px solid var(--oc-border-primary);z-index:5}.opencode-chat-header-left{display:flex;align-items:center;gap:4px}.opencode-chat-header-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);position:absolute;left:50%;transform:translate(-50%)}.opencode-chat-header-actions{display:flex;gap:4px}.opencode-header-btn{width:28px;height:28px;border-radius:6px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-header-btn:hover{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-header-btn.close:hover{background:var(--oc-danger);color:#fff}.opencode-header-btn.select-btn.active,.opencode-header-btn.session-toggle.active{background:var(--oc-primary);color:#fff}.opencode-select-mode-hint{position:fixed;top:20px;left:50%;transform:translate(-50%);padding:14px 20px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;border-radius:12px;font-size:15px;font-weight:500;box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d;z-index:9999999;display:none;align-items:center;gap:12px;border:2px solid rgba(255,255,255,.3)}.opencode-select-mode-hint.visible{display:flex;animation:slideDown .3s ease,pulseHint 2s ease-in-out infinite}.opencode-hint-shortcut{padding:4px 10px;background:#ffffff40;border-radius:6px;font-size:13px;font-weight:600;border:1px solid rgba(255,255,255,.4)}@keyframes pulseHint{0%,to{box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d}50%{box-shadow:0 6px 20px #ef444499,0 0 0 6px #ef444466}}.opencode-selected-bubbles{position:absolute;display:none;flex-direction:column;gap:6px;max-width:220px;max-height:300px;overflow-y:auto}.opencode-selected-bubbles.visible{display:flex}.opencode-selected-bubble{display:flex;flex-direction:column;gap:2px;padding:8px 24px 8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:8px;font-size:12px;box-shadow:var(--oc-shadow-sm);position:relative;cursor:pointer;transition:all .2s}.opencode-selected-bubble:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-bubble-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-remove{position:absolute;top:8px;right:6px;width:16px;height:16px;border-radius:50%;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s}.opencode-bubble-remove:hover{background:var(--oc-danger);color:#fff}.opencode-bubble-empty{padding:8px 12px;background:var(--oc-bg-main);border:1px dashed var(--oc-border-secondary);border-radius:8px;color:var(--oc-text-placeholder);font-size:12px;text-align:center}.opencode-right-toolbar{width:140px;background:var(--oc-bg-secondary);border-left:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease;overflow:hidden}.opencode-right-toolbar.collapsed{width:0;overflow:hidden}.opencode-right-toolbar.collapsed .opencode-selected-nodes-header,.opencode-right-toolbar.collapsed .opencode-selected-nodes,.opencode-right-toolbar.collapsed .opencode-clear-all-btn{display:none}.opencode-selected-nodes-header{padding:12px 8px 8px;border-bottom:1px solid var(--oc-border-primary)}.opencode-selected-nodes-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);margin-bottom:4px}.opencode-selected-nodes-desc{font-size:11px;color:var(--oc-text-placeholder);line-height:1.4}.opencode-selected-nodes{flex:1;display:flex;flex-direction:column;padding:8px;gap:6px;overflow-y:auto;overflow-x:hidden}.opencode-selected-nodes:empty:before{content:"暂无选中元素";color:var(--oc-text-placeholder);font-size:12px;text-align:center;padding:20px 10px}.opencode-selected-node{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:6px;font-size:12px;transition:all .2s}.opencode-selected-node:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-node-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.opencode-node-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-remove{width:18px;height:18px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;flex-shrink:0}.opencode-node-remove:hover{background:var(--oc-danger);color:#fff}.opencode-clear-all-btn{width:calc(100% - 16px);margin:8px;padding:8px 12px;border-radius:6px;border:none;background:var(--oc-danger);color:#fff;font-size:12px;font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:4px;transition:all .2s}.opencode-clear-all-btn:hover{background:var(--oc-danger-hover);transform:scale(1.02)}.opencode-session-list{width:240px;background:var(--oc-bg-secondary);border-right:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease}.opencode-session-list.collapsed{width:0;overflow:hidden}.opencode-session-list.collapsed .opencode-session-list-header,.opencode-session-list.collapsed .opencode-session-list-content{display:none}.opencode-session-list-header{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:flex;justify-content:space-between;align-items:center;font-weight:600;font-size:14px;color:var(--oc-text-primary)}.opencode-new-session-btn{width:28px;height:28px;border-radius:6px;border:none;background:var(--oc-primary);color:#fff;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-new-session-btn:hover{background:var(--oc-primary-hover);transform:scale(1.05)}.opencode-session-list-content{flex:1;overflow-y:auto;padding:8px;position:relative}.opencode-session-list-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:flex;align-items:center;justify-content:center;z-index:10;border-radius:8px}.opencode-loading-spinner.small{width:24px;height:24px;border-width:2px}.opencode-session-item{padding:12px;border-radius:8px;cursor:pointer;transition:transform .2s;margin-bottom:4px;color:var(--oc-text-primary)}.opencode-session-item:hover{background:var(--oc-bg-tertiary)}.opencode-session-item.active{background:var(--oc-primary);color:#fff;transition:none}.opencode-session-title{font-size:14px;font-weight:500;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-session-meta{font-size:12px;opacity:.6}.opencode-session-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.opencode-session-delete-btn{width:20px;height:20px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;opacity:0;flex-shrink:0}.opencode-session-item:hover .opencode-session-delete-btn{opacity:1}.opencode-session-delete-btn:hover{background:var(--oc-danger);color:#fff}.opencode-session-item.active .opencode-session-delete-btn{color:#ffffffb3}.opencode-session-item.active .opencode-session-delete-btn:hover{background:#fff3;color:#fff}.opencode-session-header-skeleton{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:none;justify-content:space-between;align-items:center}.opencode-session-header-skeleton.visible{display:flex}.opencode-skeleton-header-title{height:18px;width:80px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px}.opencode-skeleton-header-btn{width:28px;height:28px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:6px}.opencode-session-skeleton{flex:1;overflow-y:auto;padding:8px;display:none}.opencode-session-skeleton.visible{display:block}.opencode-skeleton-item{padding:12px;border-radius:8px;margin-bottom:4px;background:var(--oc-skeleton-bg)}.opencode-skeleton-title{height:16px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;margin-bottom:8px;width:70%}.opencode-skeleton-meta{height:12px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;width:50%}.opencode-session-empty{padding:32px 16px;text-align:center;color:var(--oc-text-placeholder);font-size:13px}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.opencode-button{width:44px;height:44px;border-radius:50%;background:var(--oc-trigger-bg);border:none;cursor:pointer;box-shadow:var(--oc-trigger-shadow);transition:all .3s ease;display:flex;align-items:center;justify-content:center;color:#fff;padding:0;position:relative}.opencode-button:before{content:"";position:absolute;inset:-8px;border-radius:50%}.opencode-button:hover{transform:scale(1.1);box-shadow:var(--oc-trigger-shadow-hover);background:var(--oc-trigger-bg-hover)}.opencode-button.active{background:var(--oc-trigger-bg-active);box-shadow:var(--oc-trigger-shadow-active)}.opencode-button.active svg{transform:rotate(180deg)}.opencode-button svg{transition:transform .3s ease}.opencode-button.loading{animation:pulse 1s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}
1
+ .opencode-widget{--oc-bg-main: #ffffff;--oc-bg-secondary: #f8f9fa;--oc-bg-tertiary: #f3f4f6;--oc-overlay-bg: rgba(255, 255, 255, .9);--oc-bg-inverse: #1e1e1e;--oc-text-primary: #282828;--oc-text-secondary: #4b5563;--oc-text-tertiary: #6b7280;--oc-text-placeholder: #9ca3af;--oc-text-inverse: #ffffff;--oc-border-primary: #e5e7eb;--oc-border-secondary: #d1d5db;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .1);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(0, 0, 0, .5);--oc-tooltip-bg: #1e1e1e;--oc-dialog-overlay: rgba(0, 0, 0, .5);--oc-skeleton-bg: #e5e7eb;--oc-skeleton-gradient: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .1);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .15);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .12);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .3);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .2);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .3);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .3);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .4);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .6);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .4);position:fixed;z-index:999999;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.opencode-widget.opencode-theme-dark{--oc-bg-main: #1a1a1a;--oc-bg-secondary: #1e1e1e;--oc-bg-tertiary: #282828;--oc-overlay-bg: rgba(26, 26, 26, .9);--oc-bg-inverse: #ffffff;--oc-text-primary: #f3f4f6;--oc-text-secondary: #d1d5db;--oc-text-tertiary: #9ca3af;--oc-text-placeholder: #6b7280;--oc-text-inverse: #282828;--oc-border-primary: #282828;--oc-border-secondary: #4b5563;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .15);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(26, 26, 26, .9);--oc-tooltip-bg: #282828;--oc-dialog-overlay: rgba(0, 0, 0, .7);--oc-skeleton-bg: #151515;--oc-skeleton-gradient: linear-gradient(90deg, #282828 25%, #4b5563 50%, #282828 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .3);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .4);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .6);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .3);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .4);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .4);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .5);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .7);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .5)}.opencode-widget.bottom-right{bottom:20px;right:20px}.opencode-widget.bottom-left{bottom:20px;left:20px}.opencode-widget.top-right{top:20px;right:20px}.opencode-widget.top-left{top:20px;left:20px}.opencode-chat{position:absolute;width:700px;height:86vh;background:var(--oc-bg-main);border-radius:16px;box-shadow:var(--oc-shadow-lg);overflow:hidden;opacity:0;visibility:hidden;transform:translateY(20px) scale(.95);transition:all .3s ease;display:flex;flex-direction:column}.opencode-chat-content{display:flex;flex:1;overflow:hidden}.opencode-widget.bottom-right .opencode-chat{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-chat{bottom:56px;left:0}.opencode-widget.top-right .opencode-chat{top:56px;right:0}.opencode-widget.top-left .opencode-chat{top:56px;left:0}.opencode-widget.bottom-right .opencode-selected-bubbles{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-selected-bubbles{bottom:56px;left:0}.opencode-widget.top-right .opencode-selected-bubbles{top:56px;bottom:auto;right:0}.opencode-widget.top-left .opencode-selected-bubbles{top:56px;bottom:auto;left:0}.opencode-chat.open{opacity:1;visibility:visible;transform:translateY(0) scale(1)}.opencode-notification{position:absolute;top:20px;left:50%;transform:translate(-50%);padding:12px 24px;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;border-radius:10px;font-size:14px;font-weight:500;box-shadow:0 4px 16px #3b82f666,0 0 0 2px #3b82f633;animation:slideDown .3s ease;z-index:10000000;display:flex;align-items:center;gap:10px}.opencode-notification:before{content:"💡";font-size:16px}.opencode-dialog-overlay{position:fixed;inset:0;background:var(--oc-dialog-overlay);display:flex;align-items:center;justify-content:center;z-index:9999999;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.opencode-dialog{background:var(--oc-bg-main);border-radius:12px;padding:24px;min-width:320px;max-width:400px;box-shadow:var(--oc-shadow-xl);animation:scaleIn .2s ease}@keyframes scaleIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.opencode-dialog-content{margin-bottom:20px}.opencode-dialog-message{font-size:15px;color:var(--oc-text-primary);line-height:1.5}.opencode-dialog-actions{display:flex;gap:12px;justify-content:flex-end}.opencode-dialog-btn{padding:10px 20px;border-radius:8px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s}.opencode-dialog-btn.cancel{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-dialog-btn.cancel:hover{background:var(--oc-text-primary);color:var(--oc-bg-main)}.opencode-dialog-btn.confirm{background:var(--oc-danger);color:#fff}.opencode-dialog-btn.confirm:hover{background:var(--oc-danger-hover)}@keyframes slideDown{0%{transform:translate(-50%) translateY(-100%);opacity:0}to{transform:translate(-50%) translateY(0);opacity:1}}.opencode-element-highlight{position:fixed;pointer-events:none;z-index:999998;display:none;transition:all .1s ease;border-radius:4px}#vue-inspector-container{display:none!important}.opencode-element-tooltip{position:fixed;background:var(--oc-tooltip-bg);color:#fff;padding:8px 12px;border-radius:6px;font-size:12px;z-index:9999998;display:none;box-shadow:var(--oc-shadow-md);max-width:300px;pointer-events:none}.opencode-tooltip-tag{font-weight:500;margin-bottom:4px;word-break:break-all}.opencode-tooltip-file{font-size:11px;color:var(--oc-text-placeholder);word-break:break-all}.opencode-element-highlight-temp{position:absolute;pointer-events:none;z-index:999998;border-radius:4px;animation:highlight-pulse 2s ease-out forwards}@keyframes highlight-pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(1.02)}to{opacity:0;transform:scale(1)}}@media (max-width:768px){.opencode-chat{width:calc(100vw - 40px);height:calc(100vh - 100px)}}.opencode-iframe-container{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;margin-top:-42px}.opencode-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:10;transition:opacity .3s ease}.opencode-loading-overlay.visible{display:flex}.opencode-loading-spinner{width:40px;height:40px;border:3px solid var(--oc-border-primary);border-top-color:var(--oc-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.opencode-loading-text{margin-top:12px;font-size:14px;color:var(--oc-text-placeholder)}.opencode-error-overlay{position:absolute;inset:0;z-index:15;margin-top:42px;display:none}.opencode-error-overlay.visible{display:flex}.opencode-empty-state-overlay{position:absolute;inset:0;background:var(--oc-bg-secondary);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:5;transition:opacity .3s ease;margin-top:42px}.opencode-empty-state-overlay.visible{display:flex}.opencode-empty-state-icon{color:var(--oc-text-placeholder);margin-bottom:16px}.opencode-empty-state-text{color:var(--oc-text-primary);font-size:16px;font-weight:500;margin-bottom:24px}.opencode-empty-state-btn{padding:10px 24px;border-radius:8px;border:none;background:var(--oc-primary);color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;box-shadow:var(--oc-shadow-primary)}.opencode-empty-state-btn:hover{background:var(--oc-primary-hover);transform:translateY(-1px);box-shadow:var(--oc-shadow-primary-hover)}.opencode-empty-state-btn:active{transform:translateY(0)}.opencode-iframe{width:100%;height:100%;border:none}.opencode-chat-header{position:relative;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 12px;height:40px;background:var(--oc-bg-secondary);border-bottom:1px solid var(--oc-border-primary);z-index:5}.opencode-chat-header-left{display:flex;align-items:center;gap:4px}.opencode-chat-header-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);position:absolute;left:50%;transform:translate(-50%)}.opencode-chat-header-actions{display:flex;gap:4px}.opencode-header-btn{width:28px;height:28px;border-radius:6px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-header-btn:hover{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-header-btn.close:hover{background:var(--oc-danger);color:#fff}.opencode-header-btn.select-btn.active,.opencode-header-btn.session-toggle.active{background:var(--oc-primary);color:#fff}.opencode-select-mode-hint{position:fixed;top:20px;left:50%;transform:translate(-50%);padding:10px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;border-radius:12px;font-size:14px;font-weight:500;box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d;z-index:9999999;display:none;align-items:center;gap:12px;border:1px solid rgba(255,255,255,.3)}.opencode-select-mode-hint.visible{display:flex;animation:slideDown .3s ease,pulseHint 2s ease-in-out infinite}.opencode-hint-shortcut{padding:4px 10px;background:#ffffff40;border-radius:6px;font-size:13px;font-weight:600;border:1px solid rgba(255,255,255,.4)}@keyframes pulseHint{0%,to{box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d}50%{box-shadow:0 6px 20px #ef444499,0 0 0 6px #ef444466}}.opencode-selected-bubbles{position:absolute;display:none;flex-direction:column;gap:6px;max-width:220px;max-height:300px;overflow-y:auto}.opencode-selected-bubbles.visible{display:flex}.opencode-selected-bubble{display:flex;flex-direction:column;gap:2px;padding:8px 24px 8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:8px;font-size:12px;box-shadow:var(--oc-shadow-sm);position:relative;cursor:pointer;transition:all .2s}.opencode-selected-bubble:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-bubble-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-remove{position:absolute;top:8px;right:6px;width:16px;height:16px;border-radius:50%;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s}.opencode-bubble-remove:hover{background:var(--oc-danger);color:#fff}.opencode-bubble-empty{padding:8px 12px;background:var(--oc-bg-main);border:1px dashed var(--oc-border-secondary);border-radius:8px;color:var(--oc-text-placeholder);font-size:12px;text-align:center}.opencode-right-toolbar{width:140px;background:var(--oc-bg-secondary);border-left:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease;overflow:hidden}.opencode-right-toolbar.collapsed{width:0;overflow:hidden}.opencode-right-toolbar.collapsed .opencode-selected-nodes-header,.opencode-right-toolbar.collapsed .opencode-selected-nodes,.opencode-right-toolbar.collapsed .opencode-clear-all-btn{display:none}.opencode-selected-nodes-header{padding:12px 8px 8px;border-bottom:1px solid var(--oc-border-primary)}.opencode-selected-nodes-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);margin-bottom:4px}.opencode-selected-nodes-desc{font-size:11px;color:var(--oc-text-placeholder);line-height:1.4}.opencode-selected-nodes{flex:1;display:flex;flex-direction:column;padding:8px;gap:6px;overflow-y:auto;overflow-x:hidden}.opencode-selected-nodes:empty:before{content:"暂无选中元素";color:var(--oc-text-placeholder);font-size:12px;text-align:center;padding:20px 10px}.opencode-selected-node{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:6px;font-size:12px;transition:all .2s}.opencode-selected-node:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-node-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.opencode-node-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-remove{width:18px;height:18px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;flex-shrink:0}.opencode-node-remove:hover{background:var(--oc-danger);color:#fff}.opencode-clear-all-btn{width:calc(100% - 16px);margin:8px;padding:8px 12px;border-radius:6px;border:none;background:var(--oc-danger);color:#fff;font-size:12px;font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:4px;transition:all .2s}.opencode-clear-all-btn:hover{background:var(--oc-danger-hover);transform:scale(1.02)}.opencode-session-list{width:240px;background:var(--oc-bg-secondary);border-right:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease}.opencode-session-list.collapsed{width:0;overflow:hidden}.opencode-session-list.collapsed .opencode-session-list-header,.opencode-session-list.collapsed .opencode-session-list-content{display:none}.opencode-session-list-header{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:flex;justify-content:space-between;align-items:center;font-weight:600;font-size:14px;color:var(--oc-text-primary)}.opencode-new-session-btn{width:28px;height:28px;border-radius:6px;border:none;background:var(--oc-primary);color:#fff;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-new-session-btn:hover{background:var(--oc-primary-hover);transform:scale(1.05)}.opencode-session-list-content{flex:1;overflow-y:auto;padding:8px;position:relative}.opencode-session-list-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:flex;align-items:center;justify-content:center;z-index:10;border-radius:8px}.opencode-loading-spinner.small{width:24px;height:24px;border-width:2px}.opencode-session-item{padding:12px;border-radius:8px;cursor:pointer;transition:transform .2s;margin-bottom:4px;color:var(--oc-text-primary)}.opencode-session-item:hover{background:var(--oc-bg-tertiary)}.opencode-session-item.active{background:var(--oc-primary);color:#fff;transition:none}.opencode-session-title{font-size:14px;font-weight:500;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-session-meta{font-size:12px;opacity:.6}.opencode-session-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.opencode-session-delete-btn{width:20px;height:20px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;opacity:0;flex-shrink:0}.opencode-session-item:hover .opencode-session-delete-btn{opacity:1}.opencode-session-delete-btn:hover{background:var(--oc-danger);color:#fff}.opencode-session-item.active .opencode-session-delete-btn{color:#ffffffb3}.opencode-session-item.active .opencode-session-delete-btn:hover{background:#fff3;color:#fff}.opencode-session-header-skeleton{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:none;justify-content:space-between;align-items:center}.opencode-session-header-skeleton.visible{display:flex}.opencode-skeleton-header-title{height:18px;width:80px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px}.opencode-skeleton-header-btn{width:28px;height:28px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:6px}.opencode-session-skeleton{flex:1;overflow-y:auto;padding:8px;display:none}.opencode-session-skeleton.visible{display:block}.opencode-skeleton-item{padding:12px;border-radius:8px;margin-bottom:4px;background:var(--oc-skeleton-bg)}.opencode-skeleton-title{height:16px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;margin-bottom:8px;width:70%}.opencode-skeleton-meta{height:12px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;width:50%}.opencode-session-empty{padding:32px 16px;text-align:center;color:var(--oc-text-placeholder);font-size:13px}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.opencode-button{width:44px;height:44px;border-radius:50%;background:var(--oc-trigger-bg);border:none;cursor:pointer;box-shadow:var(--oc-trigger-shadow);transition:all .3s ease;display:flex;align-items:center;justify-content:center;color:#fff;padding:0;position:relative}.opencode-button:before{content:"";position:absolute;inset:-8px;border-radius:50%}.opencode-button:hover{transform:scale(1.1);box-shadow:var(--oc-trigger-shadow-hover);background:var(--oc-trigger-bg-hover)}.opencode-button.active{background:var(--oc-trigger-bg-active);box-shadow:var(--oc-trigger-shadow-active)}.opencode-button.active svg{transform:rotate(180deg)}.opencode-button svg{transition:transform .3s ease}.opencode-button.loading{animation:pulse 1s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}