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