vite-plugin-opencode-assistant 1.0.15 → 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 (79) 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.js +127 -2
  28. package/es/core/service.d.ts +9 -2
  29. package/es/core/service.js +35 -31
  30. package/es/endpoints/index.js +1 -1
  31. package/es/endpoints/sse.js +0 -3
  32. package/es/endpoints/start.d.ts +1 -2
  33. package/es/endpoints/start.js +2 -2
  34. package/es/endpoints/types.d.ts +5 -2
  35. package/es/endpoints/warmup.js +15 -3
  36. package/es/index.js +8 -12
  37. package/es/utils/system.d.ts +1 -0
  38. package/es/utils/system.js +28 -0
  39. package/lib/client/App.vue.d.ts +6 -0
  40. package/lib/client/App.vue.js +329 -0
  41. package/lib/client/components/ChromeWarmupError-sfc.css +1 -0
  42. package/lib/client/components/ChromeWarmupError.vue.d.ts +11 -0
  43. package/lib/client/components/ChromeWarmupError.vue.js +215 -0
  44. package/lib/client/components/LoadingContent.vue.d.ts +5 -0
  45. package/lib/client/components/LoadingContent.vue.js +58 -0
  46. package/lib/client/composables/useContext.d.ts +8 -0
  47. package/lib/client/composables/useContext.js +86 -0
  48. package/lib/client/composables/useHotkey.d.ts +12 -0
  49. package/lib/client/composables/useHotkey.js +66 -0
  50. package/lib/client/composables/useSSE.d.ts +20 -0
  51. package/lib/client/composables/useSSE.js +84 -0
  52. package/lib/client/composables/useSelectedElements.d.ts +19 -0
  53. package/lib/client/composables/useSelectedElements.js +66 -0
  54. package/lib/client/composables/useServiceStatus.d.ts +13 -0
  55. package/lib/client/composables/useServiceStatus.js +76 -0
  56. package/lib/client/composables/useSessions.d.ts +26 -0
  57. package/lib/client/composables/useSessions.js +148 -0
  58. package/lib/client/composables/useTheme.d.ts +12 -0
  59. package/lib/client/composables/useTheme.js +65 -0
  60. package/lib/client/index.d.ts +1 -1
  61. package/lib/client/index.js +22 -667
  62. package/lib/client/styles.css +1 -0
  63. package/lib/client.js +2988 -2973
  64. package/lib/core/api.d.ts +18 -6
  65. package/lib/core/api.js +321 -74
  66. package/lib/core/proxy-server.js +127 -2
  67. package/lib/core/service.d.ts +9 -2
  68. package/lib/core/service.js +31 -30
  69. package/lib/endpoints/index.js +1 -1
  70. package/lib/endpoints/sse.js +0 -3
  71. package/lib/endpoints/start.d.ts +1 -2
  72. package/lib/endpoints/start.js +2 -2
  73. package/lib/endpoints/types.d.ts +5 -2
  74. package/lib/endpoints/warmup.js +15 -3
  75. package/lib/index.js +8 -12
  76. package/lib/style.css +1 -1
  77. package/lib/utils/system.d.ts +1 -0
  78. package/lib/utils/system.js +29 -0
  79. package/package.json +4 -4
@@ -104,11 +104,127 @@ 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
  `;
@@ -136,7 +252,8 @@ function startProxyServer(targetUrl, port, options = {}) {
136
252
  headers: __spreadProps(__spreadValues({}, req.headers), {
137
253
  host: target.host,
138
254
  "accept-encoding": "identity"
139
- })
255
+ }),
256
+ timeout: 0
140
257
  };
141
258
  const proxyReq = http.request(requestOptions, (proxyRes) => {
142
259
  var _a;
@@ -182,11 +299,19 @@ function startProxyServer(targetUrl, port, options = {}) {
182
299
  res.writeHead(502);
183
300
  res.end("Proxy error");
184
301
  });
302
+ proxyReq.on("socket", (socket) => {
303
+ socket.setTimeout(0);
304
+ });
305
+ req.on("socket", (socket) => {
306
+ socket.setTimeout(0);
307
+ });
185
308
  req.pipe(proxyReq);
186
309
  });
187
310
  server.on("error", (err) => {
188
311
  reject(err);
189
312
  });
313
+ server.timeout = 0;
314
+ server.keepAliveTimeout = 0;
190
315
  server.listen(port, () => {
191
316
  const address = server.address();
192
317
  const actualPort = typeof address === "object" && address ? address.port : port;
@@ -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;
@@ -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
@@ -224,39 +231,30 @@ Please install OpenCode first:
224
231
  this.sendTaskUpdate("warming_up_chrome");
225
232
  let warmupFailed = false;
226
233
  try {
227
- yield this.api.warmupChromeMcp(viteOrigin);
234
+ yield this.api.warmupChromeMcp(this.workspaceRoot, viteOrigin);
228
235
  timer.checkpoint("Chrome MCP warmup complete");
229
236
  } catch (e) {
230
237
  log.warn("Chrome MCP warmup failed", { error: e });
231
238
  this.chromeMcpWarmupFailed = true;
232
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
+ }
233
247
  }
234
248
  this.sendTaskUpdate("creating_session");
235
- let sessionFailed = false;
236
- try {
237
- this.sessionUrl = yield this.api.getOrCreateSession();
238
- timer.checkpoint("Session created");
239
- log.debug(`Session URL: ${this.sessionUrl}`);
240
- } catch (e) {
241
- log.warn("Failed to get/create session", { error: e });
242
- sessionFailed = true;
243
- }
244
- if (sessionFailed) {
245
- this.sendTaskUpdate("session_creation_failed");
246
- this.isStarted = false;
247
- this.startPromise = null;
248
- } else if (warmupFailed) {
249
- this.sendTaskUpdate("chrome_mcp_failed", { sessionUrl: this.sessionUrl });
250
- this.isStarted = true;
251
- } else {
252
- this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
253
- }
254
- if (!sessionFailed) {
249
+ if (warmupFailed) {
250
+ this.sendTaskUpdate("chrome_mcp_failed", {
251
+ errorType: this.chromeMcpWarmupErrorType,
252
+ errorMessage: this.chromeMcpWarmupErrorMessage
253
+ });
255
254
  this.isStarted = true;
256
255
  } else {
257
- this.sessionUrl = null;
256
+ this.sendTaskUpdate("ready");
258
257
  }
259
- log.debug(`OpenCode services started successfully: ${this.sessionUrl || webUrl}`);
260
258
  timer.end("\u2713 Services started successfully");
261
259
  }))();
262
260
  return this.startPromise;
@@ -264,12 +262,18 @@ Please install OpenCode first:
264
262
  }
265
263
  retryWarmupChromeMcp(viteOrigin) {
266
264
  return __async(this, null, function* () {
267
- const success = yield this.api.retryWarmupChromeMcp(viteOrigin);
268
- if (success) {
265
+ const result = yield this.api.retryWarmupChromeMcp(this.workspaceRoot, viteOrigin);
266
+ if (result.success) {
269
267
  this.chromeMcpWarmupFailed = false;
270
- this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
268
+ this.sendTaskUpdate("ready");
269
+ return { success: true };
271
270
  }
272
- 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
+ };
273
277
  });
274
278
  }
275
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();
@@ -4,3 +4,4 @@ export declare function checkOpenCodeInstalled(): Promise<boolean>;
4
4
  export declare function isPortAvailable(port: number, hostname?: string): Promise<boolean>;
5
5
  export declare function findAvailablePort(startPort: number, hostname?: string, maxTries?: number): Promise<number>;
6
6
  export declare function killOrphanOpenCodeProcesses(): Promise<number>;
7
+ export declare function findGitRoot(startDir: string, maxDepth?: number): string;
@@ -19,8 +19,10 @@ var __async = (__this, __arguments, generator) => {
19
19
  });
20
20
  };
21
21
  import { spawn } from "child_process";
22
+ import fs from "fs";
22
23
  import http from "http";
23
24
  import net from "net";
25
+ import path from "path";
24
26
  import { MAX_PORT_TRIES, SERVER_CHECK_INTERVAL } from "@vite-plugin-opencode-assistant/shared";
25
27
  import { PerformanceTimer, createLogger } from "@vite-plugin-opencode-assistant/shared";
26
28
  const log = createLogger("Utils");
@@ -182,6 +184,31 @@ function killOrphanProcessesOnWindows(resolve, timer) {
182
184
  resolve(0);
183
185
  });
184
186
  }
187
+ function findGitRoot(startDir, maxDepth = 10) {
188
+ const timer = log.timer("findGitRoot", { startDir, maxDepth });
189
+ let currentDir = startDir;
190
+ let depth = 0;
191
+ while (depth < maxDepth) {
192
+ const gitDir = path.join(currentDir, ".git");
193
+ try {
194
+ if (fs.existsSync(gitDir)) {
195
+ timer.end(`\u2713 Found git root at depth ${depth}: ${currentDir}`);
196
+ return currentDir;
197
+ }
198
+ } catch (err) {
199
+ log.debug(`Error checking .git directory at ${currentDir}`, { error: err.message });
200
+ }
201
+ const parentDir = path.dirname(currentDir);
202
+ if (parentDir === currentDir) {
203
+ log.debug("Reached filesystem root");
204
+ break;
205
+ }
206
+ currentDir = parentDir;
207
+ depth++;
208
+ }
209
+ timer.end(`\u274C No git root found after ${depth} levels, using start directory`);
210
+ return startDir;
211
+ }
185
212
  function killOrphanProcessesOnUnix(resolve, timer) {
186
213
  var _a;
187
214
  log.debug("Using Unix method to find orphan processes");
@@ -236,6 +263,7 @@ function killOrphanProcessesOnUnix(resolve, timer) {
236
263
  export {
237
264
  checkOpenCodeInstalled,
238
265
  findAvailablePort,
266
+ findGitRoot,
239
267
  isPortAvailable,
240
268
  killOrphanOpenCodeProcesses,
241
269
  waitForServer
@@ -0,0 +1,6 @@
1
+ import type { WidgetOptions } from "@vite-plugin-opencode-assistant/shared";
2
+ type __VLS_Props = {
3
+ config: Partial<WidgetOptions>;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;