Bugfixes
This commit is contained in:
108
main.js
108
main.js
@@ -27,7 +27,7 @@ __export(main_exports, {
|
|||||||
default: () => OpenCodePlugin
|
default: () => OpenCodePlugin
|
||||||
});
|
});
|
||||||
module.exports = __toCommonJS(main_exports);
|
module.exports = __toCommonJS(main_exports);
|
||||||
var import_obsidian5 = require("obsidian");
|
var import_obsidian4 = require("obsidian");
|
||||||
|
|
||||||
// src/types.ts
|
// src/types.ts
|
||||||
var DEFAULT_SETTINGS = {
|
var DEFAULT_SETTINGS = {
|
||||||
@@ -59,6 +59,7 @@ var OpenCodeView = class extends import_obsidian2.ItemView {
|
|||||||
super(leaf);
|
super(leaf);
|
||||||
this.iframeEl = null;
|
this.iframeEl = null;
|
||||||
this.currentState = "stopped";
|
this.currentState = "stopped";
|
||||||
|
this.unsubscribeStateChange = null;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
getViewType() {
|
getViewType() {
|
||||||
@@ -73,7 +74,7 @@ var OpenCodeView = class extends import_obsidian2.ItemView {
|
|||||||
async onOpen() {
|
async onOpen() {
|
||||||
this.contentEl.empty();
|
this.contentEl.empty();
|
||||||
this.contentEl.addClass("opencode-container");
|
this.contentEl.addClass("opencode-container");
|
||||||
this.plugin.onProcessStateChange((state) => {
|
this.unsubscribeStateChange = this.plugin.onProcessStateChange((state) => {
|
||||||
this.currentState = state;
|
this.currentState = state;
|
||||||
this.updateView();
|
this.updateView();
|
||||||
});
|
});
|
||||||
@@ -84,6 +85,10 @@ var OpenCodeView = class extends import_obsidian2.ItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async onClose() {
|
async onClose() {
|
||||||
|
if (this.unsubscribeStateChange) {
|
||||||
|
this.unsubscribeStateChange();
|
||||||
|
this.unsubscribeStateChange = null;
|
||||||
|
}
|
||||||
if (this.iframeEl) {
|
if (this.iframeEl) {
|
||||||
this.iframeEl.src = "about:blank";
|
this.iframeEl.src = "about:blank";
|
||||||
this.iframeEl = null;
|
this.iframeEl = null;
|
||||||
@@ -190,18 +195,29 @@ var OpenCodeView = class extends import_obsidian2.ItemView {
|
|||||||
const iconEl = statusContainer.createDiv({ cls: "opencode-status-icon" });
|
const iconEl = statusContainer.createDiv({ cls: "opencode-status-icon" });
|
||||||
(0, import_obsidian2.setIcon)(iconEl, "alert-circle");
|
(0, import_obsidian2.setIcon)(iconEl, "alert-circle");
|
||||||
statusContainer.createEl("h3", { text: "Failed to start OpenCode" });
|
statusContainer.createEl("h3", { text: "Failed to start OpenCode" });
|
||||||
statusContainer.createEl("p", {
|
const errorMessage = this.plugin.getLastError();
|
||||||
text: "There was an error starting the OpenCode server. Please check that OpenCode is installed and try again.",
|
if (errorMessage) {
|
||||||
cls: "opencode-status-message"
|
statusContainer.createEl("p", {
|
||||||
|
text: errorMessage,
|
||||||
|
cls: "opencode-status-message opencode-error-message"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
statusContainer.createEl("p", {
|
||||||
|
text: "There was an error starting the OpenCode server.",
|
||||||
|
cls: "opencode-status-message"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const buttonContainer = statusContainer.createDiv({
|
||||||
|
cls: "opencode-button-group"
|
||||||
});
|
});
|
||||||
const retryButton = statusContainer.createEl("button", {
|
const retryButton = buttonContainer.createEl("button", {
|
||||||
text: "Retry",
|
text: "Retry",
|
||||||
cls: "mod-cta"
|
cls: "mod-cta"
|
||||||
});
|
});
|
||||||
retryButton.addEventListener("click", () => {
|
retryButton.addEventListener("click", () => {
|
||||||
this.plugin.startServer();
|
this.plugin.startServer();
|
||||||
});
|
});
|
||||||
const settingsButton = statusContainer.createEl("button", {
|
const settingsButton = buttonContainer.createEl("button", {
|
||||||
text: "Open Settings"
|
text: "Open Settings"
|
||||||
});
|
});
|
||||||
settingsButton.addEventListener("click", () => {
|
settingsButton.addEventListener("click", () => {
|
||||||
@@ -394,11 +410,12 @@ var OpenCodeSettingTab = class extends import_obsidian3.PluginSettingTab {
|
|||||||
|
|
||||||
// src/ProcessManager.ts
|
// src/ProcessManager.ts
|
||||||
var import_child_process = require("child_process");
|
var import_child_process = require("child_process");
|
||||||
var import_obsidian4 = require("obsidian");
|
|
||||||
var ProcessManager = class {
|
var ProcessManager = class {
|
||||||
constructor(settings, workingDirectory, projectDirectory, onStateChange) {
|
constructor(settings, workingDirectory, projectDirectory, onStateChange) {
|
||||||
this.process = null;
|
this.process = null;
|
||||||
this.state = "stopped";
|
this.state = "stopped";
|
||||||
|
this.lastError = null;
|
||||||
|
this.earlyExitCode = null;
|
||||||
this.startupTimeout = null;
|
this.startupTimeout = null;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.workingDirectory = workingDirectory;
|
this.workingDirectory = workingDirectory;
|
||||||
@@ -414,6 +431,9 @@ var ProcessManager = class {
|
|||||||
getState() {
|
getState() {
|
||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
getLastError() {
|
||||||
|
return this.lastError;
|
||||||
|
}
|
||||||
getUrl() {
|
getUrl() {
|
||||||
const baseUrl = `http://${this.settings.hostname}:${this.settings.port}`;
|
const baseUrl = `http://${this.settings.hostname}:${this.settings.port}`;
|
||||||
const encodedPath = btoa(this.projectDirectory);
|
const encodedPath = btoa(this.projectDirectory);
|
||||||
@@ -425,11 +445,12 @@ var ProcessManager = class {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.setState("starting");
|
this.setState("starting");
|
||||||
|
this.lastError = null;
|
||||||
|
this.earlyExitCode = null;
|
||||||
try {
|
try {
|
||||||
if (!this.projectDirectory) {
|
if (!this.projectDirectory) {
|
||||||
const error = "Project directory (vault) not configured";
|
this.lastError = "Project directory (vault) not configured";
|
||||||
console.error("[OpenCode Error]", error);
|
console.error("[OpenCode Error]", this.lastError);
|
||||||
new import_obsidian4.Notice(`Failed to start OpenCode: ${error}`);
|
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -474,13 +495,20 @@ var ProcessManager = class {
|
|||||||
this.process.on("exit", (code, signal) => {
|
this.process.on("exit", (code, signal) => {
|
||||||
console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
|
console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
|
||||||
this.process = null;
|
this.process = null;
|
||||||
|
if (this.state === "starting" && code !== null && code !== 0) {
|
||||||
|
this.earlyExitCode = code;
|
||||||
|
}
|
||||||
if (this.state === "running") {
|
if (this.state === "running") {
|
||||||
this.setState("stopped");
|
this.setState("stopped");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.process.on("error", (err) => {
|
this.process.on("error", (err) => {
|
||||||
console.error("Failed to start OpenCode process:", err);
|
console.error("Failed to start OpenCode process:", err);
|
||||||
new import_obsidian4.Notice(`Failed to start OpenCode: ${err.message}`);
|
if (err.code === "ENOENT") {
|
||||||
|
this.lastError = `OpenCode executable not found at '${this.settings.opencodePath}'`;
|
||||||
|
} else {
|
||||||
|
this.lastError = `Failed to start OpenCode: ${err.message}`;
|
||||||
|
}
|
||||||
this.process = null;
|
this.process = null;
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
});
|
});
|
||||||
@@ -489,13 +517,23 @@ var ProcessManager = class {
|
|||||||
this.setState("running");
|
this.setState("running");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
if (this.state === "error") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.earlyExitCode !== null) {
|
||||||
|
this.lastError = `OpenCode process exited unexpectedly (exit code ${this.earlyExitCode})`;
|
||||||
|
} else if (!this.process) {
|
||||||
|
this.lastError = "OpenCode process exited before server became ready";
|
||||||
|
} else {
|
||||||
|
this.lastError = "OpenCode server failed to start within timeout";
|
||||||
|
}
|
||||||
this.stop();
|
this.stop();
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
new import_obsidian4.Notice("OpenCode server failed to start within timeout");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error starting OpenCode:", error);
|
console.error("Error starting OpenCode:", error);
|
||||||
|
this.lastError = error instanceof Error ? error.message : String(error);
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -506,17 +544,30 @@ var ProcessManager = class {
|
|||||||
this.startupTimeout = null;
|
this.startupTimeout = null;
|
||||||
}
|
}
|
||||||
if (this.process) {
|
if (this.process) {
|
||||||
|
const proc = this.process;
|
||||||
|
const pid = proc.pid;
|
||||||
|
console.log("[OpenCode] Stopping process with PID:", pid);
|
||||||
|
this.setState("stopped");
|
||||||
|
this.process = null;
|
||||||
try {
|
try {
|
||||||
this.process.kill("SIGTERM");
|
proc.kill("SIGTERM");
|
||||||
|
console.log("[OpenCode] Sent SIGTERM to process");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.process && !this.process.killed) {
|
if (proc.exitCode === null && proc.signalCode === null) {
|
||||||
this.process.kill("SIGKILL");
|
console.log("[OpenCode] Process still running after SIGTERM, sending SIGKILL");
|
||||||
|
try {
|
||||||
|
proc.kill("SIGKILL");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[OpenCode] Error sending SIGKILL:", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[OpenCode] Process exited with:", proc.exitCode, proc.signalCode);
|
||||||
}
|
}
|
||||||
}, 2e3);
|
}, 2e3);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error stopping OpenCode process:", error);
|
console.error("[OpenCode] Error stopping process:", error);
|
||||||
}
|
}
|
||||||
this.process = null;
|
return;
|
||||||
}
|
}
|
||||||
this.setState("stopped");
|
this.setState("stopped");
|
||||||
}
|
}
|
||||||
@@ -556,7 +607,7 @@ var ProcessManager = class {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/main.ts
|
// src/main.ts
|
||||||
var OpenCodePlugin = class extends import_obsidian5.Plugin {
|
var OpenCodePlugin = class extends import_obsidian4.Plugin {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.settings = DEFAULT_SETTINGS;
|
this.settings = DEFAULT_SETTINGS;
|
||||||
@@ -680,12 +731,12 @@ var OpenCodePlugin = class extends import_obsidian5.Plugin {
|
|||||||
// Start the OpenCode server
|
// Start the OpenCode server
|
||||||
async startServer() {
|
async startServer() {
|
||||||
if (!this.processManager) {
|
if (!this.processManager) {
|
||||||
new import_obsidian5.Notice("OpenCode: Process manager not initialized");
|
new import_obsidian4.Notice("OpenCode: Process manager not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const success = await this.processManager.start();
|
const success = await this.processManager.start();
|
||||||
if (success) {
|
if (success) {
|
||||||
new import_obsidian5.Notice("OpenCode server started");
|
new import_obsidian4.Notice("OpenCode server started");
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@@ -693,7 +744,7 @@ var OpenCodePlugin = class extends import_obsidian5.Plugin {
|
|||||||
stopServer() {
|
stopServer() {
|
||||||
if (this.processManager) {
|
if (this.processManager) {
|
||||||
this.processManager.stop();
|
this.processManager.stop();
|
||||||
new import_obsidian5.Notice("OpenCode server stopped");
|
new import_obsidian4.Notice("OpenCode server stopped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get the current process state
|
// Get the current process state
|
||||||
@@ -701,14 +752,25 @@ var OpenCodePlugin = class extends import_obsidian5.Plugin {
|
|||||||
var _a, _b;
|
var _a, _b;
|
||||||
return (_b = (_a = this.processManager) == null ? void 0 : _a.getState()) != null ? _b : "stopped";
|
return (_b = (_a = this.processManager) == null ? void 0 : _a.getState()) != null ? _b : "stopped";
|
||||||
}
|
}
|
||||||
|
// Get the last error message from the process manager
|
||||||
|
getLastError() {
|
||||||
|
var _a, _b;
|
||||||
|
return (_b = (_a = this.processManager) == null ? void 0 : _a.getLastError()) != null ? _b : null;
|
||||||
|
}
|
||||||
// Get the server URL
|
// Get the server URL
|
||||||
getServerUrl() {
|
getServerUrl() {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
return (_b = (_a = this.processManager) == null ? void 0 : _a.getUrl()) != null ? _b : `http://127.0.0.1:${this.settings.port}`;
|
return (_b = (_a = this.processManager) == null ? void 0 : _a.getUrl()) != null ? _b : `http://127.0.0.1:${this.settings.port}`;
|
||||||
}
|
}
|
||||||
// Subscribe to process state changes
|
// Subscribe to process state changes, returns unsubscribe function
|
||||||
onProcessStateChange(callback) {
|
onProcessStateChange(callback) {
|
||||||
this.stateChangeCallbacks.push(callback);
|
this.stateChangeCallbacks.push(callback);
|
||||||
|
return () => {
|
||||||
|
const index = this.stateChangeCallbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
this.stateChangeCallbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// Notify all subscribers of state change
|
// Notify all subscribers of state change
|
||||||
notifyStateChange(state) {
|
notifyStateChange(state) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ This plugin embeds the OpenCode AI assistant into Obsidian by spawning a local s
|
|||||||
**Decision:** Embed OpenCode's web UI via iframe rather than building native Obsidian UI.
|
**Decision:** Embed OpenCode's web UI via iframe rather than building native Obsidian UI.
|
||||||
|
|
||||||
**Rationale:**
|
**Rationale:**
|
||||||
|
- Can be up and running quickly
|
||||||
- OpenCode already provides a full-featured web interface
|
- OpenCode already provides a full-featured web interface
|
||||||
- Reduces maintenance burden - UI updates come from OpenCode automatically
|
- Reduces maintenance burden - UI updates come from OpenCode automatically
|
||||||
- Allows feature parity with standalone OpenCode web experience
|
- Allows feature parity with standalone OpenCode web experience
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export class OpenCodeView extends ItemView {
|
|||||||
plugin: OpenCodePlugin;
|
plugin: OpenCodePlugin;
|
||||||
private iframeEl: HTMLIFrameElement | null = null;
|
private iframeEl: HTMLIFrameElement | null = null;
|
||||||
private currentState: ProcessState = "stopped";
|
private currentState: ProcessState = "stopped";
|
||||||
|
private unsubscribeStateChange: (() => void) | null = null;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: OpenCodePlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: OpenCodePlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
@@ -31,7 +32,7 @@ export class OpenCodeView extends ItemView {
|
|||||||
this.contentEl.addClass("opencode-container");
|
this.contentEl.addClass("opencode-container");
|
||||||
|
|
||||||
// Subscribe to state changes
|
// Subscribe to state changes
|
||||||
this.plugin.onProcessStateChange((state) => {
|
this.unsubscribeStateChange = this.plugin.onProcessStateChange((state) => {
|
||||||
this.currentState = state;
|
this.currentState = state;
|
||||||
this.updateView();
|
this.updateView();
|
||||||
});
|
});
|
||||||
@@ -47,6 +48,12 @@ export class OpenCodeView extends ItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onClose(): Promise<void> {
|
async onClose(): Promise<void> {
|
||||||
|
// Unsubscribe from state changes to prevent memory leak
|
||||||
|
if (this.unsubscribeStateChange) {
|
||||||
|
this.unsubscribeStateChange();
|
||||||
|
this.unsubscribeStateChange = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up iframe
|
// Clean up iframe
|
||||||
if (this.iframeEl) {
|
if (this.iframeEl) {
|
||||||
this.iframeEl.src = "about:blank";
|
this.iframeEl.src = "about:blank";
|
||||||
@@ -184,12 +191,26 @@ export class OpenCodeView extends ItemView {
|
|||||||
setIcon(iconEl, "alert-circle");
|
setIcon(iconEl, "alert-circle");
|
||||||
|
|
||||||
statusContainer.createEl("h3", { text: "Failed to start OpenCode" });
|
statusContainer.createEl("h3", { text: "Failed to start OpenCode" });
|
||||||
statusContainer.createEl("p", {
|
|
||||||
text: "There was an error starting the OpenCode server. Please check that OpenCode is installed and try again.",
|
// Display specific error message if available
|
||||||
cls: "opencode-status-message",
|
const errorMessage = this.plugin.getLastError();
|
||||||
|
if (errorMessage) {
|
||||||
|
statusContainer.createEl("p", {
|
||||||
|
text: errorMessage,
|
||||||
|
cls: "opencode-status-message opencode-error-message",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
statusContainer.createEl("p", {
|
||||||
|
text: "There was an error starting the OpenCode server.",
|
||||||
|
cls: "opencode-status-message",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonContainer = statusContainer.createDiv({
|
||||||
|
cls: "opencode-button-group",
|
||||||
});
|
});
|
||||||
|
|
||||||
const retryButton = statusContainer.createEl("button", {
|
const retryButton = buttonContainer.createEl("button", {
|
||||||
text: "Retry",
|
text: "Retry",
|
||||||
cls: "mod-cta",
|
cls: "mod-cta",
|
||||||
});
|
});
|
||||||
@@ -197,7 +218,7 @@ export class OpenCodeView extends ItemView {
|
|||||||
this.plugin.startServer();
|
this.plugin.startServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsButton = statusContainer.createEl("button", {
|
const settingsButton = buttonContainer.createEl("button", {
|
||||||
text: "Open Settings",
|
text: "Open Settings",
|
||||||
});
|
});
|
||||||
settingsButton.addEventListener("click", () => {
|
settingsButton.addEventListener("click", () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { spawn, ChildProcess } from "child_process";
|
import { spawn, ChildProcess } from "child_process";
|
||||||
import { Notice } from "obsidian";
|
|
||||||
import { OpenCodeSettings } from "./types";
|
import { OpenCodeSettings } from "./types";
|
||||||
|
|
||||||
export type ProcessState = "stopped" | "starting" | "running" | "error";
|
export type ProcessState = "stopped" | "starting" | "running" | "error";
|
||||||
@@ -7,6 +6,8 @@ export type ProcessState = "stopped" | "starting" | "running" | "error";
|
|||||||
export class ProcessManager {
|
export class ProcessManager {
|
||||||
private process: ChildProcess | null = null;
|
private process: ChildProcess | null = null;
|
||||||
private state: ProcessState = "stopped";
|
private state: ProcessState = "stopped";
|
||||||
|
private lastError: string | null = null;
|
||||||
|
private earlyExitCode: number | null = null;
|
||||||
private settings: OpenCodeSettings;
|
private settings: OpenCodeSettings;
|
||||||
private workingDirectory: string;
|
private workingDirectory: string;
|
||||||
private projectDirectory: string;
|
private projectDirectory: string;
|
||||||
@@ -37,6 +38,10 @@ export class ProcessManager {
|
|||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLastError(): string | null {
|
||||||
|
return this.lastError;
|
||||||
|
}
|
||||||
|
|
||||||
getUrl(): string {
|
getUrl(): string {
|
||||||
const baseUrl = `http://${this.settings.hostname}:${this.settings.port}`;
|
const baseUrl = `http://${this.settings.hostname}:${this.settings.port}`;
|
||||||
// Encode the project directory path as base64 for the URL
|
// Encode the project directory path as base64 for the URL
|
||||||
@@ -50,13 +55,14 @@ export class ProcessManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState("starting");
|
this.setState("starting");
|
||||||
|
this.lastError = null;
|
||||||
|
this.earlyExitCode = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validate vault/project directory is set
|
// Validate vault/project directory is set
|
||||||
if (!this.projectDirectory) {
|
if (!this.projectDirectory) {
|
||||||
const error = "Project directory (vault) not configured";
|
this.lastError = "Project directory (vault) not configured";
|
||||||
console.error("[OpenCode Error]", error);
|
console.error("[OpenCode Error]", this.lastError);
|
||||||
new Notice(`Failed to start OpenCode: ${error}`);
|
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -113,15 +119,28 @@ export class ProcessManager {
|
|||||||
this.process.on("exit", (code, signal) => {
|
this.process.on("exit", (code, signal) => {
|
||||||
console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
|
console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
|
||||||
this.process = null;
|
this.process = null;
|
||||||
|
|
||||||
|
// Track early exit during startup for better error messages
|
||||||
|
if (this.state === "starting" && code !== null && code !== 0) {
|
||||||
|
this.earlyExitCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
// Only set stopped if we're in running state (not during startup)
|
// Only set stopped if we're in running state (not during startup)
|
||||||
if (this.state === "running") {
|
if (this.state === "running") {
|
||||||
this.setState("stopped");
|
this.setState("stopped");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.process.on("error", (err) => {
|
this.process.on("error", (err: NodeJS.ErrnoException) => {
|
||||||
console.error("Failed to start OpenCode process:", err);
|
console.error("Failed to start OpenCode process:", err);
|
||||||
new Notice(`Failed to start OpenCode: ${err.message}`);
|
|
||||||
|
// Provide user-friendly error messages for common errors
|
||||||
|
if (err.code === "ENOENT") {
|
||||||
|
this.lastError = `OpenCode executable not found at '${this.settings.opencodePath}'`;
|
||||||
|
} else {
|
||||||
|
this.lastError = `Failed to start OpenCode: ${err.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
this.process = null;
|
this.process = null;
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
});
|
});
|
||||||
@@ -132,13 +151,27 @@ export class ProcessManager {
|
|||||||
this.setState("running");
|
this.setState("running");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
// If already in error state (e.g., from spawn error event), don't overwrite
|
||||||
|
if (this.state === "error") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine appropriate error message
|
||||||
|
if (this.earlyExitCode !== null) {
|
||||||
|
this.lastError = `OpenCode process exited unexpectedly (exit code ${this.earlyExitCode})`;
|
||||||
|
} else if (!this.process) {
|
||||||
|
this.lastError = "OpenCode process exited before server became ready";
|
||||||
|
} else {
|
||||||
|
this.lastError = "OpenCode server failed to start within timeout";
|
||||||
|
}
|
||||||
|
|
||||||
this.stop();
|
this.stop();
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
new Notice("OpenCode server failed to start within timeout");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error starting OpenCode:", error);
|
console.error("Error starting OpenCode:", error);
|
||||||
|
this.lastError = error instanceof Error ? error.message : String(error);
|
||||||
this.setState("error");
|
this.setState("error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -151,20 +184,42 @@ export class ProcessManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.process) {
|
if (this.process) {
|
||||||
|
const proc = this.process;
|
||||||
|
const pid = proc.pid;
|
||||||
|
|
||||||
|
console.log("[OpenCode] Stopping process with PID:", pid);
|
||||||
|
|
||||||
|
// Set state to stopped first to prevent exit handler from interfering
|
||||||
|
this.setState("stopped");
|
||||||
|
|
||||||
|
// Now clear the process reference before killing
|
||||||
|
// This ensures the exit handler knows we initiated the stop
|
||||||
|
this.process = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try graceful shutdown first
|
// Try graceful shutdown first
|
||||||
this.process.kill("SIGTERM");
|
proc.kill("SIGTERM");
|
||||||
|
console.log("[OpenCode] Sent SIGTERM to process");
|
||||||
|
|
||||||
// Force kill after 2 seconds if still running
|
// Force kill after 2 seconds if still running
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.process && !this.process.killed) {
|
// Check if process has exited (exitCode or signalCode will be set)
|
||||||
this.process.kill("SIGKILL");
|
if (proc.exitCode === null && proc.signalCode === null) {
|
||||||
|
console.log("[OpenCode] Process still running after SIGTERM, sending SIGKILL");
|
||||||
|
try {
|
||||||
|
proc.kill("SIGKILL");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[OpenCode] Error sending SIGKILL:", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[OpenCode] Process exited with:", proc.exitCode, proc.signalCode);
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error stopping OpenCode process:", error);
|
console.error("[OpenCode] Error stopping process:", error);
|
||||||
}
|
}
|
||||||
this.process = null;
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState("stopped");
|
this.setState("stopped");
|
||||||
|
|||||||
15
src/main.ts
15
src/main.ts
@@ -197,14 +197,25 @@ export default class OpenCodePlugin extends Plugin {
|
|||||||
return this.processManager?.getState() ?? "stopped";
|
return this.processManager?.getState() ?? "stopped";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the last error message from the process manager
|
||||||
|
getLastError(): string | null {
|
||||||
|
return this.processManager?.getLastError() ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the server URL
|
// Get the server URL
|
||||||
getServerUrl(): string {
|
getServerUrl(): string {
|
||||||
return this.processManager?.getUrl() ?? `http://127.0.0.1:${this.settings.port}`;
|
return this.processManager?.getUrl() ?? `http://127.0.0.1:${this.settings.port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to process state changes
|
// Subscribe to process state changes, returns unsubscribe function
|
||||||
onProcessStateChange(callback: (state: ProcessState) => void): void {
|
onProcessStateChange(callback: (state: ProcessState) => void): () => void {
|
||||||
this.stateChangeCallbacks.push(callback);
|
this.stateChangeCallbacks.push(callback);
|
||||||
|
return () => {
|
||||||
|
const index = this.stateChangeCallbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
this.stateChangeCallbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all subscribers of state change
|
// Notify all subscribers of state change
|
||||||
|
|||||||
Reference in New Issue
Block a user