vite-plugin-opencode-assistant 1.0.14 → 1.0.16

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 (81) hide show
  1. package/es/client/App.vue.d.ts +6 -0
  2. package/es/client/App.vue.js +300 -0
  3. package/es/client/components/ChromeWarmupError-sfc.css +1 -0
  4. package/es/client/components/ChromeWarmupError.vue.d.ts +11 -0
  5. package/es/client/components/ChromeWarmupError.vue.js +196 -0
  6. package/es/client/components/LoadingContent.vue.d.ts +5 -0
  7. package/es/client/components/LoadingContent.vue.js +39 -0
  8. package/es/client/composables/useContext.d.ts +8 -0
  9. package/es/client/composables/useContext.js +63 -0
  10. package/es/client/composables/useHotkey.d.ts +12 -0
  11. package/es/client/composables/useHotkey.js +41 -0
  12. package/es/client/composables/useSSE.d.ts +20 -0
  13. package/es/client/composables/useSSE.js +61 -0
  14. package/es/client/composables/useSelectedElements.d.ts +19 -0
  15. package/es/client/composables/useSelectedElements.js +43 -0
  16. package/es/client/composables/useServiceStatus.d.ts +13 -0
  17. package/es/client/composables/useServiceStatus.js +53 -0
  18. package/es/client/composables/useSessions.d.ts +26 -0
  19. package/es/client/composables/useSessions.js +127 -0
  20. package/es/client/composables/useTheme.d.ts +12 -0
  21. package/es/client/composables/useTheme.js +42 -0
  22. package/es/client/index.d.ts +1 -1
  23. package/es/client/index.js +5 -675
  24. package/es/client/styles.css +1 -0
  25. package/es/core/api.d.ts +18 -6
  26. package/es/core/api.js +324 -69
  27. package/es/core/proxy-server.d.ts +5 -1
  28. package/es/core/proxy-server.js +201 -70
  29. package/es/core/service.d.ts +9 -2
  30. package/es/core/service.js +80 -44
  31. package/es/endpoints/index.js +1 -1
  32. package/es/endpoints/sse.js +0 -3
  33. package/es/endpoints/start.d.ts +1 -2
  34. package/es/endpoints/start.js +2 -2
  35. package/es/endpoints/types.d.ts +5 -2
  36. package/es/endpoints/warmup.js +15 -3
  37. package/es/index.js +8 -12
  38. package/es/utils/system.d.ts +3 -1
  39. package/es/utils/system.js +39 -10
  40. package/lib/client/App.vue.d.ts +6 -0
  41. package/lib/client/App.vue.js +329 -0
  42. package/lib/client/components/ChromeWarmupError-sfc.css +1 -0
  43. package/lib/client/components/ChromeWarmupError.vue.d.ts +11 -0
  44. package/lib/client/components/ChromeWarmupError.vue.js +215 -0
  45. package/lib/client/components/LoadingContent.vue.d.ts +5 -0
  46. package/lib/client/components/LoadingContent.vue.js +58 -0
  47. package/lib/client/composables/useContext.d.ts +8 -0
  48. package/lib/client/composables/useContext.js +86 -0
  49. package/lib/client/composables/useHotkey.d.ts +12 -0
  50. package/lib/client/composables/useHotkey.js +66 -0
  51. package/lib/client/composables/useSSE.d.ts +20 -0
  52. package/lib/client/composables/useSSE.js +84 -0
  53. package/lib/client/composables/useSelectedElements.d.ts +19 -0
  54. package/lib/client/composables/useSelectedElements.js +66 -0
  55. package/lib/client/composables/useServiceStatus.d.ts +13 -0
  56. package/lib/client/composables/useServiceStatus.js +76 -0
  57. package/lib/client/composables/useSessions.d.ts +26 -0
  58. package/lib/client/composables/useSessions.js +148 -0
  59. package/lib/client/composables/useTheme.d.ts +12 -0
  60. package/lib/client/composables/useTheme.js +65 -0
  61. package/lib/client/index.d.ts +1 -1
  62. package/lib/client/index.js +22 -667
  63. package/lib/client/styles.css +1 -0
  64. package/lib/client.js +2988 -2973
  65. package/lib/core/api.d.ts +18 -6
  66. package/lib/core/api.js +321 -74
  67. package/lib/core/proxy-server.d.ts +5 -1
  68. package/lib/core/proxy-server.js +201 -70
  69. package/lib/core/service.d.ts +9 -2
  70. package/lib/core/service.js +76 -43
  71. package/lib/endpoints/index.js +1 -1
  72. package/lib/endpoints/sse.js +0 -3
  73. package/lib/endpoints/start.d.ts +1 -2
  74. package/lib/endpoints/start.js +2 -2
  75. package/lib/endpoints/types.d.ts +5 -2
  76. package/lib/endpoints/warmup.js +15 -3
  77. package/lib/index.js +8 -12
  78. package/lib/style.css +1 -1
  79. package/lib/utils/system.d.ts +3 -1
  80. package/lib/utils/system.js +39 -5
  81. package/package.json +4 -4
@@ -130,90 +130,221 @@ function generateBridgeScript(options) {
130
130
  }
131
131
  });
132
132
 
133
+ // === \u601D\u8003\u72B6\u6001\u76D1\u542C (\u5B8C\u5168\u590D\u523B OpenCode Web \u5B9E\u73B0) ===
134
+ // OpenCode Web \u6838\u5FC3\u903B\u8F91:
135
+ // working = !!pending() || sessionStatus().type !== "idle"
136
+ // pending = \u6700\u540E\u4E00\u6761\u672A\u5B8C\u6210\u7684 assistant \u6D88\u606F (time.completed \u4E0D\u662F\u6570\u5B57)
137
+ // sessionStatus = sync.data.session_status[sessionID]
138
+
139
+ let eventSource = null;
140
+ const sessionStatus = {};
141
+ const pendingMessages = {};
142
+
143
+ function getCurrentSessionID() {
144
+ const match = window.location.pathname.match(/\\/session\\/([^\\/]+)/);
145
+ return match ? match[1] : null;
146
+ }
147
+
148
+ function isPending(message) {
149
+ return message.role === 'assistant' && typeof message.time?.completed !== 'number';
150
+ }
151
+
152
+ function updateThinkingState(sessionID) {
153
+ const status = sessionStatus[sessionID];
154
+ const pending = pendingMessages[sessionID];
155
+
156
+ const isThinking = !!pending || (status && status.type !== 'idle');
157
+
158
+ if (window.parent !== window) {
159
+ window.parent.postMessage({
160
+ type: 'OPENCODE_THINKING_STATE',
161
+ thinking: isThinking,
162
+ sessionID: sessionID,
163
+ statusType: status?.type || 'idle',
164
+ hasPending: !!pending
165
+ }, '*');
166
+ }
167
+ }
168
+
169
+ function handleEvent(payload) {
170
+ const type = payload.type;
171
+ const props = payload.properties;
172
+
173
+ switch (type) {
174
+ case 'session.status': {
175
+ const sessionID = props.sessionID;
176
+ sessionStatus[sessionID] = props.status;
177
+ updateThinkingState(sessionID);
178
+ break;
179
+ }
180
+
181
+ case 'message.updated': {
182
+ const info = props.info;
183
+ if (!info || !info.sessionID) break;
184
+ const sessionID = info.sessionID;
185
+
186
+ if (info.role === 'assistant') {
187
+ if (isPending(info)) {
188
+ pendingMessages[sessionID] = info;
189
+ } else {
190
+ delete pendingMessages[sessionID];
191
+ }
192
+ updateThinkingState(sessionID);
193
+ }
194
+ break;
195
+ }
196
+
197
+ case 'message.part.delta': {
198
+ const sessionID = props.sessionID;
199
+ if (sessionID && !pendingMessages[sessionID]) {
200
+ pendingMessages[sessionID] = { role: 'assistant', time: {} };
201
+ updateThinkingState(sessionID);
202
+ }
203
+ break;
204
+ }
205
+ }
206
+ }
207
+
208
+ function setupThinkingListener() {
209
+ if (eventSource) {
210
+ eventSource.close();
211
+ }
212
+
213
+ eventSource = new EventSource('/global/event');
214
+
215
+ eventSource.onmessage = function(event) {
216
+ try {
217
+ const data = JSON.parse(event.data);
218
+ const payload = data.payload;
219
+ if (!payload) return;
220
+ handleEvent(payload);
221
+ } catch (e) {
222
+ // ignore parse errors
223
+ }
224
+ };
225
+
226
+ eventSource.onerror = function(err) {
227
+ console.warn('[OpenCode Bridge] SSE connection error, retrying in 3s...');
228
+ eventSource.close();
229
+ setTimeout(setupThinkingListener, 3000);
230
+ };
231
+
232
+ console.log('[OpenCode Bridge] SSE listener setup complete');
233
+ }
234
+
133
235
  // === \u5C31\u7EEA\u901A\u77E5 ===
134
- window.addEventListener("load", function() {
236
+ function init() {
135
237
  if (window.parent !== window) {
136
238
  window.parent.postMessage({ type: "OPENCODE_READY" }, "*");
137
239
  }
240
+ setupThinkingListener();
241
+ }
242
+
243
+ if (document.readyState === 'loading') {
244
+ document.addEventListener('DOMContentLoaded', init);
245
+ } else {
246
+ init();
247
+ }
248
+
249
+ window.addEventListener('beforeunload', function() {
250
+ if (eventSource) {
251
+ eventSource.close();
252
+ eventSource = null;
253
+ }
138
254
  });
139
255
  })();
140
256
  `;
141
257
  }
142
258
  function startProxyServer(targetUrl, port, options = {}) {
143
- const target = new URL(targetUrl);
144
- const bridgeScript = generateBridgeScript(options);
145
- const server = import_http.default.createServer((req, res) => {
146
- if (req.url === "/__opencode_bridge__.js") {
147
- const body = bridgeScript;
148
- res.writeHead(200, {
149
- "content-type": "application/javascript; charset=utf-8",
150
- "cache-control": "no-store",
151
- "content-length": Buffer.byteLength(body)
152
- });
153
- res.end(body);
154
- return;
155
- }
156
- const options2 = {
157
- hostname: target.hostname,
158
- port: target.port,
159
- path: req.url,
160
- method: req.method,
161
- headers: __spreadProps(__spreadValues({}, req.headers), {
162
- host: target.host,
163
- // Don't accept compressed responses so we can modify HTML
164
- "accept-encoding": "identity"
165
- })
166
- };
167
- const proxyReq = import_http.default.request(options2, (proxyRes) => {
168
- var _a;
169
- const rawContentType = proxyRes.headers["content-type"];
170
- const contentType = Array.isArray(rawContentType) ? (_a = rawContentType[0]) != null ? _a : "" : rawContentType != null ? rawContentType : "";
171
- if (contentType.includes("text/html")) {
172
- const chunks = [];
173
- proxyRes.on("data", (chunk) => {
174
- chunks.push(chunk);
175
- });
176
- proxyRes.on("end", () => {
177
- let body = Buffer.concat(chunks).toString("utf-8");
178
- if (body.match(/<\/head>/i)) {
179
- body = body.replace(
180
- /<\/head>/i,
181
- '<script src="/__opencode_bridge__.js"></script></head>'
182
- );
183
- } else if (body.match(/<\/body>/i)) {
184
- body = body.replace(
185
- /<\/body>/i,
186
- '<script src="/__opencode_bridge__.js"></script></body>'
187
- );
188
- } else {
189
- body += '<script src="/__opencode_bridge__.js"></script>';
190
- }
191
- const headers = {};
192
- for (const [key, value] of Object.entries(proxyRes.headers)) {
193
- if (value !== void 0 && key !== "content-encoding" && key !== "transfer-encoding" && key !== "content-length") {
194
- headers[key] = value;
195
- }
196
- }
197
- headers["content-length"] = Buffer.byteLength(body);
198
- res.writeHead(proxyRes.statusCode || 200, headers);
199
- res.end(body);
259
+ return new Promise((resolve, reject) => {
260
+ const target = new URL(targetUrl);
261
+ const bridgeScript = generateBridgeScript(options);
262
+ const server = import_http.default.createServer((req, res) => {
263
+ if (req.url === "/__opencode_bridge__.js") {
264
+ const body = bridgeScript;
265
+ res.writeHead(200, {
266
+ "content-type": "application/javascript; charset=utf-8",
267
+ "cache-control": "no-store",
268
+ "content-length": Buffer.byteLength(body)
200
269
  });
201
- } else {
202
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
203
- proxyRes.pipe(res);
270
+ res.end(body);
271
+ return;
204
272
  }
273
+ const requestOptions = {
274
+ hostname: target.hostname,
275
+ port: target.port,
276
+ path: req.url,
277
+ method: req.method,
278
+ headers: __spreadProps(__spreadValues({}, req.headers), {
279
+ host: target.host,
280
+ "accept-encoding": "identity"
281
+ }),
282
+ timeout: 0
283
+ };
284
+ const proxyReq = import_http.default.request(requestOptions, (proxyRes) => {
285
+ var _a;
286
+ const rawContentType = proxyRes.headers["content-type"];
287
+ const contentType = Array.isArray(rawContentType) ? (_a = rawContentType[0]) != null ? _a : "" : rawContentType != null ? rawContentType : "";
288
+ if (contentType.includes("text/html")) {
289
+ const chunks = [];
290
+ proxyRes.on("data", (chunk) => {
291
+ chunks.push(chunk);
292
+ });
293
+ proxyRes.on("end", () => {
294
+ let body = Buffer.concat(chunks).toString("utf-8");
295
+ if (body.match(/<\/head>/i)) {
296
+ body = body.replace(
297
+ /<\/head>/i,
298
+ '<script src="/__opencode_bridge__.js"></script></head>'
299
+ );
300
+ } else if (body.match(/<\/body>/i)) {
301
+ body = body.replace(
302
+ /<\/body>/i,
303
+ '<script src="/__opencode_bridge__.js"></script></body>'
304
+ );
305
+ } else {
306
+ body += '<script src="/__opencode_bridge__.js"></script>';
307
+ }
308
+ const headers = {};
309
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
310
+ if (value !== void 0 && key !== "content-encoding" && key !== "transfer-encoding" && key !== "content-length") {
311
+ headers[key] = value;
312
+ }
313
+ }
314
+ headers["content-length"] = Buffer.byteLength(body);
315
+ res.writeHead(proxyRes.statusCode || 200, headers);
316
+ res.end(body);
317
+ });
318
+ } else {
319
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
320
+ proxyRes.pipe(res);
321
+ }
322
+ });
323
+ proxyReq.on("error", (err) => {
324
+ log.error("Proxy error", { error: err.message, url: req.url });
325
+ res.writeHead(502);
326
+ res.end("Proxy error");
327
+ });
328
+ proxyReq.on("socket", (socket) => {
329
+ socket.setTimeout(0);
330
+ });
331
+ req.on("socket", (socket) => {
332
+ socket.setTimeout(0);
333
+ });
334
+ req.pipe(proxyReq);
205
335
  });
206
- proxyReq.on("error", (err) => {
207
- log.error("Proxy error", { error: err.message, url: req.url });
208
- res.writeHead(502);
209
- res.end("Proxy error");
336
+ server.on("error", (err) => {
337
+ reject(err);
338
+ });
339
+ server.timeout = 0;
340
+ server.keepAliveTimeout = 0;
341
+ server.listen(port, () => {
342
+ const address = server.address();
343
+ const actualPort = typeof address === "object" && address ? address.port : port;
344
+ log.info(`Proxy server started on port ${actualPort} -> ${targetUrl}`);
345
+ resolve({ server, actualPort });
210
346
  });
211
- req.pipe(proxyReq);
212
- });
213
- server.listen(port, () => {
214
- log.info(`Proxy server started on port ${port} -> ${targetUrl}`);
215
347
  });
216
- return server;
217
348
  }
218
349
  // Annotate the CommonJS export names for ESM import in node:
219
350
  0 && (module.exports = {
@@ -1,6 +1,7 @@
1
1
  import type { ResultPromise } from "execa";
2
2
  import type http from "http";
3
3
  import type { OpenCodeOptions, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
4
+ import { ChromeMcpWarmupErrorType } from "@vite-plugin-opencode-assistant/shared";
4
5
  import type { OpenCodeAPI } from "./api.js";
5
6
  export declare class OpenCodeService {
6
7
  private config;
@@ -13,16 +14,22 @@ export declare class OpenCodeService {
13
14
  actualProxyPort: number;
14
15
  isStarted: boolean;
15
16
  private startPromise;
16
- sessionUrl: string | null;
17
17
  private proxyServer;
18
18
  chromeMcpWarmupFailed: boolean;
19
+ chromeMcpWarmupErrorType: ChromeMcpWarmupErrorType | null;
20
+ chromeMcpWarmupErrorMessage: string | null;
19
21
  currentTask: {
20
22
  task: ServiceStartupTask;
21
23
  data?: Record<string, unknown>;
22
24
  } | null;
25
+ workspaceRoot: string | null;
23
26
  constructor(config: Required<OpenCodeOptions>, api: OpenCodeAPI, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
24
27
  private sendTaskUpdate;
25
28
  start(corsOrigins?: string[], contextApiUrl?: string, viteOrigin?: string): Promise<void>;
26
- retryWarmupChromeMcp(viteOrigin?: string): Promise<boolean>;
29
+ retryWarmupChromeMcp(viteOrigin?: string): Promise<{
30
+ success: boolean;
31
+ errorType?: string;
32
+ errorMessage?: string;
33
+ }>;
27
34
  stop(): Promise<void>;
28
35
  }
@@ -72,10 +72,12 @@ class OpenCodeService {
72
72
  __publicField(this, "actualProxyPort");
73
73
  __publicField(this, "isStarted", false);
74
74
  __publicField(this, "startPromise", null);
75
- __publicField(this, "sessionUrl", null);
76
75
  __publicField(this, "proxyServer", null);
77
76
  __publicField(this, "chromeMcpWarmupFailed", false);
77
+ __publicField(this, "chromeMcpWarmupErrorType", null);
78
+ __publicField(this, "chromeMcpWarmupErrorMessage", null);
78
79
  __publicField(this, "currentTask", null);
80
+ __publicField(this, "workspaceRoot", null);
79
81
  var _a;
80
82
  this.actualWebPort = config.webPort;
81
83
  this.actualProxyPort = (_a = config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT;
@@ -103,7 +105,7 @@ class OpenCodeService {
103
105
  return this.startPromise;
104
106
  }
105
107
  this.startPromise = (() => __async(this, null, function* () {
106
- var _a, _b, _c;
108
+ var _a, _b, _c, _d, _e;
107
109
  const timer = log.timer("startServices", {
108
110
  corsOrigins,
109
111
  contextApiUrl,
@@ -149,8 +151,10 @@ Please install OpenCode first:
149
151
  log.debug(`Using port ${this.actualWebPort}`);
150
152
  }
151
153
  timer.checkpoint("Port allocated");
154
+ this.workspaceRoot = (0, import_system.findGitRoot)(process.cwd());
155
+ log.info(`Using workspace root: ${this.workspaceRoot}`);
152
156
  this.sendTaskUpdate("preparing_runtime");
153
- const configDir = (0, import_opencode.prepareOpenCodeRuntime)(process.cwd());
157
+ const configDir = (0, import_opencode.prepareOpenCodeRuntime)(this.workspaceRoot);
154
158
  timer.checkpoint("Plugin setup complete");
155
159
  this.sendTaskUpdate("starting_web");
156
160
  log.debug("Starting OpenCode Web process...", {
@@ -162,7 +166,7 @@ Please install OpenCode first:
162
166
  port: this.actualWebPort,
163
167
  hostname: this.config.hostname,
164
168
  serverUrl: "",
165
- cwd: process.cwd(),
169
+ cwd: this.workspaceRoot,
166
170
  configDir,
167
171
  corsOrigins,
168
172
  contextApiUrl
@@ -172,7 +176,10 @@ Please install OpenCode first:
172
176
  log.info(`Waiting for OpenCode Web to become ready at ${webUrl}...`);
173
177
  this.sendTaskUpdate("waiting_web_ready");
174
178
  try {
175
- yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT);
179
+ yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT, this.webProcess);
180
+ if (((_a = this.webProcess) == null ? void 0 : _a.exitCode) !== null && ((_b = this.webProcess) == null ? void 0 : _b.exitCode) !== void 0) {
181
+ throw new Error(`OpenCode process exited with code ${this.webProcess.exitCode}`);
182
+ }
176
183
  log.info(`OpenCode Web started at ${webUrl}`);
177
184
  } catch (e) {
178
185
  log.error("OpenCode Web failed to start", { error: e });
@@ -182,60 +189,80 @@ Please install OpenCode first:
182
189
  return;
183
190
  }
184
191
  this.sendTaskUpdate("starting_proxy");
185
- this.actualProxyPort = yield (0, import_system.findAvailablePort)(
186
- (_a = this.config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT,
187
- this.config.hostname
188
- );
192
+ let proxyStartPort = (_c = this.config.proxyPort) != null ? _c : import_shared.DEFAULT_PROXY_PORT;
193
+ if (proxyStartPort === this.actualWebPort) {
194
+ proxyStartPort = this.actualWebPort + 1;
195
+ log.debug(`Proxy start port conflicts with web port, using ${proxyStartPort} instead`);
196
+ }
197
+ this.actualProxyPort = yield (0, import_system.findAvailablePort)(proxyStartPort, this.config.hostname);
189
198
  this.onProxyPortAllocated(this.actualProxyPort);
190
- if (this.actualProxyPort !== ((_b = this.config.proxyPort) != null ? _b : import_shared.DEFAULT_PROXY_PORT)) {
199
+ if (this.actualProxyPort !== ((_d = this.config.proxyPort) != null ? _d : import_shared.DEFAULT_PROXY_PORT)) {
191
200
  log.info(
192
- `Proxy port ${(_c = this.config.proxyPort) != null ? _c : import_shared.DEFAULT_PROXY_PORT} is in use, using ${this.actualProxyPort} instead`
201
+ `Proxy port ${(_e = this.config.proxyPort) != null ? _e : import_shared.DEFAULT_PROXY_PORT} is in use, using ${this.actualProxyPort} instead`
193
202
  );
194
203
  } else {
195
204
  log.debug(`Using proxy port ${this.actualProxyPort}`);
196
205
  }
197
- this.proxyServer = (0, import_proxy_server.startProxyServer)(webUrl, this.actualProxyPort, {
198
- theme: this.config.theme,
199
- language: this.config.language,
200
- settings: this.config.settings
201
- });
206
+ try {
207
+ const result = yield (0, import_proxy_server.startProxyServer)(webUrl, this.actualProxyPort, {
208
+ theme: this.config.theme,
209
+ language: this.config.language,
210
+ settings: this.config.settings
211
+ });
212
+ this.proxyServer = result.server;
213
+ if (result.actualPort !== this.actualProxyPort) {
214
+ log.info(
215
+ `Proxy port ${this.actualProxyPort} was taken, using ${result.actualPort} instead`
216
+ );
217
+ this.actualProxyPort = result.actualPort;
218
+ this.onProxyPortAllocated(this.actualProxyPort);
219
+ }
220
+ } catch (err) {
221
+ const nodeErr = err;
222
+ if (nodeErr.code === "EADDRINUSE") {
223
+ log.warn(`Proxy port ${this.actualProxyPort} became unavailable, trying next port...`);
224
+ const nextPort = yield (0, import_system.findAvailablePort)(this.actualProxyPort + 1, this.config.hostname);
225
+ const result = yield (0, import_proxy_server.startProxyServer)(webUrl, nextPort, {
226
+ theme: this.config.theme,
227
+ language: this.config.language,
228
+ settings: this.config.settings
229
+ });
230
+ this.proxyServer = result.server;
231
+ this.actualProxyPort = result.actualPort;
232
+ this.onProxyPortAllocated(this.actualProxyPort);
233
+ log.info(`Proxy server started on fallback port ${this.actualProxyPort}`);
234
+ } else {
235
+ throw err;
236
+ }
237
+ }
202
238
  timer.checkpoint("Proxy server started");
203
239
  this.sendTaskUpdate("warming_up_chrome");
204
240
  let warmupFailed = false;
205
241
  try {
206
- yield this.api.warmupChromeMcp(viteOrigin);
242
+ yield this.api.warmupChromeMcp(this.workspaceRoot, viteOrigin);
207
243
  timer.checkpoint("Chrome MCP warmup complete");
208
244
  } catch (e) {
209
245
  log.warn("Chrome MCP warmup failed", { error: e });
210
246
  this.chromeMcpWarmupFailed = true;
211
247
  warmupFailed = true;
248
+ if (e instanceof import_shared.ChromeMcpWarmupError) {
249
+ this.chromeMcpWarmupErrorType = e.type;
250
+ this.chromeMcpWarmupErrorMessage = e.message;
251
+ } else {
252
+ this.chromeMcpWarmupErrorType = import_shared.ChromeMcpWarmupErrorType.UNKNOWN;
253
+ this.chromeMcpWarmupErrorMessage = e instanceof Error ? e.message : String(e);
254
+ }
212
255
  }
213
256
  this.sendTaskUpdate("creating_session");
214
- let sessionFailed = false;
215
- try {
216
- this.sessionUrl = yield this.api.getOrCreateSession();
217
- timer.checkpoint("Session created");
218
- log.debug(`Session URL: ${this.sessionUrl}`);
219
- } catch (e) {
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) {
257
+ if (warmupFailed) {
258
+ this.sendTaskUpdate("chrome_mcp_failed", {
259
+ errorType: this.chromeMcpWarmupErrorType,
260
+ errorMessage: this.chromeMcpWarmupErrorMessage
261
+ });
234
262
  this.isStarted = true;
235
263
  } else {
236
- this.sessionUrl = null;
264
+ this.sendTaskUpdate("ready");
237
265
  }
238
- log.debug(`OpenCode services started successfully: ${this.sessionUrl || webUrl}`);
239
266
  timer.end("\u2713 Services started successfully");
240
267
  }))();
241
268
  return this.startPromise;
@@ -243,12 +270,18 @@ Please install OpenCode first:
243
270
  }
244
271
  retryWarmupChromeMcp(viteOrigin) {
245
272
  return __async(this, null, function* () {
246
- const success = yield this.api.retryWarmupChromeMcp(viteOrigin);
247
- if (success) {
273
+ const result = yield this.api.retryWarmupChromeMcp(this.workspaceRoot, viteOrigin);
274
+ if (result.success) {
248
275
  this.chromeMcpWarmupFailed = false;
249
- this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
276
+ this.sendTaskUpdate("ready");
277
+ return { success: true };
250
278
  }
251
- return success;
279
+ const error = result.error;
280
+ return {
281
+ success: false,
282
+ errorType: error == null ? void 0 : error.type,
283
+ errorMessage: (error == null ? void 0 : error.message) || "Unknown error"
284
+ };
252
285
  });
253
286
  }
254
287
  stop() {
@@ -31,7 +31,7 @@ __reExport(endpoints_exports, require("./types.js"), module.exports);
31
31
  function setupMiddlewares(server, ctx) {
32
32
  (0, import_widget.setupWidgetEndpoints)(server, ctx);
33
33
  (0, import_context.setupContextEndpoint)(server, ctx);
34
- (0, import_start.setupStartEndpoint)(server, ctx);
34
+ (0, import_start.setupStartEndpoint)(server);
35
35
  (0, import_sse.setupSseEndpoint)(server, ctx);
36
36
  (0, import_sessions.setupSessionsEndpoint)(server, ctx);
37
37
  (0, import_warmup.setupWarmupEndpoint)(server, ctx);
@@ -67,9 +67,6 @@ function setupSseEndpoint(server, ctx) {
67
67
  Object.assign(statusPayload, ctx.currentTask.data);
68
68
  }
69
69
  }
70
- if (ctx.sessionUrl) {
71
- statusPayload.sessionUrl = ctx.sessionUrl;
72
- }
73
70
  res.write(`data: ${JSON.stringify(statusPayload)}
74
71
 
75
72
  `);
@@ -1,3 +1,2 @@
1
1
  import type { ViteDevServer } from "vite";
2
- import type { EndpointContext } from "./types.js";
3
- export declare function setupStartEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
2
+ export declare function setupStartEndpoint(server: ViteDevServer): void;
@@ -42,13 +42,13 @@ __export(start_exports, {
42
42
  module.exports = __toCommonJS(start_exports);
43
43
  var import_shared = require("@vite-plugin-opencode-assistant/shared");
44
44
  var import_shared2 = require("@vite-plugin-opencode-assistant/shared");
45
- function setupStartEndpoint(server, ctx) {
45
+ function setupStartEndpoint(server) {
46
46
  server.middlewares.use(import_shared.START_API_PATH, (_req, res) => __async(null, null, function* () {
47
47
  const reqCtx = new import_shared2.RequestContext("GET", import_shared.START_API_PATH);
48
48
  res.setHeader("Content-Type", "application/json");
49
49
  res.setHeader("Access-Control-Allow-Origin", "*");
50
50
  res.writeHead(200);
51
- res.end(JSON.stringify({ success: true, sessionUrl: ctx.sessionUrl }));
51
+ res.end(JSON.stringify({ success: true }));
52
52
  reqCtx.end(200);
53
53
  }));
54
54
  }
@@ -1,7 +1,6 @@
1
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
- get sessionUrl(): string | null;
5
4
  get webUrl(): string | null;
6
5
  get sseClients(): Set<http.ServerResponse>;
7
6
  get pageContext(): PageContext;
@@ -16,5 +15,9 @@ export interface EndpointContext {
16
15
  deleteSession: (id: string) => Promise<void>;
17
16
  resolveWidgetPath: () => string;
18
17
  resolveWidgetStylePath: () => string;
19
- retryWarmupChromeMcp: () => Promise<boolean>;
18
+ retryWarmupChromeMcp: () => Promise<{
19
+ success: boolean;
20
+ errorType?: string;
21
+ errorMessage?: string;
22
+ }>;
20
23
  }
@@ -50,15 +50,27 @@ function setupWarmupEndpoint(server, ctx) {
50
50
  return;
51
51
  }
52
52
  try {
53
- const success = yield ctx.retryWarmupChromeMcp();
53
+ const result = yield ctx.retryWarmupChromeMcp();
54
54
  res.setHeader("Content-Type", "application/json");
55
55
  res.writeHead(200);
56
- res.end(JSON.stringify({ success }));
56
+ if (result.success) {
57
+ res.end(JSON.stringify({ success: true }));
58
+ } else {
59
+ res.end(JSON.stringify({
60
+ success: false,
61
+ errorType: result.errorType,
62
+ error: result.errorMessage
63
+ }));
64
+ }
57
65
  } catch (e) {
58
66
  log.error("Failed to retry warmup", { error: e });
59
67
  res.setHeader("Content-Type", "application/json");
60
68
  res.writeHead(500);
61
- res.end(JSON.stringify({ success: false, error: String(e) }));
69
+ res.end(JSON.stringify({
70
+ success: false,
71
+ errorType: "UNKNOWN",
72
+ error: String(e)
73
+ }));
62
74
  }
63
75
  }));
64
76
  }
package/lib/index.js CHANGED
@@ -92,7 +92,12 @@ function createOpenCodePlugin(options = {}) {
92
92
  let actualProxyPort = (_a = config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT;
93
93
  let pageContext = { url: "", title: "" };
94
94
  const sseClients = /* @__PURE__ */ new Set();
95
- const api = new import_api.OpenCodeAPI(config.hostname, () => actualWebPort, config.warmupChromeMcp);
95
+ const api = new import_api.OpenCodeAPI(
96
+ config.hostname,
97
+ () => actualWebPort,
98
+ () => actualProxyPort,
99
+ config.warmupChromeMcp
100
+ );
96
101
  const service = new import_service.OpenCodeService(
97
102
  config,
98
103
  api,
@@ -117,9 +122,6 @@ function createOpenCodePlugin(options = {}) {
117
122
  let viteOrigin = "";
118
123
  const getViteOrigin = () => viteOrigin;
119
124
  (0, import_endpoints.setupMiddlewares)(server, {
120
- get sessionUrl() {
121
- return service.sessionUrl;
122
- },
123
125
  get webUrl() {
124
126
  return actualWebPort ? `http://${config.hostname}:${actualWebPort}` : null;
125
127
  },
@@ -138,8 +140,8 @@ function createOpenCodePlugin(options = {}) {
138
140
  get currentTask() {
139
141
  return service.currentTask;
140
142
  },
141
- getSessions: () => api.getSessions(),
142
- createSession: () => api.createSession(),
143
+ getSessions: () => api.getSessions(service.workspaceRoot),
144
+ createSession: () => api.createSession(service.workspaceRoot),
143
145
  deleteSession: (id) => api.deleteSession(id),
144
146
  resolveWidgetPath: import_paths.resolveWidgetPath,
145
147
  resolveWidgetStylePath: import_paths.resolveWidgetStylePath,
@@ -195,15 +197,9 @@ function createOpenCodePlugin(options = {}) {
195
197
  transformIndexHtml(html) {
196
198
  const timer = log.timer("transformIndexHtml");
197
199
  const widget = (0, import_injector.injectWidget)({
198
- webUrl: `http://${config.hostname}:${actualWebPort}`,
199
- proxyUrl: `http://${config.hostname}:${actualProxyPort}`,
200
- serverUrl: `http://${config.hostname}:${actualWebPort}`,
201
200
  position: config.position,
202
201
  theme: config.theme,
203
202
  open: config.open,
204
- autoReload: config.autoReload,
205
- cwd: process.cwd(),
206
- // 不再注入 sessionUrl,客户端完全依赖 SSE 状态同步
207
203
  hotkey: config.hotkey
208
204
  });
209
205
  timer.end();