Move OpenCode UI into Electron shell
This commit is contained in:
@@ -10,14 +10,19 @@
|
||||
"package": "pnpm run build && electron-builder --config electron-builder.yml"
|
||||
},
|
||||
"dependencies": {
|
||||
"@git.zone/ide-protocol": "workspace:*",
|
||||
"@git.zone/ide-opencode-bridge": "workspace:*",
|
||||
"@git.zone/ide-protocol": "workspace:*",
|
||||
"@git.zone/ide-server-installer": "workspace:*",
|
||||
"@git.zone/ide-ssh": "workspace:*",
|
||||
"electron": "^42.0.1"
|
||||
"electron": "^42.0.1",
|
||||
"opencode-ai": "1.14.48"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron-builder": "^26.8.1"
|
||||
},
|
||||
"files": ["dist_ts/**/*", "preload.cjs", "electron-builder.yml"]
|
||||
"files": [
|
||||
"dist_ts/**/*",
|
||||
"preload.cjs",
|
||||
"electron-builder.yml"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,22 @@ contextBridge.exposeInMainWorld('gitZoneIde', {
|
||||
connect: (input) => ipcRenderer.invoke('gitzone:connect', input),
|
||||
addProject: (input) => ipcRenderer.invoke('gitzone:add-project', input),
|
||||
openProject: (input) => ipcRenderer.invoke('gitzone:open-project', input),
|
||||
openCode: {
|
||||
health: (input) => ipcRenderer.invoke('gitzone:opencode-health', input),
|
||||
sessions: (input) => ipcRenderer.invoke('gitzone:opencode-sessions', input),
|
||||
createSession: (input) => ipcRenderer.invoke('gitzone:opencode-create-session', input),
|
||||
messages: (input) => ipcRenderer.invoke('gitzone:opencode-messages', input),
|
||||
prompt: (input) => ipcRenderer.invoke('gitzone:opencode-prompt', input),
|
||||
abort: (input) => ipcRenderer.invoke('gitzone:opencode-abort', input),
|
||||
respondToPermission: (input) => ipcRenderer.invoke('gitzone:opencode-respond-permission', input),
|
||||
providers: (input) => ipcRenderer.invoke('gitzone:opencode-providers', input),
|
||||
agents: (input) => ipcRenderer.invoke('gitzone:opencode-agents', input),
|
||||
onEvent: (callback) => {
|
||||
const listener = (_event, payload) => callback(payload);
|
||||
ipcRenderer.on('gitzone:opencode-event', listener);
|
||||
return () => ipcRenderer.removeListener('gitzone:opencode-event', listener);
|
||||
},
|
||||
},
|
||||
onConnectProgress: (callback) => {
|
||||
const listener = (_event, message) => callback(message);
|
||||
ipcRenderer.on('gitzone:connect-progress', listener);
|
||||
|
||||
@@ -213,6 +213,76 @@ class GitZoneIdeElectronShell {
|
||||
progress(`Project ${project.title} is ready.`);
|
||||
return instance;
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-health', async (_event, input: IOpenCodeRuntimeInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).health();
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-sessions', async (_event, input: IOpenCodeRuntimeInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).sessions();
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-create-session', async (_event, input: IOpenCodeCreateSessionInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).createSession({
|
||||
title: trimOptional(input.title),
|
||||
});
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-messages', async (_event, input: IOpenCodeSessionInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).messages(
|
||||
requireTrimmed(input.sessionId, 'OpenCode session id'),
|
||||
normalizeOptionalLimit(input.limit, 200),
|
||||
);
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-prompt', async (_event, input: IOpenCodePromptInput) => {
|
||||
const text = requireBoundedText(input.text, 'Prompt', 200000);
|
||||
await this.createLocalOpenCodeClient(input.instanceId).promptAsync(
|
||||
requireTrimmed(input.sessionId, 'OpenCode session id'),
|
||||
{
|
||||
agent: trimOptional(input.agent),
|
||||
parts: [{ type: 'text', text }],
|
||||
},
|
||||
);
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-abort', async (_event, input: IOpenCodeSessionInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).abort(requireTrimmed(input.sessionId, 'OpenCode session id'));
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-respond-permission', async (_event, input: IOpenCodePermissionInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).respondToPermission(
|
||||
requireTrimmed(input.sessionId, 'OpenCode session id'),
|
||||
requireTrimmed(input.permissionId, 'OpenCode permission id'),
|
||||
{
|
||||
response: requireTrimmed(input.response, 'OpenCode permission response'),
|
||||
remember: input.remember,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-providers', async (_event, input: IOpenCodeRuntimeInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).providers();
|
||||
});
|
||||
|
||||
plugins.electron.ipcMain.handle('gitzone:opencode-agents', async (_event, input: IOpenCodeRuntimeInput) => {
|
||||
return this.createLocalOpenCodeClient(input.instanceId).agents();
|
||||
});
|
||||
}
|
||||
|
||||
private requireLocalOpenCodeRuntime(instanceId: string | undefined) {
|
||||
const id = requireTrimmed(instanceId, 'OpenCode runtime id');
|
||||
const runtime = this.localOpenCodeRuntimes.get(id);
|
||||
if (!runtime) {
|
||||
throw new Error(`OpenCode runtime not found: ${id}`);
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
|
||||
private createLocalOpenCodeClient(instanceId: string | undefined) {
|
||||
const runtime = this.requireLocalOpenCodeRuntime(instanceId);
|
||||
return createLocalOpenCodeClient(runtime);
|
||||
}
|
||||
|
||||
private async ensureToolBridge() {
|
||||
@@ -292,6 +362,7 @@ class GitZoneIdeElectronShell {
|
||||
});
|
||||
|
||||
await waitForOpenCodeHealth(baseUrl, username, password, 15000);
|
||||
startOpenCodeEventStream(runtime);
|
||||
progress(`Local OpenCode server ready for ${project.title}; remote tools are bridged over SSH.`);
|
||||
return {
|
||||
status: 'ready',
|
||||
@@ -385,6 +456,30 @@ interface IOpenProjectInput {
|
||||
projectPath?: string;
|
||||
}
|
||||
|
||||
interface IOpenCodeRuntimeInput {
|
||||
instanceId?: string;
|
||||
}
|
||||
|
||||
interface IOpenCodeCreateSessionInput extends IOpenCodeRuntimeInput {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
interface IOpenCodeSessionInput extends IOpenCodeRuntimeInput {
|
||||
sessionId?: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
interface IOpenCodePromptInput extends IOpenCodeSessionInput {
|
||||
text?: string;
|
||||
agent?: string;
|
||||
}
|
||||
|
||||
interface IOpenCodePermissionInput extends IOpenCodeSessionInput {
|
||||
permissionId?: string;
|
||||
response?: string;
|
||||
remember?: boolean;
|
||||
}
|
||||
|
||||
interface IRemoteProject {
|
||||
id: string;
|
||||
path: string;
|
||||
@@ -425,6 +520,7 @@ interface ILocalOpenCodeRuntime {
|
||||
process: childProcess.ChildProcess;
|
||||
bridge: LocalOpenCodeToolBridge;
|
||||
bridgeToken: string;
|
||||
eventAbortController?: AbortController;
|
||||
configDir: string;
|
||||
proxyWorkspacePath: string;
|
||||
baseUrl: string;
|
||||
@@ -649,12 +745,13 @@ const writeOpenCodeBridgeConfig = async (configDir: string) => {
|
||||
};
|
||||
|
||||
const resolveOpenCodeExecutable = async () => {
|
||||
const executableName = process.platform === 'win32' ? 'opencode.cmd' : 'opencode';
|
||||
const candidates = [
|
||||
process.env.GITZONE_IDE_OPENCODE_BINARY,
|
||||
process.env.OPENCODE_BINARY,
|
||||
path.join(os.homedir(), '.opencode', 'bin', 'opencode'),
|
||||
'/usr/local/bin/opencode',
|
||||
'/usr/bin/opencode',
|
||||
path.join(electronPackageRoot, 'node_modules', '.bin', executableName),
|
||||
path.join(workspaceRoot, 'node_modules', '.bin', executableName),
|
||||
path.join(electronPackageRoot, 'node_modules', 'opencode-ai', 'bin', 'opencode'),
|
||||
path.join(workspaceRoot, 'node_modules', 'opencode-ai', 'bin', 'opencode'),
|
||||
].filter(Boolean) as string[];
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
@@ -662,7 +759,7 @@ const resolveOpenCodeExecutable = async () => {
|
||||
return candidate;
|
||||
} catch {}
|
||||
}
|
||||
return 'opencode';
|
||||
throw new Error('Git.Zone IDE OpenCode install not found. Run pnpm install so the electron shell dependency opencode-ai is available.');
|
||||
};
|
||||
|
||||
const waitForOpenCodeHealth = async (baseUrl: string, username: string, password: string, timeoutMs: number) => {
|
||||
@@ -692,6 +789,7 @@ const waitForOpenCodeHealth = async (baseUrl: string, username: string, password
|
||||
};
|
||||
|
||||
const disposeLocalOpenCodeRuntime = (runtime: ILocalOpenCodeRuntime) => {
|
||||
runtime.eventAbortController?.abort();
|
||||
runtime.bridge.unregister(runtime.bridgeToken);
|
||||
if (runtime.process.exitCode === null && !runtime.process.killed) {
|
||||
runtime.process.kill('SIGTERM');
|
||||
@@ -748,6 +846,68 @@ const normalizeOptionalPort = (value: number | undefined, label: string) => {
|
||||
return port;
|
||||
};
|
||||
|
||||
const normalizeOptionalLimit = (value: number | undefined, defaultLimit: number) => {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultLimit;
|
||||
}
|
||||
const limit = Number(value);
|
||||
if (!Number.isInteger(limit) || limit <= 0) {
|
||||
return defaultLimit;
|
||||
}
|
||||
return Math.min(limit, 500);
|
||||
};
|
||||
|
||||
const requireBoundedText = (value: string | undefined, label: string, maxLength: number) => {
|
||||
const text = requireTrimmed(value, label);
|
||||
if (text.length > maxLength) {
|
||||
throw new Error(`${label} must not exceed ${maxLength} characters.`);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const createLocalOpenCodeClient = (runtime: ILocalOpenCodeRuntime) => new plugins.ideOpenCodeBridge.OpenCodeServerClient({
|
||||
baseUrl: runtime.baseUrl,
|
||||
username: runtime.username,
|
||||
password: runtime.password,
|
||||
});
|
||||
|
||||
const startOpenCodeEventStream = (runtime: ILocalOpenCodeRuntime) => {
|
||||
runtime.eventAbortController?.abort();
|
||||
const abortController = new AbortController();
|
||||
runtime.eventAbortController = abortController;
|
||||
void (async () => {
|
||||
while (!abortController.signal.aborted && runtime.process.exitCode === null) {
|
||||
try {
|
||||
const client = createLocalOpenCodeClient(runtime);
|
||||
for await (const event of client.events(abortController.signal)) {
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
sendOpenCodeEventToRenderers(runtime.instanceId, event);
|
||||
}
|
||||
} catch (error) {
|
||||
if (abortController.signal.aborted || runtime.process.exitCode !== null) {
|
||||
return;
|
||||
}
|
||||
console.warn(`OpenCode event stream failed for ${runtime.instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const sendOpenCodeEventToRenderers = (instanceId: string, event: plugins.ideOpenCodeBridge.IOpenCodeEvent) => {
|
||||
const payload = {
|
||||
instanceId,
|
||||
event: plugins.ideOpenCodeBridge.sanitizeOpenCodeEventForRenderer(event),
|
||||
};
|
||||
for (const window of plugins.electron.BrowserWindow.getAllWindows()) {
|
||||
if (!window.webContents.isDestroyed()) {
|
||||
window.webContents.send('gitzone:opencode-event', payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toHostSessionDescriptor = (session: IRemoteHostSession) => ({
|
||||
id: session.id,
|
||||
hostAlias: session.target.hostAlias,
|
||||
@@ -1085,7 +1245,7 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
<title>Git.Zone IDE</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
:root { color-scheme: dark; --activity: #333333; --activity-muted: #858585; --activity-hover: #d7d7d7; --side: #252526; --editor: #1e1e1e; --panel: #181818; --border: #3c3c3c; --input: #3c3c3c; --text: #cccccc; --muted: #8f8f8f; --blue: #007acc; --blue-hover: #0e639c; --green: #89d185; --tab: #2d2d2d; --tab-active: #1e1e1e; }
|
||||
:root { color-scheme: dark; --activity: #333333; --activity-muted: #858585; --activity-hover: #d7d7d7; --side: #252526; --editor: #1e1e1e; --panel: #181818; --border: #3c3c3c; --input: #3c3c3c; --text: #cccccc; --muted: #8f8f8f; --blue: #007acc; --blue-hover: #0e639c; --green: #89d185; --tab: #2d2d2d; --tab-active: #1e1e1e; --sidebar-width: 300px; --panel-height: 118px; }
|
||||
body { margin: 0; overflow: hidden; font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--editor); color: var(--text); }
|
||||
button, input, select { font: inherit; }
|
||||
button { height: 30px; padding: 0 12px; border: 1px solid transparent; color: #ffffff; background: #3a3d41; cursor: pointer; }
|
||||
@@ -1098,17 +1258,17 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
label { display: block; margin: 13px 0 5px; color: #bbbbbb; font-size: 12px; }
|
||||
p { color: var(--muted); line-height: 1.45; }
|
||||
[hidden] { display: none !important; }
|
||||
.workbench { min-width: 980px; height: 100vh; display: grid; grid-template-rows: 30px 32px minmax(0, 1fr) 118px 22px; }
|
||||
.titlebar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 0 12px; background: #3c3c3c; color: #cccccc; user-select: none; }
|
||||
.workbench { min-width: 980px; height: 100vh; display: grid; grid-template-rows: 30px 32px minmax(0, 1fr) 6px var(--panel-height) 22px; }
|
||||
.titlebar { grid-row: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 0 12px; background: #3c3c3c; color: #cccccc; user-select: none; }
|
||||
.titlebar-title { font-size: 12px; }
|
||||
.titlebar-host { color: #d7ba7d; font-size: 12px; }
|
||||
.project-tabs { display: flex; align-items: end; min-width: 0; overflow-x: auto; background: #252526; border-bottom: 1px solid var(--border); }
|
||||
.project-tabs { grid-row: 2; display: flex; align-items: end; min-width: 0; overflow-x: auto; background: #252526; border-bottom: 1px solid var(--border); }
|
||||
.project-tab { height: 31px; max-width: 220px; display: flex; align-items: center; gap: 8px; padding: 0 11px; border: 0; border-right: 1px solid var(--border); border-radius: 0; background: var(--tab); color: #c8c8c8; white-space: nowrap; }
|
||||
.project-tab.active { background: var(--tab-active); color: #ffffff; box-shadow: inset 0 1px 0 var(--blue); }
|
||||
.project-tab-close { color: #999999; font-size: 13px; }
|
||||
.project-tab-close:hover { color: #ffffff; }
|
||||
.content { min-height: 0; display: grid; grid-template-columns: 48px 300px minmax(0, 1fr); }
|
||||
.activitybar { background: var(--activity); border-right: 1px solid #2b2b2b; display: flex; flex-direction: column; align-items: stretch; justify-content: space-between; padding: 4px 0; }
|
||||
.content { grid-row: 3; min-height: 0; display: grid; grid-template-columns: 48px var(--sidebar-width) 6px minmax(0, 1fr); overflow: hidden; }
|
||||
.activitybar { grid-column: 1; background: var(--activity); border-right: 1px solid #2b2b2b; display: flex; flex-direction: column; align-items: stretch; justify-content: space-between; padding: 4px 0; }
|
||||
.activitybar-group { display: flex; flex-direction: column; align-items: stretch; }
|
||||
.activity-button { position: relative; width: 47px; height: 48px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 0; background: transparent; color: var(--activity-muted); }
|
||||
.activity-button:hover { background: transparent; color: var(--activity-hover); }
|
||||
@@ -1117,7 +1277,7 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
.activity-button svg { width: 24px; height: 24px; }
|
||||
.activity-button svg [fill] { fill: currentColor; }
|
||||
.activity-button-bottom { margin-top: auto; }
|
||||
.sidebar { min-width: 0; background: var(--side); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
|
||||
.sidebar { grid-column: 2; min-width: 0; background: var(--side); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
|
||||
.sidebar-panel { min-height: 0; display: flex; flex: 1; flex-direction: column; overflow: hidden; }
|
||||
.sidebar-title { padding: 11px 12px 8px; color: #bbbbbb; font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||
.sidebar-toolbar { display: flex; gap: 6px; padding: 0 10px 10px; }
|
||||
@@ -1130,6 +1290,22 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
.sidebar-form .actions button { flex: 1; }
|
||||
.sidebar-note { margin: 0 10px 10px; color: var(--muted); font-size: 12px; line-height: 1.4; }
|
||||
.sidebar-output { min-height: 0; flex: 1; margin: 0; border-top: 1px solid var(--border); background: #141414; }
|
||||
.opencode-session-select { margin: 0 10px 8px; width: calc(100% - 20px); height: 28px; background: #2d2d2d; }
|
||||
.opencode-permissions { display: flex; flex-direction: column; gap: 8px; padding: 0 10px 8px; }
|
||||
.opencode-permission { padding: 8px; border: 1px solid #5a3d1a; background: #2a2117; color: #d7ba7d; }
|
||||
.opencode-permission-title { margin-bottom: 6px; color: #ffffff; font-size: 12px; }
|
||||
.opencode-permission pre { max-height: 100px; margin: 0 0 8px; padding: 6px; background: #1a1a1a; font-size: 11px; }
|
||||
.opencode-chat-log { min-height: 0; flex: 1; overflow: auto; padding: 0 10px 8px; border-top: 1px solid var(--border); }
|
||||
.opencode-message { margin: 8px 0; padding: 8px; border: 1px solid #333333; background: #1f1f1f; }
|
||||
.opencode-message.user { border-left: 2px solid var(--blue); }
|
||||
.opencode-message.assistant { border-left: 2px solid var(--green); }
|
||||
.opencode-message.tool { border-left: 2px solid #d7ba7d; }
|
||||
.opencode-message-title { margin-bottom: 5px; color: #ffffff; font-size: 11px; font-weight: 700; text-transform: uppercase; }
|
||||
.opencode-message-text { color: #d4d4d4; white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.4; }
|
||||
.opencode-composer { padding: 8px 10px 10px; border-top: 1px solid var(--border); background: #202020; }
|
||||
.opencode-composer textarea { width: 100%; min-height: 78px; resize: vertical; padding: 7px 8px; border: 1px solid var(--border); background: #151515; color: #f0f0f0; font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; outline: none; }
|
||||
.opencode-composer textarea:focus { border-color: var(--blue); }
|
||||
.opencode-composer .actions { margin-top: 7px; }
|
||||
.host-select { margin: 0 10px 8px; width: calc(100% - 20px); height: 28px; background: #2d2d2d; }
|
||||
.host-list, .project-list { overflow: auto; padding-bottom: 8px; }
|
||||
.host-list { max-height: 36%; }
|
||||
@@ -1141,9 +1317,20 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
.host-detail, .project-detail { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.empty { display: none; margin: 8px 10px; padding: 10px; border: 1px dashed #555555; color: #a6a6a6; background: #1f1f1f; }
|
||||
.remote-panel { border-top: 1px solid var(--border); min-height: 0; display: flex; flex-direction: column; }
|
||||
.main { min-width: 0; min-height: 0; background: var(--editor); display: grid; grid-template-rows: 35px minmax(0, 1fr); }
|
||||
.main-header { display: flex; align-items: center; padding: 0 14px; background: #1f1f1f; border-bottom: 1px solid var(--border); color: #ffffff; }
|
||||
.view { min-width: 0; min-height: 0; overflow: auto; padding: 28px 34px 34px; }
|
||||
.splitter { position: relative; background: #252526; }
|
||||
.splitter::before { content: ''; position: absolute; background: transparent; }
|
||||
.splitter:hover, .splitter.active { background: #3a3d41; }
|
||||
.sidebar-resizer { grid-column: 3; cursor: col-resize; border-right: 1px solid var(--border); }
|
||||
.sidebar-resizer::before { top: 0; bottom: 0; left: -4px; right: -4px; }
|
||||
.panel-resizer { grid-row: 4; cursor: row-resize; border-top: 1px solid #2b2b2b; border-bottom: 1px solid var(--border); }
|
||||
.panel-resizer::before { left: 0; right: 0; top: -4px; bottom: -4px; }
|
||||
body.resizing { user-select: none; }
|
||||
.main { grid-column: 4; min-width: 0; min-height: 0; overflow: hidden; background: var(--editor); display: grid; grid-template-rows: 35px minmax(0, 1fr); }
|
||||
.main-header { grid-row: 1; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 8px 0 14px; background: #1f1f1f; border-bottom: 1px solid var(--border); color: #ffffff; }
|
||||
.main-header-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.main-header-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
|
||||
.main-header-action { height: 24px; padding: 0 9px; font-size: 12px; }
|
||||
.view { grid-row: 2; min-width: 0; min-height: 0; overflow: auto; padding: 28px 34px 34px; }
|
||||
.frame-view { padding: 0; overflow: hidden; }
|
||||
webview { width: 100%; height: 100%; border: 0; background: #111111; }
|
||||
.welcome { max-width: 840px; }
|
||||
@@ -1159,15 +1346,30 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
.project-card-title { color: #ffffff; font-size: 14px; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.project-card-path { color: var(--muted); font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.project-card button { margin-top: 10px; }
|
||||
.panel { background: var(--panel); border-top: 1px solid var(--border); display: grid; grid-template-rows: 28px minmax(0, 1fr); }
|
||||
.panel { grid-row: 5; background: var(--panel); border-top: 1px solid var(--border); display: grid; grid-template-rows: 28px minmax(0, 1fr); }
|
||||
.panel-title { display: flex; align-items: center; padding: 0 12px; color: #cccccc; font-size: 11px; font-weight: 700; text-transform: uppercase; border-bottom: 1px solid #2a2a2a; }
|
||||
pre { margin: 0; padding: 10px 12px; overflow: auto; white-space: pre-wrap; color: #d4d4d4; font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.statusbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 10px; background: var(--blue); color: #ffffff; font-size: 12px; }
|
||||
@media (max-width: 860px) { body { overflow: auto; } .workbench { min-width: 0; height: auto; min-height: 100vh; grid-template-rows: 30px auto auto 160px 22px; } .content { grid-template-columns: 1fr; } .activitybar { display: none; } .sidebar { max-height: 420px; } .grid { grid-template-columns: 1fr; } .view { padding: 20px; } }
|
||||
.statusbar { grid-row: 6; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 10px; background: var(--blue); color: #ffffff; font-size: 12px; }
|
||||
.statusbar-left, .statusbar-right { min-width: 0; display: flex; align-items: center; gap: 10px; }
|
||||
.statusbar-left { flex: 1; }
|
||||
.statusbar-right { flex: 0 1 auto; }
|
||||
.statusbar span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.statusbar-button { display: none; height: 18px; padding: 0 8px; border-color: rgba(255,255,255,0.25); background: rgba(0,0,0,0.18); color: #ffffff; font-size: 11px; }
|
||||
.statusbar-button:hover { background: rgba(255,255,255,0.16); }
|
||||
.workbench.main-expanded { grid-template-rows: 0 0 minmax(0, 1fr) 0 0 22px; }
|
||||
.workbench.main-expanded .content { grid-template-columns: minmax(0, 1fr); }
|
||||
.workbench.main-expanded .main { grid-column: 1 / -1; }
|
||||
.workbench.main-expanded .main { grid-template-rows: minmax(0, 1fr); }
|
||||
.workbench.main-expanded .titlebar, .workbench.main-expanded .project-tabs { display: none; }
|
||||
.workbench.main-expanded .main-header { display: none; }
|
||||
.workbench.main-expanded .frame-view { grid-row: 1; }
|
||||
.workbench.main-expanded .activitybar, .workbench.main-expanded .sidebar, .workbench.main-expanded .sidebar-resizer, .workbench.main-expanded .panel-resizer, .workbench.main-expanded .panel { display: none; }
|
||||
.workbench.main-expanded .statusbar-button { display: inline-flex; align-items: center; }
|
||||
@media (max-width: 860px) { body { overflow: auto; } .workbench { min-width: 0; height: auto; min-height: 100vh; grid-template-rows: 30px auto auto 0 160px 22px; } .workbench.main-expanded { grid-template-rows: 30px auto auto 0 0 22px; } .content { grid-template-columns: 1fr; } .activitybar, .sidebar-resizer, .panel-resizer { display: none; } .sidebar { max-height: 420px; } .grid { grid-template-columns: 1fr; } .view { padding: 20px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="workbench">
|
||||
<main id="workbench" class="workbench">
|
||||
<div class="titlebar"><span class="titlebar-title">Git.Zone IDE</span><span id="titlebarHost" class="titlebar-host">Not connected</span></div>
|
||||
<div id="projectTabs" class="project-tabs"></div>
|
||||
<div class="content">
|
||||
@@ -1240,6 +1442,18 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
<p class="sidebar-note">OpenCode runs locally with your local provider config. Git.Zone overrides code tools so shell and file operations execute on the selected remote project over SSH.</p>
|
||||
<div class="sidebar-toolbar">
|
||||
<button id="showActiveProject" class="secondary">Active Project</button>
|
||||
<button id="newOpenCodeSession" class="secondary">New Chat</button>
|
||||
<button id="refreshOpenCode" class="secondary">Refresh</button>
|
||||
</div>
|
||||
<select id="opencodeSession" class="opencode-session-select"></select>
|
||||
<div id="opencodePermissions" class="opencode-permissions"></div>
|
||||
<div id="opencodeMessages" class="opencode-chat-log"></div>
|
||||
<div class="opencode-composer">
|
||||
<textarea id="opencodePrompt" placeholder="Ask OpenCode about this remote project..."></textarea>
|
||||
<div class="actions">
|
||||
<button id="sendOpenCodePrompt" class="primary">Send</button>
|
||||
<button id="abortOpenCode" class="secondary">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="outputPanel" class="sidebar-panel" data-sidebar-panel="output" hidden>
|
||||
@@ -1260,8 +1474,14 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
<div id="sidebarResizer" class="splitter sidebar-resizer" role="separator" aria-label="Resize sidebar" aria-orientation="vertical"></div>
|
||||
<section class="main">
|
||||
<div id="mainHeader" class="main-header">Connect to SSH Host</div>
|
||||
<div id="mainHeader" class="main-header">
|
||||
<span id="mainHeaderTitle" class="main-header-title">Connect to SSH Host</span>
|
||||
<div class="main-header-actions">
|
||||
<button id="expandMainPanel" class="secondary main-header-action" type="button" disabled>Expand Theia</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="connectView" class="view">
|
||||
<div class="welcome">
|
||||
<h1>Connect to Remote Host</h1>
|
||||
@@ -1300,29 +1520,42 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="panelResizer" class="splitter panel-resizer" role="separator" aria-label="Resize output panel" aria-orientation="horizontal"></div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">Output</div>
|
||||
<pre id="output"></pre>
|
||||
</div>
|
||||
<div class="statusbar"><span id="statusText">Ready</span><span id="statusRight">OpenSSH</span></div>
|
||||
<div class="statusbar">
|
||||
<div class="statusbar-left"><span id="statusText">Ready</span></div>
|
||||
<div class="statusbar-right"><button id="restoreMainPanelFooter" class="statusbar-button" type="button">Restore Shell</button><span id="statusRight">OpenSSH</span></div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
const ideApi = window.gitZoneIde;
|
||||
const elements = {
|
||||
workbench: document.getElementById('workbench'),
|
||||
activityButtons: Array.from(document.querySelectorAll('.activity-button[data-view]')),
|
||||
sidebarPanels: Array.from(document.querySelectorAll('[data-sidebar-panel]')),
|
||||
sidebarResizer: document.getElementById('sidebarResizer'),
|
||||
panelResizer: document.getElementById('panelResizer'),
|
||||
projectTabs: document.getElementById('projectTabs'),
|
||||
savedHost: document.getElementById('savedHost'),
|
||||
hostList: document.getElementById('hostList'),
|
||||
emptyState: document.getElementById('emptyState'),
|
||||
explorerHostStatus: document.getElementById('explorerHostStatus'),
|
||||
opencodeStatus: document.getElementById('opencodeStatus'),
|
||||
opencodeSession: document.getElementById('opencodeSession'),
|
||||
opencodePermissions: document.getElementById('opencodePermissions'),
|
||||
opencodeMessages: document.getElementById('opencodeMessages'),
|
||||
opencodePrompt: document.getElementById('opencodePrompt'),
|
||||
projectList: document.getElementById('projectList'),
|
||||
projectEmptyState: document.getElementById('projectEmptyState'),
|
||||
projectCards: document.getElementById('projectCards'),
|
||||
configPath: document.getElementById('configPath'),
|
||||
titlebarHost: document.getElementById('titlebarHost'),
|
||||
mainHeader: document.getElementById('mainHeader'),
|
||||
mainHeaderTitle: document.getElementById('mainHeaderTitle'),
|
||||
expandMainPanel: document.getElementById('expandMainPanel'),
|
||||
restoreMainPanelFooter: document.getElementById('restoreMainPanelFooter'),
|
||||
connectView: document.getElementById('connectView'),
|
||||
dashboardView: document.getElementById('dashboardView'),
|
||||
frameView: document.getElementById('frameView'),
|
||||
@@ -1347,6 +1580,23 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
let openTabs = [];
|
||||
let activeTabId = 'dashboard';
|
||||
let currentActivityView = 'hosts';
|
||||
let projectFrameDomReady = false;
|
||||
let opencodeState = {
|
||||
instanceId: undefined,
|
||||
sessions: [],
|
||||
activeSessionId: undefined,
|
||||
messages: [],
|
||||
permissions: [],
|
||||
loading: false,
|
||||
};
|
||||
let opencodeRefreshTimer = undefined;
|
||||
const layoutStorageKey = 'gitzone-ide-layout-v1';
|
||||
const defaultLayoutState = {
|
||||
sidebarWidth: 300,
|
||||
panelHeight: 118,
|
||||
mainExpanded: false,
|
||||
};
|
||||
let layoutState = { ...defaultLayoutState };
|
||||
|
||||
const optionalNumber = (value) => value ? Number(value) : undefined;
|
||||
const selectedHost = () => hosts.find((host) => host.alias === elements.savedHost.value);
|
||||
@@ -1369,6 +1619,9 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
if (ideApi.onConnectProgress) {
|
||||
ideApi.onConnectProgress((message) => appendOutput(String(message)));
|
||||
}
|
||||
if (ideApi.openCode?.onEvent) {
|
||||
ideApi.openCode.onEvent((payload) => handleOpenCodeEvent(payload));
|
||||
}
|
||||
const formPayload = () => ({
|
||||
hostAlias: elements.alias.value.trim() || undefined,
|
||||
alias: elements.alias.value.trim() || undefined,
|
||||
@@ -1379,7 +1632,359 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
proxyJump: elements.proxyJump.value.trim() || undefined,
|
||||
});
|
||||
|
||||
const recordId = (record) => record?.id || record?.ID || record?.sessionID || record?.sessionId || record?.permissionID || record?.permissionId;
|
||||
const sessionTitle = (session) => session?.title || session?.name || recordId(session) || 'Session';
|
||||
const activeOpenCodeTab = () => {
|
||||
const tab = activeProjectTab();
|
||||
return tab?.openCode?.status === 'ready' ? tab : undefined;
|
||||
};
|
||||
const flattenText = (value) => {
|
||||
if (value === undefined || value === null) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (Array.isArray(value)) return value.map(flattenText).filter(Boolean).join('\\n');
|
||||
if (typeof value === 'object') {
|
||||
if (typeof value.text === 'string') return value.text;
|
||||
if (typeof value.content === 'string') return value.content;
|
||||
if (typeof value.output === 'string') return value.output;
|
||||
if (Array.isArray(value.parts)) return flattenText(value.parts);
|
||||
return JSON.stringify(value, undefined, 2);
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
const messageRole = (message) => {
|
||||
const info = message?.info || message;
|
||||
return String(info?.role || info?.type || info?.author || 'message').toLowerCase();
|
||||
};
|
||||
const messageText = (message) => {
|
||||
const parts = message?.parts || message?.info?.parts || [];
|
||||
if (Array.isArray(parts) && parts.length) {
|
||||
return parts.map((part) => {
|
||||
const type = part?.type ? String(part.type) : 'part';
|
||||
const text = flattenText(part?.text ?? part?.content ?? part?.output ?? part?.message ?? part);
|
||||
return type === 'text' ? text : '[' + type + ']\\n' + text;
|
||||
}).filter(Boolean).join('\\n\\n');
|
||||
}
|
||||
return flattenText(message?.text ?? message?.content ?? message?.message ?? message);
|
||||
};
|
||||
const eventPayload = (event) => {
|
||||
const data = event?.data;
|
||||
return data && typeof data === 'object' && data.properties && typeof data.properties === 'object' ? data.properties : data || {};
|
||||
};
|
||||
const permissionIdFromPayload = (data) => data.requestID || data.requestId || data.permissionID || data.permissionId || data.id || data.permission?.id;
|
||||
const sessionIdFromPayload = (data) => data.sessionID || data.sessionId || data.session?.id;
|
||||
const setOpenCodeDisabled = (disabled) => {
|
||||
elements.opencodeSession.disabled = disabled;
|
||||
elements.opencodePrompt.disabled = disabled;
|
||||
document.getElementById('sendOpenCodePrompt').disabled = disabled;
|
||||
document.getElementById('abortOpenCode').disabled = disabled;
|
||||
document.getElementById('newOpenCodeSession').disabled = disabled;
|
||||
document.getElementById('refreshOpenCode').disabled = disabled;
|
||||
};
|
||||
const renderOpenCodeSessions = () => {
|
||||
elements.opencodeSession.replaceChildren();
|
||||
const emptyOption = document.createElement('option');
|
||||
emptyOption.value = '';
|
||||
emptyOption.textContent = opencodeState.sessions.length ? 'Select session' : 'No sessions yet';
|
||||
elements.opencodeSession.appendChild(emptyOption);
|
||||
for (const session of opencodeState.sessions) {
|
||||
const option = document.createElement('option');
|
||||
option.value = recordId(session) || '';
|
||||
option.textContent = sessionTitle(session);
|
||||
elements.opencodeSession.appendChild(option);
|
||||
}
|
||||
elements.opencodeSession.value = opencodeState.activeSessionId || '';
|
||||
};
|
||||
const renderOpenCodeMessages = () => {
|
||||
elements.opencodeMessages.replaceChildren();
|
||||
if (!opencodeState.messages.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'empty';
|
||||
empty.style.display = 'block';
|
||||
empty.textContent = opencodeState.activeSessionId ? 'No messages in this session yet.' : 'Create or select a session to chat.';
|
||||
elements.opencodeMessages.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
for (const message of opencodeState.messages) {
|
||||
const role = messageRole(message);
|
||||
const container = document.createElement('div');
|
||||
container.className = 'opencode-message ' + (role.includes('user') ? 'user' : role.includes('assistant') ? 'assistant' : role.includes('tool') ? 'tool' : '');
|
||||
const title = document.createElement('div');
|
||||
title.className = 'opencode-message-title';
|
||||
title.textContent = role;
|
||||
const text = document.createElement('div');
|
||||
text.className = 'opencode-message-text';
|
||||
text.textContent = messageText(message) || '(empty)';
|
||||
container.append(title, text);
|
||||
elements.opencodeMessages.appendChild(container);
|
||||
}
|
||||
elements.opencodeMessages.scrollTop = elements.opencodeMessages.scrollHeight;
|
||||
};
|
||||
const renderOpenCodePermissions = () => {
|
||||
elements.opencodePermissions.replaceChildren();
|
||||
for (const permission of opencodeState.permissions) {
|
||||
const data = eventPayload(permission);
|
||||
const card = document.createElement('div');
|
||||
card.className = 'opencode-permission';
|
||||
const title = document.createElement('div');
|
||||
title.className = 'opencode-permission-title';
|
||||
title.textContent = data.title || data.permission || 'Permission requested';
|
||||
const body = document.createElement('pre');
|
||||
body.textContent = JSON.stringify(data, undefined, 2);
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'actions';
|
||||
const once = document.createElement('button');
|
||||
once.className = 'primary';
|
||||
once.textContent = 'Allow Once';
|
||||
once.addEventListener('click', () => respondOpenCodePermission(permission, 'once'));
|
||||
const always = document.createElement('button');
|
||||
always.className = 'secondary';
|
||||
always.textContent = 'Always';
|
||||
always.addEventListener('click', () => respondOpenCodePermission(permission, 'always'));
|
||||
const reject = document.createElement('button');
|
||||
reject.className = 'secondary';
|
||||
reject.textContent = 'Reject';
|
||||
reject.addEventListener('click', () => respondOpenCodePermission(permission, 'reject'));
|
||||
actions.append(once, always, reject);
|
||||
card.append(title, body, actions);
|
||||
elements.opencodePermissions.appendChild(card);
|
||||
}
|
||||
};
|
||||
const renderOpenCodePanel = () => {
|
||||
const tab = activeOpenCodeTab();
|
||||
renderActivityPanelState();
|
||||
renderOpenCodeSessions();
|
||||
renderOpenCodeMessages();
|
||||
renderOpenCodePermissions();
|
||||
setOpenCodeDisabled(!tab || opencodeState.loading || !opencodeState.activeSessionId);
|
||||
document.getElementById('newOpenCodeSession').disabled = !tab || opencodeState.loading;
|
||||
document.getElementById('refreshOpenCode').disabled = !tab || opencodeState.loading;
|
||||
};
|
||||
const resetOpenCodeState = (instanceId) => {
|
||||
opencodeState = {
|
||||
instanceId,
|
||||
sessions: [],
|
||||
activeSessionId: undefined,
|
||||
messages: [],
|
||||
permissions: [],
|
||||
loading: false,
|
||||
};
|
||||
};
|
||||
const refreshOpenCodeForActiveProject = async (autoCreate) => {
|
||||
const tab = activeOpenCodeTab();
|
||||
if (!tab) {
|
||||
resetOpenCodeState(undefined);
|
||||
renderOpenCodePanel();
|
||||
return;
|
||||
}
|
||||
if (opencodeState.instanceId !== tab.id) {
|
||||
resetOpenCodeState(tab.id);
|
||||
}
|
||||
opencodeState.loading = true;
|
||||
renderOpenCodePanel();
|
||||
try {
|
||||
const sessions = await ideApi.openCode.sessions({ instanceId: tab.id });
|
||||
opencodeState.sessions = Array.isArray(sessions) ? sessions : [];
|
||||
if (!opencodeState.activeSessionId || !opencodeState.sessions.some((session) => recordId(session) === opencodeState.activeSessionId)) {
|
||||
opencodeState.activeSessionId = recordId(opencodeState.sessions[0]);
|
||||
}
|
||||
if (autoCreate && !opencodeState.activeSessionId) {
|
||||
const session = await ideApi.openCode.createSession({ instanceId: tab.id, title: 'Git.Zone IDE Session' });
|
||||
opencodeState.sessions = [session].filter(Boolean);
|
||||
opencodeState.activeSessionId = recordId(session);
|
||||
}
|
||||
await loadOpenCodeMessages();
|
||||
} catch (error) {
|
||||
appendOutput(error.stack || String(error));
|
||||
} finally {
|
||||
opencodeState.loading = false;
|
||||
renderOpenCodePanel();
|
||||
}
|
||||
};
|
||||
const loadOpenCodeMessages = async () => {
|
||||
const tab = activeOpenCodeTab();
|
||||
if (!tab || !opencodeState.activeSessionId) {
|
||||
opencodeState.messages = [];
|
||||
return;
|
||||
}
|
||||
const messages = await ideApi.openCode.messages({
|
||||
instanceId: tab.id,
|
||||
sessionId: opencodeState.activeSessionId,
|
||||
limit: 100,
|
||||
});
|
||||
opencodeState.messages = Array.isArray(messages) ? messages : [];
|
||||
};
|
||||
const createOpenCodeSession = async () => {
|
||||
const tab = activeOpenCodeTab();
|
||||
if (!tab) return;
|
||||
opencodeState.loading = true;
|
||||
renderOpenCodePanel();
|
||||
try {
|
||||
const session = await ideApi.openCode.createSession({ instanceId: tab.id, title: 'Git.Zone IDE Session' });
|
||||
opencodeState.activeSessionId = recordId(session);
|
||||
await refreshOpenCodeForActiveProject(false);
|
||||
} catch (error) {
|
||||
appendOutput(error.stack || String(error));
|
||||
} finally {
|
||||
opencodeState.loading = false;
|
||||
renderOpenCodePanel();
|
||||
}
|
||||
};
|
||||
const sendOpenCodePrompt = async () => {
|
||||
const tab = activeOpenCodeTab();
|
||||
const text = elements.opencodePrompt.value.trim();
|
||||
if (!tab || !text) return;
|
||||
if (!opencodeState.activeSessionId) {
|
||||
await createOpenCodeSession();
|
||||
}
|
||||
if (!opencodeState.activeSessionId) return;
|
||||
elements.opencodePrompt.value = '';
|
||||
opencodeState.loading = true;
|
||||
renderOpenCodePanel();
|
||||
try {
|
||||
await ideApi.openCode.prompt({ instanceId: tab.id, sessionId: opencodeState.activeSessionId, text });
|
||||
await loadOpenCodeMessages();
|
||||
} catch (error) {
|
||||
appendOutput(error.stack || String(error));
|
||||
} finally {
|
||||
opencodeState.loading = false;
|
||||
renderOpenCodePanel();
|
||||
}
|
||||
};
|
||||
const abortOpenCode = async () => {
|
||||
const tab = activeOpenCodeTab();
|
||||
if (!tab || !opencodeState.activeSessionId) return;
|
||||
try {
|
||||
await ideApi.openCode.abort({ instanceId: tab.id, sessionId: opencodeState.activeSessionId });
|
||||
} catch (error) {
|
||||
appendOutput(error.stack || String(error));
|
||||
}
|
||||
};
|
||||
const respondOpenCodePermission = async (permission, response) => {
|
||||
const tab = activeOpenCodeTab();
|
||||
const data = eventPayload(permission);
|
||||
const permissionId = permissionIdFromPayload(data);
|
||||
const sessionId = sessionIdFromPayload(data) || opencodeState.activeSessionId;
|
||||
if (!tab || !permissionId || !sessionId) return;
|
||||
try {
|
||||
await ideApi.openCode.respondToPermission({ instanceId: tab.id, sessionId, permissionId, response, remember: response === 'always' });
|
||||
opencodeState.permissions = opencodeState.permissions.filter((candidate) => candidate !== permission);
|
||||
renderOpenCodePanel();
|
||||
} catch (error) {
|
||||
appendOutput(error.stack || String(error));
|
||||
}
|
||||
};
|
||||
const scheduleOpenCodeRefresh = () => {
|
||||
if (opencodeRefreshTimer) return;
|
||||
opencodeRefreshTimer = window.setTimeout(async () => {
|
||||
opencodeRefreshTimer = undefined;
|
||||
await refreshOpenCodeForActiveProject(false);
|
||||
}, 600);
|
||||
};
|
||||
const handleOpenCodeEvent = (payload) => {
|
||||
if (!payload || payload.instanceId !== opencodeState.instanceId) return;
|
||||
const event = payload.event || {};
|
||||
const type = String(event.type || '');
|
||||
if (type === 'permission.asked' || type === 'permission.updated') {
|
||||
const data = eventPayload(event);
|
||||
const permissionId = permissionIdFromPayload(data);
|
||||
opencodeState.permissions = permissionId
|
||||
? opencodeState.permissions.filter((candidate) => permissionIdFromPayload(eventPayload(candidate)) !== permissionId).concat(event)
|
||||
: opencodeState.permissions.concat(event);
|
||||
renderOpenCodePanel();
|
||||
return;
|
||||
}
|
||||
if (type === 'permission.replied') {
|
||||
const data = eventPayload(event);
|
||||
const permissionId = permissionIdFromPayload(data);
|
||||
opencodeState.permissions = permissionId
|
||||
? opencodeState.permissions.filter((candidate) => {
|
||||
return permissionIdFromPayload(eventPayload(candidate)) !== permissionId;
|
||||
})
|
||||
: [];
|
||||
renderOpenCodePanel();
|
||||
return;
|
||||
}
|
||||
if (type.startsWith('message.') || type.startsWith('session.') || type.startsWith('tool.') || type === 'server.connected') {
|
||||
scheduleOpenCodeRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
const activeProjectTab = () => openTabs.find((candidate) => candidate.projectId === activeTabId);
|
||||
const isTheiaFrameShowing = () => Boolean(activeProjectTab() && !elements.frameView.hidden && projectFrameDomReady && elements.projectFrame.src);
|
||||
const clampNumber = (value, min, max) => Math.min(max, Math.max(min, value));
|
||||
const loadLayoutState = () => {
|
||||
try {
|
||||
const stored = JSON.parse(localStorage.getItem(layoutStorageKey) || '{}');
|
||||
layoutState = {
|
||||
sidebarWidth: clampNumber(Number(stored.sidebarWidth) || defaultLayoutState.sidebarWidth, 180, 560),
|
||||
panelHeight: clampNumber(Number(stored.panelHeight) || defaultLayoutState.panelHeight, 52, 360),
|
||||
mainExpanded: Boolean(stored.mainExpanded),
|
||||
};
|
||||
} catch {
|
||||
layoutState = { ...defaultLayoutState };
|
||||
}
|
||||
};
|
||||
const saveLayoutState = () => {
|
||||
try {
|
||||
localStorage.setItem(layoutStorageKey, JSON.stringify(layoutState));
|
||||
} catch {}
|
||||
};
|
||||
const applyLayoutState = () => {
|
||||
elements.workbench.style.setProperty('--sidebar-width', layoutState.sidebarWidth + 'px');
|
||||
elements.workbench.style.setProperty('--panel-height', layoutState.panelHeight + 'px');
|
||||
elements.workbench.classList.toggle('main-expanded', layoutState.mainExpanded);
|
||||
};
|
||||
const syncProjectFrameLayout = () => {
|
||||
if (elements.frameView.hidden || !projectFrameDomReady) return;
|
||||
if (typeof elements.projectFrame.executeJavaScript === 'function') {
|
||||
try {
|
||||
elements.projectFrame.executeJavaScript('window.dispatchEvent(new Event("resize"));', false).catch(() => undefined);
|
||||
} catch {
|
||||
projectFrameDomReady = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
const scheduleProjectFrameLayoutSync = () => {
|
||||
for (const delay of [0, 50, 200]) {
|
||||
window.setTimeout(() => requestAnimationFrame(syncProjectFrameLayout), delay);
|
||||
}
|
||||
};
|
||||
const renderMainPanelControls = () => {
|
||||
const canExpandTheia = isTheiaFrameShowing();
|
||||
elements.expandMainPanel.disabled = !canExpandTheia;
|
||||
elements.expandMainPanel.textContent = layoutState.mainExpanded ? 'Restore Shell' : 'Expand Theia';
|
||||
elements.restoreMainPanelFooter.disabled = !layoutState.mainExpanded;
|
||||
};
|
||||
const setMainExpanded = (expanded) => {
|
||||
if (expanded && !isTheiaFrameShowing()) return;
|
||||
layoutState.mainExpanded = Boolean(expanded);
|
||||
saveLayoutState();
|
||||
applyLayoutState();
|
||||
renderMainPanelControls();
|
||||
scheduleProjectFrameLayoutSync();
|
||||
};
|
||||
const beginDragResize = (event, splitter, axis, startValue, updateValue) => {
|
||||
if (layoutState.mainExpanded) return;
|
||||
event.preventDefault();
|
||||
const startPosition = axis === 'x' ? event.clientX : event.clientY;
|
||||
splitter.classList.add('active');
|
||||
document.body.classList.add('resizing');
|
||||
const onPointerMove = (moveEvent) => {
|
||||
const currentPosition = axis === 'x' ? moveEvent.clientX : moveEvent.clientY;
|
||||
updateValue(startValue, currentPosition - startPosition);
|
||||
applyLayoutState();
|
||||
syncProjectFrameLayout();
|
||||
};
|
||||
const onPointerUp = () => {
|
||||
splitter.classList.remove('active');
|
||||
document.body.classList.remove('resizing');
|
||||
document.removeEventListener('pointermove', onPointerMove);
|
||||
document.removeEventListener('pointerup', onPointerUp);
|
||||
saveLayoutState();
|
||||
};
|
||||
document.addEventListener('pointermove', onPointerMove);
|
||||
document.addEventListener('pointerup', onPointerUp, { once: true });
|
||||
};
|
||||
const renderActivityPanelState = () => {
|
||||
elements.explorerHostStatus.textContent = connection
|
||||
? 'Connected to ' + connection.hostAlias + '. ' + projects.length + ' remote project' + (projects.length === 1 ? '' : 's') + ' registered.'
|
||||
@@ -1520,17 +2125,27 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
elements.dashboardView.hidden = tabId !== 'dashboard' || !connection;
|
||||
elements.frameView.hidden = !tab;
|
||||
if (tab) {
|
||||
projectFrameDomReady = false;
|
||||
elements.projectFrame.src = tab.url;
|
||||
elements.mainHeader.textContent = tab.title + ' - ' + tab.path;
|
||||
elements.mainHeaderTitle.textContent = tab.title + ' - ' + tab.path;
|
||||
scheduleProjectFrameLayoutSync();
|
||||
} else if (connection) {
|
||||
elements.projectFrame.removeAttribute('src');
|
||||
elements.mainHeader.textContent = 'Remote Project Dashboard';
|
||||
elements.mainHeaderTitle.textContent = 'Remote Project Dashboard';
|
||||
if (layoutState.mainExpanded) {
|
||||
setMainExpanded(false);
|
||||
}
|
||||
} else {
|
||||
elements.projectFrame.removeAttribute('src');
|
||||
elements.mainHeader.textContent = 'Connect to SSH Host';
|
||||
elements.mainHeaderTitle.textContent = 'Connect to SSH Host';
|
||||
if (layoutState.mainExpanded) {
|
||||
setMainExpanded(false);
|
||||
}
|
||||
}
|
||||
renderMainPanelControls();
|
||||
renderTabs();
|
||||
renderProjects();
|
||||
void refreshOpenCodeForActiveProject(!!tab);
|
||||
};
|
||||
|
||||
const closeTab = (tabId) => {
|
||||
@@ -1608,6 +2223,71 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
activateActivityView('hosts');
|
||||
}
|
||||
});
|
||||
elements.expandMainPanel.addEventListener('click', () => {
|
||||
setMainExpanded(!layoutState.mainExpanded);
|
||||
});
|
||||
elements.restoreMainPanelFooter.addEventListener('click', () => {
|
||||
setMainExpanded(false);
|
||||
});
|
||||
elements.projectFrame.addEventListener('did-stop-loading', () => {
|
||||
projectFrameDomReady = true;
|
||||
scheduleProjectFrameLayoutSync();
|
||||
renderMainPanelControls();
|
||||
});
|
||||
elements.projectFrame.addEventListener('dom-ready', () => {
|
||||
projectFrameDomReady = true;
|
||||
scheduleProjectFrameLayoutSync();
|
||||
renderMainPanelControls();
|
||||
});
|
||||
window.addEventListener('resize', () => {
|
||||
scheduleProjectFrameLayoutSync();
|
||||
});
|
||||
if (typeof ResizeObserver === 'function') {
|
||||
new ResizeObserver(() => scheduleProjectFrameLayoutSync()).observe(elements.frameView);
|
||||
}
|
||||
elements.sidebarResizer.addEventListener('pointerdown', (event) => {
|
||||
beginDragResize(event, elements.sidebarResizer, 'x', layoutState.sidebarWidth, (startWidth, delta) => {
|
||||
layoutState.sidebarWidth = clampNumber(startWidth + delta, 180, Math.max(220, window.innerWidth - 420));
|
||||
});
|
||||
scheduleProjectFrameLayoutSync();
|
||||
});
|
||||
elements.panelResizer.addEventListener('pointerdown', (event) => {
|
||||
beginDragResize(event, elements.panelResizer, 'y', layoutState.panelHeight, (startHeight, delta) => {
|
||||
layoutState.panelHeight = clampNumber(startHeight - delta, 52, Math.max(80, window.innerHeight - 220));
|
||||
});
|
||||
scheduleProjectFrameLayoutSync();
|
||||
});
|
||||
elements.opencodeSession.addEventListener('change', async () => {
|
||||
opencodeState.activeSessionId = elements.opencodeSession.value || undefined;
|
||||
opencodeState.loading = true;
|
||||
renderOpenCodePanel();
|
||||
try {
|
||||
await loadOpenCodeMessages();
|
||||
} catch (error) {
|
||||
appendOutput(error.stack || String(error));
|
||||
} finally {
|
||||
opencodeState.loading = false;
|
||||
renderOpenCodePanel();
|
||||
}
|
||||
});
|
||||
document.getElementById('newOpenCodeSession').addEventListener('click', () => {
|
||||
void createOpenCodeSession();
|
||||
});
|
||||
document.getElementById('refreshOpenCode').addEventListener('click', () => {
|
||||
void refreshOpenCodeForActiveProject(false);
|
||||
});
|
||||
document.getElementById('sendOpenCodePrompt').addEventListener('click', () => {
|
||||
void sendOpenCodePrompt();
|
||||
});
|
||||
document.getElementById('abortOpenCode').addEventListener('click', () => {
|
||||
void abortOpenCode();
|
||||
});
|
||||
elements.opencodePrompt.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
void sendOpenCodePrompt();
|
||||
}
|
||||
});
|
||||
document.getElementById('clearOutput').addEventListener('click', () => {
|
||||
elements.output.textContent = '';
|
||||
elements.statusText.textContent = 'Ready';
|
||||
@@ -1684,6 +2364,13 @@ const renderLauncherHtml = () => `<!doctype html>
|
||||
}
|
||||
});
|
||||
|
||||
loadLayoutState();
|
||||
if (layoutState.mainExpanded && !activeProjectTab()) {
|
||||
layoutState.mainExpanded = false;
|
||||
saveLayoutState();
|
||||
}
|
||||
applyLayoutState();
|
||||
renderMainPanelControls();
|
||||
renderTabs();
|
||||
renderProjects();
|
||||
activateActivityView('hosts');
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"watch": "pnpm run rebuild && theia build --watch --mode development"
|
||||
},
|
||||
"dependencies": {
|
||||
"@git.zone/ide-extension-opencode": "workspace:*",
|
||||
"@git.zone/ide-extension-product": "workspace:*",
|
||||
"@git.zone/ide-extension-remote": "workspace:*",
|
||||
"@theia/core": "1.71.0",
|
||||
|
||||
@@ -16,10 +16,6 @@ globalThis.extensionInfo = [
|
||||
"name": "@theia/core",
|
||||
"version": "1.71.0"
|
||||
},
|
||||
{
|
||||
"name": "@git.zone/ide-extension-opencode",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"name": "@git.zone/ide-extension-product",
|
||||
"version": "0.1.0"
|
||||
|
||||
@@ -61,7 +61,6 @@ module.exports = async (port, host, argv) => {
|
||||
await load(require('@theia/core/lib/node/i18n/i18n-backend-module'));
|
||||
await load(require('@theia/core/lib/node/hosting/backend-hosting-module'));
|
||||
await load(require('@theia/core/lib/node/request/backend-request-module'));
|
||||
await load(require('@git.zone/ide-extension-opencode/lib/node/gitzone-opencode-backend-module'));
|
||||
await load(require('@git.zone/ide-extension-remote/lib/node/gitzone-remote-backend-module'));
|
||||
await load(require('@theia/editor/lib/node/editor-backend-module'));
|
||||
await load(require('@theia/filesystem/lib/node/filesystem-backend-module'));
|
||||
|
||||
@@ -81,7 +81,6 @@ module.exports = (async () => {
|
||||
await load(container, import('@theia/core/lib/browser/window/browser-window-module'));
|
||||
await load(container, import('@theia/core/lib/browser/keyboard/browser-keyboard-module'));
|
||||
await load(container, import('@theia/core/lib/browser/request/browser-request-module'));
|
||||
await load(container, import('@git.zone/ide-extension-opencode/lib/browser/gitzone-opencode-frontend-module'));
|
||||
await load(container, import('@git.zone/ide-extension-product/lib/browser/gitzone-product-frontend-module'));
|
||||
await load(container, import('@git.zone/ide-extension-remote/lib/browser/gitzone-remote-frontend-module'));
|
||||
await load(container, import('@theia/variable-resolver/lib/browser/variable-resolver-frontend-module'));
|
||||
|
||||
+4
-2
@@ -12,8 +12,10 @@ The remote server is a Theia browser application installed under `~/.git.zone/id
|
||||
|
||||
## OpenCode Integration
|
||||
|
||||
The `@git.zone/ide-extension-opencode` backend starts or connects to `opencode serve` in the remote workspace. The Theia frontend talks to that backend over Theia JSON-RPC. The browser never receives the OpenCode server password and never talks to OpenCode HTTP directly.
|
||||
The Electron shell starts a local `opencode serve` runtime for each opened project. The native shell owns OpenCode sessions, messages, permissions, and chat UI. Git.Zone writes OpenCode tool overrides into that local runtime so shell, file, search, and patch operations are forwarded over SSH and execute in the selected remote workspace.
|
||||
|
||||
Theia does not host OpenCode chat or OpenCode sessions. Any future Theia-facing tool should be an intellisense/context provider for editor and language-server state, not another OpenCode transport.
|
||||
|
||||
## Execution Boundary
|
||||
|
||||
Files, terminal commands, Git, language servers, builds, tests, Git.Zone commands, and OpenCode tools all run on the remote SSH host. The local machine only displays UI and maintains SSH transport.
|
||||
Files, terminal commands, Git, language servers, builds, tests, Git.Zone commands, and bridged OpenCode code tools all run on the remote SSH host. The local machine displays UI, runs the local OpenCode provider process, and maintains SSH transport.
|
||||
|
||||
@@ -53,6 +53,13 @@ export interface IOpenCodeEvent {
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface IRendererOpenCodeEvent {
|
||||
type: string;
|
||||
id?: string;
|
||||
retry?: number;
|
||||
data?: unknown;
|
||||
}
|
||||
|
||||
export class OpenCodeHttpError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
@@ -476,6 +483,13 @@ export const parseServerSentEvent = (raw: string): IOpenCodeEvent | undefined =>
|
||||
return { type, id, retry, data, raw };
|
||||
};
|
||||
|
||||
export const sanitizeOpenCodeEventForRenderer = (event: IOpenCodeEvent): IRendererOpenCodeEvent => ({
|
||||
type: event.type,
|
||||
id: event.id,
|
||||
retry: event.retry,
|
||||
data: event.data,
|
||||
});
|
||||
|
||||
const parseJsonIfPossible = (value: string) => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
|
||||
Generated
+118
-12
@@ -47,6 +47,9 @@ importers:
|
||||
electron:
|
||||
specifier: ^42.0.1
|
||||
version: 42.0.1
|
||||
opencode-ai:
|
||||
specifier: 1.14.48
|
||||
version: 1.14.48
|
||||
devDependencies:
|
||||
electron-builder:
|
||||
specifier: ^26.8.1
|
||||
@@ -54,9 +57,6 @@ importers:
|
||||
|
||||
applications/remote-theia:
|
||||
dependencies:
|
||||
'@git.zone/ide-extension-opencode':
|
||||
specifier: workspace:*
|
||||
version: link:../../theia-extensions/gitzone-opencode
|
||||
'@git.zone/ide-extension-product':
|
||||
specifier: workspace:*
|
||||
version: link:../../theia-extensions/gitzone-product
|
||||
@@ -147,15 +147,6 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../protocol
|
||||
|
||||
theia-extensions/gitzone-opencode:
|
||||
dependencies:
|
||||
'@git.zone/ide-opencode-bridge':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/opencode-bridge
|
||||
'@theia/core':
|
||||
specifier: 1.71.0
|
||||
version: 1.71.0
|
||||
|
||||
theia-extensions/gitzone-product:
|
||||
dependencies:
|
||||
'@theia/core':
|
||||
@@ -5621,6 +5612,70 @@ packages:
|
||||
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
opencode-ai@1.14.48:
|
||||
resolution: {integrity: sha512-65ogCJao8ujS4gFP64QuStv/h+qII30xNiARGsu9ai8Uoj+a/m6gz8yhNC9oz1dZm7PF6n15iIpB/LzCXBrhZQ==}
|
||||
hasBin: true
|
||||
|
||||
opencode-darwin-arm64@1.14.48:
|
||||
resolution: {integrity: sha512-QF05WtuPVnXCkvBHdDSuIhHVgYb/f10HO2mRlUbbBWStbPedLWwOKC4YKUhpsrLAVBEFLcL/xh7RtPM70+0TZQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-darwin-x64-baseline@1.14.48:
|
||||
resolution: {integrity: sha512-F3p1GRuPR+HKDVIGn/uS9N0685BQ1msOOZsYHiAc4Qe22ZhZGrnMveg4CZwDseAQxoFL5n29i9J7iw/QORmO/g==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-darwin-x64@1.14.48:
|
||||
resolution: {integrity: sha512-Yx0/opXz7cdne1Xi77TNTIwWpYSwv+T32PdEcyqrcgEx0NR5f4S4kEsydmKsV7lzFRSkqRXniwvLj+8BPJEwNA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-linux-arm64-musl@1.14.48:
|
||||
resolution: {integrity: sha512-w+wY4ROlq6lpL+SPYLYHBA6k+twVU9kurBGt+6irf4ZA5BCkhva7mgRET4NBxheUKEPSEQknq3Umi2Ltab4G+w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-arm64@1.14.48:
|
||||
resolution: {integrity: sha512-DPZUi4IErlM/oXbNQKOiAqlmIij322sGUewNkvn+UptZtoqMVjtM8UQc6RkfoSQtAhk9zfdXTRjmajXpSSPJTA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-baseline-musl@1.14.48:
|
||||
resolution: {integrity: sha512-92icmlOdDHLm0ityztGHGlgv6OiqQTdXYZRsPUs17IcHlkxLUT2b5O8HUpHk/xtMwyJEleYpV3E5EQz4vP5HVw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-baseline@1.14.48:
|
||||
resolution: {integrity: sha512-I8KoLTSpCYkChdosh2iFfXO0zwPuAp7ZbPZyzPN9R0jTW95EstO5qYPTLs8F+sZJiM+oUdxOEpgIVkGF6JMzAg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-musl@1.14.48:
|
||||
resolution: {integrity: sha512-h2QPam8t++TEphKm8zroxfuDZk8oAF40aKFxH753XrINfCAyRikOb/hNfLJd0J3oAcolxMbFHjr6c35zyJbzHQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64@1.14.48:
|
||||
resolution: {integrity: sha512-WLAABtQD2Zr1+7zfGRcNYdAKYtr2EuMY6RwjoNRAqHzQslOlItZhDpwdqg03YFe0zrQhg0IlK6iGuplF1ertdw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-windows-arm64@1.14.48:
|
||||
resolution: {integrity: sha512-WqcJHoxI3e06So6g83tKuyAGYXHp++/urLtojw78HDhV0RlO9Sthbv19YRu9gqp/GLYNqNChU5JLjVYFmoegkg==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
opencode-windows-x64-baseline@1.14.48:
|
||||
resolution: {integrity: sha512-3RC6Pq/9yJn4jnWjOdDbJPJgIoMXRmWr4AytNeBwi7NifTBGmGnqxU+mFRWYJxJmCIvyWxHw/iVfEUtoej7/JQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
opencode-windows-x64@1.14.48:
|
||||
resolution: {integrity: sha512-dq5glnTtVjd5sJJcQWpcSHTHj4x9hKsBTFSvGV7tV+FoBTQFsnPtCFqt6Wmo/s0D0VYQsMz/syY1YGtrA+O3pA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
opener@1.5.2:
|
||||
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
|
||||
hasBin: true
|
||||
@@ -15315,6 +15370,57 @@ snapshots:
|
||||
is-docker: 2.2.1
|
||||
is-wsl: 2.2.0
|
||||
|
||||
opencode-ai@1.14.48:
|
||||
optionalDependencies:
|
||||
opencode-darwin-arm64: 1.14.48
|
||||
opencode-darwin-x64: 1.14.48
|
||||
opencode-darwin-x64-baseline: 1.14.48
|
||||
opencode-linux-arm64: 1.14.48
|
||||
opencode-linux-arm64-musl: 1.14.48
|
||||
opencode-linux-x64: 1.14.48
|
||||
opencode-linux-x64-baseline: 1.14.48
|
||||
opencode-linux-x64-baseline-musl: 1.14.48
|
||||
opencode-linux-x64-musl: 1.14.48
|
||||
opencode-windows-arm64: 1.14.48
|
||||
opencode-windows-x64: 1.14.48
|
||||
opencode-windows-x64-baseline: 1.14.48
|
||||
|
||||
opencode-darwin-arm64@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-darwin-x64-baseline@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-darwin-x64@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-linux-arm64-musl@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-linux-arm64@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-baseline-musl@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-baseline@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-musl@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-windows-arm64@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-windows-x64-baseline@1.14.48:
|
||||
optional: true
|
||||
|
||||
opencode-windows-x64@1.14.48:
|
||||
optional: true
|
||||
|
||||
opener@1.5.2: {}
|
||||
|
||||
opfs-worker@1.3.1(typescript@6.0.3):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Git.Zone IDE
|
||||
|
||||
Git.Zone IDE is a remote-first desktop IDE based on Eclipse Theia, Electron, SSH, and OpenCode server.
|
||||
Git.Zone IDE is a remote-first desktop IDE based on Eclipse Theia, Electron, SSH, and OpenCode.
|
||||
|
||||
The local Electron shell manages SSH sessions and tunnels. The remote host runs the Theia backend and OpenCode server inside the selected workspace, so files, terminals, Git, language servers, tests, and AI agent actions all execute where the code lives.
|
||||
The local Electron shell manages SSH sessions, tunnels, and the native OpenCode chat runtime. The remote host runs the Theia backend inside the selected workspace; OpenCode code tools are overridden so file, shell, search, and patch actions execute remotely over SSH where the code lives.
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
+3
-2
@@ -3,5 +3,6 @@
|
||||
1. Build a local Electron shell that reads SSH targets, starts SSH tunnels, and opens the remote Theia frontend.
|
||||
2. Install a versioned Theia server bundle into `~/.git.zone/ide-server/<version>` on remote SSH hosts.
|
||||
3. Run the Theia backend and OpenCode server on the remote host, bound to `127.0.0.1`.
|
||||
4. Expose OpenCode through a Theia backend service, not directly to the Electron renderer.
|
||||
5. Render OpenCode sessions, messages, permissions, diffs, todos, and Git.Zone commands inside Theia.
|
||||
4. Run OpenCode in the native Electron shell with tool overrides that execute against the selected remote project over SSH.
|
||||
5. Render OpenCode sessions, messages, permissions, diffs, todos, and Git.Zone commands in the native shell UI.
|
||||
6. Keep Theia integration scoped to editor context and future intellisense data for OpenCode tools.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { parseServerSentEvent } from '../packages/opencode-bridge/ts/index.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { parseServerSentEvent, sanitizeOpenCodeEventForRenderer } from '../packages/opencode-bridge/ts/index.js';
|
||||
|
||||
tap.test('should parse named opencode sse events', async () => {
|
||||
const event = parseServerSentEvent('id: 1\nevent: server.connected\ndata: {"type":"server.connected"}\n');
|
||||
@@ -14,4 +15,26 @@ tap.test('should infer opencode event type from json data', async () => {
|
||||
expect(event!.data).toEqual({ type: 'session.updated', properties: { id: 'abc' } });
|
||||
});
|
||||
|
||||
tap.test('should sanitize opencode events for renderer delivery', async () => {
|
||||
const event = parseServerSentEvent('id: 2\nretry: 1000\nevent: permission.asked\ndata: {"permissionID":"perm-1"}\n')!;
|
||||
const sanitized = sanitizeOpenCodeEventForRenderer(event);
|
||||
|
||||
expect(sanitized).toEqual({
|
||||
type: 'permission.asked',
|
||||
id: '2',
|
||||
retry: 1000,
|
||||
data: { permissionID: 'perm-1' },
|
||||
});
|
||||
expect(Object.prototype.hasOwnProperty.call(sanitized, 'raw')).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should keep electron shell opencode resolution IDE-local', async () => {
|
||||
const source = await fs.readFile(new URL('../applications/electron-shell/ts/main.ts', import.meta.url), 'utf8');
|
||||
|
||||
expect(source.includes('process.env.OPENCODE_BINARY')).toEqual(false);
|
||||
expect(source.includes("'.opencode', 'bin', 'opencode'")).toEqual(false);
|
||||
expect(source.includes('/usr/local/bin/opencode')).toEqual(false);
|
||||
expect(source.includes('/usr/bin/opencode')).toEqual(false);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||
import { type IGitZoneOpenCodeServer } from '../common/gitzone-opencode-protocol.js';
|
||||
export declare const GitZoneOpenCodeHealthCommand: {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
export declare const GitZoneOpenCodeNewSessionCommand: {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
export declare class GitZoneOpenCodeContribution implements CommandContribution, MenuContribution {
|
||||
protected readonly openCodeServer: IGitZoneOpenCodeServer;
|
||||
protected readonly messages: MessageService;
|
||||
registerCommands(registry: CommandRegistry): void;
|
||||
registerMenus(menus: MenuModelRegistry): void;
|
||||
}
|
||||
declare const _default: ContainerModule;
|
||||
export default _default;
|
||||
//# sourceMappingURL=gitzone-opencode-frontend-module.d.ts.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/gitzone-opencode-frontend-module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oDAAoD,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAsB,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,eAAO,MAAM,4BAA4B;;;CAGxC,CAAC;AAEF,eAAO,MAAM,gCAAgC;;;CAG5C,CAAC;AAEF,qBACa,2BAA4B,YAAW,mBAAmB,EAAE,gBAAgB;IAEvF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAG,sBAAsB,CAAC;IAG3D,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,cAAc,CAAC;IAE7C,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAejD,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;CAU9C;;AAQD,wBAWG"}
|
||||
@@ -1,83 +0,0 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GitZoneOpenCodeContribution = exports.GitZoneOpenCodeNewSessionCommand = exports.GitZoneOpenCodeHealthCommand = void 0;
|
||||
const common_menus_js_1 = require("@theia/core/lib/browser/common-menus.js");
|
||||
const ws_connection_provider_js_1 = require("@theia/core/lib/browser/messaging/ws-connection-provider.js");
|
||||
const command_js_1 = require("@theia/core/lib/common/command.js");
|
||||
const menu_model_registry_js_1 = require("@theia/core/lib/common/menu/menu-model-registry.js");
|
||||
const message_service_js_1 = require("@theia/core/lib/common/message-service.js");
|
||||
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||
const gitzone_opencode_protocol_js_1 = require("../common/gitzone-opencode-protocol.js");
|
||||
exports.GitZoneOpenCodeHealthCommand = {
|
||||
id: 'gitzone.opencode.health',
|
||||
label: 'OpenCode: Check Health',
|
||||
};
|
||||
exports.GitZoneOpenCodeNewSessionCommand = {
|
||||
id: 'gitzone.opencode.newSession',
|
||||
label: 'OpenCode: New Session',
|
||||
};
|
||||
let GitZoneOpenCodeContribution = class GitZoneOpenCodeContribution {
|
||||
openCodeServer;
|
||||
messages;
|
||||
registerCommands(registry) {
|
||||
registry.registerCommand(exports.GitZoneOpenCodeHealthCommand, {
|
||||
execute: async () => {
|
||||
const health = await this.openCodeServer.health();
|
||||
await this.messages.info(`OpenCode health: ${JSON.stringify(health)}`);
|
||||
},
|
||||
});
|
||||
registry.registerCommand(exports.GitZoneOpenCodeNewSessionCommand, {
|
||||
execute: async () => {
|
||||
const session = await this.openCodeServer.createSession('Git.Zone IDE Session');
|
||||
await this.messages.info(`OpenCode session created: ${JSON.stringify(session)}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
registerMenus(menus) {
|
||||
menus.registerMenuAction(common_menus_js_1.CommonMenus.VIEW_VIEWS, {
|
||||
commandId: exports.GitZoneOpenCodeHealthCommand.id,
|
||||
label: exports.GitZoneOpenCodeHealthCommand.label,
|
||||
});
|
||||
menus.registerMenuAction(common_menus_js_1.CommonMenus.VIEW_VIEWS, {
|
||||
commandId: exports.GitZoneOpenCodeNewSessionCommand.id,
|
||||
label: exports.GitZoneOpenCodeNewSessionCommand.label,
|
||||
});
|
||||
}
|
||||
};
|
||||
exports.GitZoneOpenCodeContribution = GitZoneOpenCodeContribution;
|
||||
__decorate([
|
||||
(0, index_js_1.inject)(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer),
|
||||
__metadata("design:type", Object)
|
||||
], GitZoneOpenCodeContribution.prototype, "openCodeServer", void 0);
|
||||
__decorate([
|
||||
(0, index_js_1.inject)(message_service_js_1.MessageService),
|
||||
__metadata("design:type", message_service_js_1.MessageService)
|
||||
], GitZoneOpenCodeContribution.prototype, "messages", void 0);
|
||||
exports.GitZoneOpenCodeContribution = GitZoneOpenCodeContribution = __decorate([
|
||||
(0, index_js_1.injectable)()
|
||||
], GitZoneOpenCodeContribution);
|
||||
const openCodeClient = {
|
||||
onOpenCodeEvent: (event) => {
|
||||
globalThis.dispatchEvent(new CustomEvent('gitzone-opencode-event', { detail: event }));
|
||||
},
|
||||
};
|
||||
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||
bind(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer)
|
||||
.toDynamicValue((context) => context.container
|
||||
.get(ws_connection_provider_js_1.WebSocketConnectionProvider)
|
||||
.createProxy(gitzone_opencode_protocol_js_1.gitZoneOpenCodePath, openCodeClient))
|
||||
.inSingletonScope();
|
||||
bind(GitZoneOpenCodeContribution).toSelf().inSingletonScope();
|
||||
bind(command_js_1.CommandContribution).toService(GitZoneOpenCodeContribution);
|
||||
bind(menu_model_registry_js_1.MenuContribution).toService(GitZoneOpenCodeContribution);
|
||||
});
|
||||
//# sourceMappingURL=gitzone-opencode-frontend-module.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-frontend-module.js","sourceRoot":"","sources":["../../src/browser/gitzone-opencode-frontend-module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6EAAsE;AACtE,2GAA0G;AAC1G,kEAAyF;AACzF,+FAAyG;AACzG,kFAA2E;AAC3E,oEAA4F;AAC5F,yFAKgD;AAEnC,QAAA,4BAA4B,GAAG;IAC1C,EAAE,EAAE,yBAAyB;IAC7B,KAAK,EAAE,wBAAwB;CAChC,CAAC;AAEW,QAAA,gCAAgC,GAAG;IAC9C,EAAE,EAAE,6BAA6B;IACjC,KAAK,EAAE,uBAAuB;CAC/B,CAAC;AAGK,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;IAEnB,cAAc,CAA0B;IAGxC,QAAQ,CAAkB;IAE7C,gBAAgB,CAAC,QAAyB;QACxC,QAAQ,CAAC,eAAe,CAAC,oCAA4B,EAAE;YACrD,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC;gBAClD,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzE,CAAC;SACF,CAAC,CAAC;QACH,QAAQ,CAAC,eAAe,CAAC,wCAAgC,EAAE;YACzD,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;gBAChF,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,KAAwB;QACpC,KAAK,CAAC,kBAAkB,CAAC,6BAAW,CAAC,UAAU,EAAE;YAC/C,SAAS,EAAE,oCAA4B,CAAC,EAAE;YAC1C,KAAK,EAAE,oCAA4B,CAAC,KAAK;SAC1C,CAAC,CAAC;QACH,KAAK,CAAC,kBAAkB,CAAC,6BAAW,CAAC,UAAU,EAAE;YAC/C,SAAS,EAAE,wCAAgC,CAAC,EAAE;YAC9C,KAAK,EAAE,wCAAgC,CAAC,KAAK;SAC9C,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAhCY,kEAA2B;AAEnB;IADlB,IAAA,iBAAM,EAAC,oDAAqB,CAAC;;mEAC6B;AAGxC;IADlB,IAAA,iBAAM,EAAC,mCAAc,CAAC;8BACO,mCAAc;6DAAC;sCALlC,2BAA2B;IADvC,IAAA,qBAAU,GAAE;GACA,2BAA2B,CAgCvC;AAED,MAAM,cAAc,GAA2B;IAC7C,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QACzB,UAAU,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,wBAAwB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;CACF,CAAC;AAEF,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,oDAAqB,CAAC;SACxB,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,OAAO,CAAC,SAAS;SACd,GAAG,CAAC,uDAA2B,CAAC;SAChC,WAAW,CAAyB,kDAAmB,EAAE,cAAc,CAAC,CAC5E;SACA,gBAAgB,EAAE,CAAC;IACtB,IAAI,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC9D,IAAI,CAAC,gCAAmB,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;IACjE,IAAI,CAAC,yCAAgB,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC"}
|
||||
@@ -1,51 +0,0 @@
|
||||
export declare const gitZoneOpenCodePath = "/services/git-zone/opencode";
|
||||
export declare const GitZoneOpenCodeServer: unique symbol;
|
||||
export interface IGitZoneOpenCodeConnectionInfo {
|
||||
baseUrl: string;
|
||||
port: number;
|
||||
workspacePath: string;
|
||||
autoStart: boolean;
|
||||
}
|
||||
export interface IGitZoneOpenCodeEvent {
|
||||
type: string;
|
||||
id?: string;
|
||||
retry?: number;
|
||||
data?: unknown;
|
||||
raw: string;
|
||||
}
|
||||
export interface IGitZoneOpenCodeClient {
|
||||
onOpenCodeEvent(event: IGitZoneOpenCodeEvent): void;
|
||||
}
|
||||
export interface IGitZoneOpenCodePromptBody {
|
||||
messageID?: string;
|
||||
model?: {
|
||||
providerID: string;
|
||||
modelID: string;
|
||||
};
|
||||
agent?: string;
|
||||
noReply?: boolean;
|
||||
system?: string;
|
||||
tools?: Record<string, boolean>;
|
||||
parts: Array<{
|
||||
type: string;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
}
|
||||
export interface IGitZoneOpenCodeServer {
|
||||
setClient(client: IGitZoneOpenCodeClient | undefined): void;
|
||||
getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo>;
|
||||
health(): Promise<unknown>;
|
||||
providers(): Promise<unknown>;
|
||||
agents(): Promise<unknown>;
|
||||
sessions(): Promise<unknown>;
|
||||
createSession(title?: string): Promise<unknown>;
|
||||
messages(sessionId: string, limit?: number): Promise<unknown>;
|
||||
prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown>;
|
||||
promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void>;
|
||||
command(sessionId: string, command: string, commandArguments?: string): Promise<unknown>;
|
||||
abort(sessionId: string): Promise<unknown>;
|
||||
diff(sessionId: string, messageId?: string): Promise<unknown>;
|
||||
todo(sessionId: string): Promise<unknown>;
|
||||
respondToPermission(sessionId: string, permissionId: string, response: string, remember?: boolean): Promise<unknown>;
|
||||
}
|
||||
//# sourceMappingURL=gitzone-opencode-protocol.d.ts.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-protocol.d.ts","sourceRoot":"","sources":["../../src/common/gitzone-opencode-protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE,eAAO,MAAM,qBAAqB,eAAkC,CAAC;AAErE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAsB;IACrC,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,SAAS,GAAG,IAAI,CAAC;IAC5D,iBAAiB,IAAI,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzF,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtH"}
|
||||
@@ -1,6 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GitZoneOpenCodeServer = exports.gitZoneOpenCodePath = void 0;
|
||||
exports.gitZoneOpenCodePath = '/services/git-zone/opencode';
|
||||
exports.GitZoneOpenCodeServer = Symbol('GitZoneOpenCodeServer');
|
||||
//# sourceMappingURL=gitzone-opencode-protocol.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-protocol.js","sourceRoot":"","sources":["../../src/common/gitzone-opencode-protocol.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG,6BAA6B,CAAC;AAEpD,QAAA,qBAAqB,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC"}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||
declare const _default: ContainerModule;
|
||||
export default _default;
|
||||
//# sourceMappingURL=gitzone-opencode-backend-module.d.ts.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-backend-module.d.ts","sourceRoot":"","sources":["../../src/node/gitzone-opencode-backend-module.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;;AASxE,wBAaG"}
|
||||
@@ -1,21 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const backend_application_js_1 = require("@theia/core/lib/node/backend-application.js");
|
||||
const handler_js_1 = require("@theia/core/lib/common/messaging/handler.js");
|
||||
const proxy_factory_js_1 = require("@theia/core/lib/common/messaging/proxy-factory.js");
|
||||
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||
const gitzone_opencode_protocol_js_1 = require("../common/gitzone-opencode-protocol.js");
|
||||
const gitzone_opencode_node_service_js_1 = require("./gitzone-opencode-node-service.js");
|
||||
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||
bind(gitzone_opencode_node_service_js_1.GitZoneOpenCodeNodeService).toSelf().inSingletonScope();
|
||||
bind(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer).toService(gitzone_opencode_node_service_js_1.GitZoneOpenCodeNodeService);
|
||||
bind(backend_application_js_1.BackendApplicationContribution).toService(gitzone_opencode_node_service_js_1.GitZoneOpenCodeNodeService);
|
||||
bind(handler_js_1.ConnectionHandler)
|
||||
.toDynamicValue((context) => new proxy_factory_js_1.RpcConnectionHandler(gitzone_opencode_protocol_js_1.gitZoneOpenCodePath, (client) => {
|
||||
const server = context.container.get(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer);
|
||||
server.setClient(client);
|
||||
return server;
|
||||
}))
|
||||
.inSingletonScope();
|
||||
});
|
||||
//# sourceMappingURL=gitzone-opencode-backend-module.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-backend-module.js","sourceRoot":"","sources":["../../src/node/gitzone-opencode-backend-module.ts"],"names":[],"mappings":";;AAAA,wFAA6F;AAC7F,4EAAgF;AAChF,wFAAyF;AACzF,oEAAwE;AACxE,yFAKgD;AAChD,yFAAgF;AAEhF,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,6DAA0B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC7D,IAAI,CAAC,oDAAqB,CAAC,CAAC,SAAS,CAAC,6DAA0B,CAAC,CAAC;IAClE,IAAI,CAAC,uDAA8B,CAAC,CAAC,SAAS,CAAC,6DAA0B,CAAC,CAAC;IAC3E,IAAI,CAAC,8BAAiB,CAAC;SACpB,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,IAAI,uCAAoB,CAAyB,kDAAmB,EAAE,CAAC,MAAM,EAAE,EAAE;QAC/E,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAyB,oDAAqB,CAAC,CAAC;QACpF,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CACH;SACA,gBAAgB,EAAE,CAAC;AACxB,CAAC,CAAC,CAAC"}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { OpenCodeServerClient } from '@git.zone/ide-opencode-bridge';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application.js';
|
||||
import type { IGitZoneOpenCodeClient, IGitZoneOpenCodeConnectionInfo, IGitZoneOpenCodePromptBody, IGitZoneOpenCodeServer } from '../common/gitzone-opencode-protocol.js';
|
||||
import * as plugins from './plugins.js';
|
||||
export declare class GitZoneOpenCodeNodeService implements IGitZoneOpenCodeServer, BackendApplicationContribution {
|
||||
protected client: IGitZoneOpenCodeClient | undefined;
|
||||
protected eventAbortController: AbortController | undefined;
|
||||
protected openCodeProcess: plugins.childProcess.ChildProcess | undefined;
|
||||
initialize(): void;
|
||||
onStop(): void;
|
||||
setClient(client: IGitZoneOpenCodeClient | undefined): void;
|
||||
getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo>;
|
||||
health(): Promise<unknown>;
|
||||
providers(): Promise<unknown>;
|
||||
agents(): Promise<unknown>;
|
||||
sessions(): Promise<unknown>;
|
||||
createSession(title?: string): Promise<unknown>;
|
||||
messages(sessionId: string, limit?: number): Promise<unknown>;
|
||||
prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown>;
|
||||
promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void>;
|
||||
command(sessionId: string, command: string, commandArguments?: string): Promise<unknown>;
|
||||
abort(sessionId: string): Promise<unknown>;
|
||||
diff(sessionId: string, messageId?: string): Promise<unknown>;
|
||||
todo(sessionId: string): Promise<unknown>;
|
||||
respondToPermission(sessionId: string, permissionId: string, response: string, remember?: boolean): Promise<unknown>;
|
||||
protected ensureOpenCodeStarted(): Promise<void>;
|
||||
protected restartEventStream(): void;
|
||||
protected createClient(): OpenCodeServerClient;
|
||||
protected get workspacePath(): string;
|
||||
protected get port(): number;
|
||||
protected get baseUrl(): string;
|
||||
protected get username(): string;
|
||||
protected get password(): string;
|
||||
protected get autoStart(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=gitzone-opencode-node-service.d.ts.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-node-service.d.ts","sourceRoot":"","sources":["../../src/node/gitzone-opencode-node-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,8BAA8B,EAAE,MAAM,6CAA6C,CAAC;AAE7F,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAC9B,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,wCAAwC,CAAC;AAChD,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAExC,qBACa,0BAA2B,YAAW,sBAAsB,EAAE,8BAA8B;IACvG,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,SAAS,CAAC;IACrD,SAAS,CAAC,oBAAoB,EAAE,eAAe,GAAG,SAAS,CAAC;IAC5D,SAAS,CAAC,eAAe,EAAE,OAAO,CAAC,YAAY,CAAC,YAAY,GAAG,SAAS,CAAC;IAEzE,UAAU,IAAI,IAAI;IAMlB,MAAM,IAAI,IAAI;IAOd,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,SAAS,GAAG,IAAI;IAKrD,iBAAiB,IAAI,OAAO,CAAC,8BAA8B,CAAC;IAS5D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAI7B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAI5B,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI/C,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,SAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAIpF,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzC,mBAAmB,CACvB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,OAAO,GACjB,OAAO,CAAC,OAAO,CAAC;cAIH,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCtD,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAmBpC,SAAS,CAAC,YAAY;IAQtB,SAAS,KAAK,aAAa,WAE1B;IAED,SAAS,KAAK,IAAI,WAGjB;IAED,SAAS,KAAK,OAAO,WAEpB;IAED,SAAS,KAAK,QAAQ,WAErB;IAED,SAAS,KAAK,QAAQ,WAErB;IAED,SAAS,KAAK,SAAS,YAEtB;CACF"}
|
||||
@@ -1,182 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GitZoneOpenCodeNodeService = void 0;
|
||||
const ide_opencode_bridge_1 = require("@git.zone/ide-opencode-bridge");
|
||||
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||
const plugins = __importStar(require("./plugins.js"));
|
||||
let GitZoneOpenCodeNodeService = class GitZoneOpenCodeNodeService {
|
||||
client;
|
||||
eventAbortController;
|
||||
openCodeProcess;
|
||||
initialize() {
|
||||
if (this.autoStart) {
|
||||
void this.ensureOpenCodeStarted();
|
||||
}
|
||||
}
|
||||
onStop() {
|
||||
this.eventAbortController?.abort();
|
||||
if (this.openCodeProcess && this.openCodeProcess.exitCode === null) {
|
||||
this.openCodeProcess.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
setClient(client) {
|
||||
this.client = client;
|
||||
this.restartEventStream();
|
||||
}
|
||||
async getConnectionInfo() {
|
||||
return {
|
||||
baseUrl: this.baseUrl,
|
||||
port: this.port,
|
||||
workspacePath: this.workspacePath,
|
||||
autoStart: this.autoStart,
|
||||
};
|
||||
}
|
||||
async health() {
|
||||
await this.ensureOpenCodeStarted();
|
||||
return this.createClient().health();
|
||||
}
|
||||
async providers() {
|
||||
return this.createClient().providers();
|
||||
}
|
||||
async agents() {
|
||||
return this.createClient().agents();
|
||||
}
|
||||
async sessions() {
|
||||
return this.createClient().sessions();
|
||||
}
|
||||
async createSession(title) {
|
||||
return this.createClient().createSession(title ? { title } : {});
|
||||
}
|
||||
async messages(sessionId, limit) {
|
||||
return this.createClient().messages(sessionId, limit);
|
||||
}
|
||||
async prompt(sessionId, body) {
|
||||
return this.createClient().prompt(sessionId, body);
|
||||
}
|
||||
async promptAsync(sessionId, body) {
|
||||
await this.createClient().promptAsync(sessionId, body);
|
||||
}
|
||||
async command(sessionId, command, commandArguments = '') {
|
||||
return this.createClient().command(sessionId, { command, arguments: commandArguments });
|
||||
}
|
||||
async abort(sessionId) {
|
||||
return this.createClient().abort(sessionId);
|
||||
}
|
||||
async diff(sessionId, messageId) {
|
||||
return this.createClient().diff(sessionId, messageId);
|
||||
}
|
||||
async todo(sessionId) {
|
||||
return this.createClient().todo(sessionId);
|
||||
}
|
||||
async respondToPermission(sessionId, permissionId, response, remember) {
|
||||
return this.createClient().respondToPermission(sessionId, permissionId, { response, remember });
|
||||
}
|
||||
async ensureOpenCodeStarted() {
|
||||
try {
|
||||
await this.createClient().health();
|
||||
return;
|
||||
}
|
||||
catch {
|
||||
if (!this.autoStart || this.openCodeProcess) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.openCodeProcess = plugins.childProcess.spawn('opencode', ['serve', '--hostname', '127.0.0.1', '--port', `${this.port}`], {
|
||||
cwd: this.workspacePath,
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_SERVER_USERNAME: this.username,
|
||||
OPENCODE_SERVER_PASSWORD: this.password,
|
||||
},
|
||||
shell: false,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
windowsHide: true,
|
||||
});
|
||||
this.openCodeProcess.once('error', (error) => {
|
||||
console.warn(`OpenCode server autostart failed: ${error.message}`);
|
||||
this.openCodeProcess = undefined;
|
||||
});
|
||||
this.openCodeProcess.once('exit', () => {
|
||||
this.openCodeProcess = undefined;
|
||||
});
|
||||
}
|
||||
restartEventStream() {
|
||||
this.eventAbortController?.abort();
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
const abortController = new AbortController();
|
||||
this.eventAbortController = abortController;
|
||||
void (async () => {
|
||||
try {
|
||||
await this.ensureOpenCodeStarted();
|
||||
for await (const event of this.createClient().events(abortController.signal)) {
|
||||
this.client?.onOpenCodeEvent(event);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// The UI can explicitly call health() for detailed connection diagnostics.
|
||||
}
|
||||
})();
|
||||
}
|
||||
createClient() {
|
||||
return new ide_opencode_bridge_1.OpenCodeServerClient({
|
||||
baseUrl: this.baseUrl,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
});
|
||||
}
|
||||
get workspacePath() {
|
||||
return process.env.GITZONE_IDE_WORKSPACE || process.cwd();
|
||||
}
|
||||
get port() {
|
||||
const port = Number(process.env.GITZONE_IDE_OPENCODE_PORT || '4096');
|
||||
return Number.isInteger(port) && port > 0 ? port : 4096;
|
||||
}
|
||||
get baseUrl() {
|
||||
return `http://127.0.0.1:${this.port}`;
|
||||
}
|
||||
get username() {
|
||||
return process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
||||
}
|
||||
get password() {
|
||||
return process.env.OPENCODE_SERVER_PASSWORD || '';
|
||||
}
|
||||
get autoStart() {
|
||||
return process.env.GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART !== '1';
|
||||
}
|
||||
};
|
||||
exports.GitZoneOpenCodeNodeService = GitZoneOpenCodeNodeService;
|
||||
exports.GitZoneOpenCodeNodeService = GitZoneOpenCodeNodeService = __decorate([
|
||||
(0, index_js_1.injectable)()
|
||||
], GitZoneOpenCodeNodeService);
|
||||
//# sourceMappingURL=gitzone-opencode-node-service.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"gitzone-opencode-node-service.js","sourceRoot":"","sources":["../../src/node/gitzone-opencode-node-service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uEAAqE;AAErE,oEAAmE;AAOnE,sDAAwC;AAGjC,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAC3B,MAAM,CAAqC;IAC3C,oBAAoB,CAA8B;IAClD,eAAe,CAAgD;IAEzE,UAAU;QACR,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,KAAK,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACpC,CAAC;IACH,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACnE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,MAA0C;QAClD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,SAAS,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAc;QAChC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,KAAc;QAC9C,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB,EAAE,IAAgC;QAC9D,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,IAAgC;QACnE,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,OAAe,EAAE,gBAAgB,GAAG,EAAE;QACrE,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB;QAC3B,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB,EAAE,SAAkB;QAC9C,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,SAAiB,EACjB,YAAoB,EACpB,QAAgB,EAChB,QAAkB;QAElB,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,mBAAmB,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClG,CAAC;IAES,KAAK,CAAC,qBAAqB;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC;YACnC,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC5C,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAC/C,UAAU,EACV,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAC9D;YACE,GAAG,EAAE,IAAI,CAAC,aAAa;YACvB,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,wBAAwB,EAAE,IAAI,CAAC,QAAQ;gBACvC,wBAAwB,EAAE,IAAI,CAAC,QAAQ;aACxC;YACD,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;YACrC,WAAW,EAAE,IAAI;SAClB,CACF,CAAC;QACF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3C,OAAO,CAAC,IAAI,CAAC,qCAAqC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACnC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;YACrC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;IAES,kBAAkB;QAC1B,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,IAAI,CAAC,oBAAoB,GAAG,eAAe,CAAC;QAC5C,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBACnC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC7E,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,2EAA2E;YAC7E,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IAES,YAAY;QACpB,OAAO,IAAI,0CAAoB,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;IACL,CAAC;IAED,IAAc,aAAa;QACzB,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC5D,CAAC;IAED,IAAc,IAAI;QAChB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,MAAM,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,UAAU,CAAC;IAC5D,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,IAAc,SAAS;QACrB,OAAO,OAAO,CAAC,GAAG,CAAC,sCAAsC,KAAK,GAAG,CAAC;IACpE,CAAC;CACF,CAAA;AA/KY,gEAA0B;qCAA1B,0BAA0B;IADtC,IAAA,qBAAU,GAAE;GACA,0BAA0B,CA+KtC"}
|
||||
@@ -1,3 +0,0 @@
|
||||
import * as childProcess from 'node:child_process';
|
||||
export { childProcess };
|
||||
//# sourceMappingURL=plugins.d.ts.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/node/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
||||
@@ -1,29 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.childProcess = void 0;
|
||||
const childProcess = __importStar(require("node:child_process"));
|
||||
exports.childProcess = childProcess;
|
||||
//# sourceMappingURL=plugins.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/node/plugins.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iEAAmD;AAE1C,oCAAY"}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "@git.zone/ide-extension-opencode",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"keywords": ["theia-extension"],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@git.zone/ide-opencode-bridge": "workspace:*",
|
||||
"@theia/core": "1.71.0"
|
||||
},
|
||||
"theiaExtensions": [
|
||||
{
|
||||
"frontend": "lib/browser/gitzone-opencode-frontend-module",
|
||||
"backend": "lib/node/gitzone-opencode-backend-module"
|
||||
}
|
||||
],
|
||||
"files": ["src", "lib"]
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-menus.js';
|
||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider.js';
|
||||
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||
import { ContainerModule, inject, injectable } from '@theia/core/shared/inversify/index.js';
|
||||
import {
|
||||
GitZoneOpenCodeServer,
|
||||
gitZoneOpenCodePath,
|
||||
type IGitZoneOpenCodeClient,
|
||||
type IGitZoneOpenCodeServer,
|
||||
} from '../common/gitzone-opencode-protocol.js';
|
||||
|
||||
export const GitZoneOpenCodeHealthCommand = {
|
||||
id: 'gitzone.opencode.health',
|
||||
label: 'OpenCode: Check Health',
|
||||
};
|
||||
|
||||
export const GitZoneOpenCodeNewSessionCommand = {
|
||||
id: 'gitzone.opencode.newSession',
|
||||
label: 'OpenCode: New Session',
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class GitZoneOpenCodeContribution implements CommandContribution, MenuContribution {
|
||||
@inject(GitZoneOpenCodeServer)
|
||||
protected readonly openCodeServer!: IGitZoneOpenCodeServer;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messages!: MessageService;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(GitZoneOpenCodeHealthCommand, {
|
||||
execute: async () => {
|
||||
const health = await this.openCodeServer.health();
|
||||
await this.messages.info(`OpenCode health: ${JSON.stringify(health)}`);
|
||||
},
|
||||
});
|
||||
registry.registerCommand(GitZoneOpenCodeNewSessionCommand, {
|
||||
execute: async () => {
|
||||
const session = await this.openCodeServer.createSession('Git.Zone IDE Session');
|
||||
await this.messages.info(`OpenCode session created: ${JSON.stringify(session)}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
|
||||
commandId: GitZoneOpenCodeHealthCommand.id,
|
||||
label: GitZoneOpenCodeHealthCommand.label,
|
||||
});
|
||||
menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
|
||||
commandId: GitZoneOpenCodeNewSessionCommand.id,
|
||||
label: GitZoneOpenCodeNewSessionCommand.label,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const openCodeClient: IGitZoneOpenCodeClient = {
|
||||
onOpenCodeEvent: (event) => {
|
||||
globalThis.dispatchEvent(new CustomEvent('gitzone-opencode-event', { detail: event }));
|
||||
},
|
||||
};
|
||||
|
||||
export default new ContainerModule((bind) => {
|
||||
bind(GitZoneOpenCodeServer)
|
||||
.toDynamicValue((context) =>
|
||||
context.container
|
||||
.get(WebSocketConnectionProvider)
|
||||
.createProxy<IGitZoneOpenCodeServer>(gitZoneOpenCodePath, openCodeClient),
|
||||
)
|
||||
.inSingletonScope();
|
||||
bind(GitZoneOpenCodeContribution).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(GitZoneOpenCodeContribution);
|
||||
bind(MenuContribution).toService(GitZoneOpenCodeContribution);
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
export const gitZoneOpenCodePath = '/services/git-zone/opencode';
|
||||
|
||||
export const GitZoneOpenCodeServer = Symbol('GitZoneOpenCodeServer');
|
||||
|
||||
export interface IGitZoneOpenCodeConnectionInfo {
|
||||
baseUrl: string;
|
||||
port: number;
|
||||
workspacePath: string;
|
||||
autoStart: boolean;
|
||||
}
|
||||
|
||||
export interface IGitZoneOpenCodeEvent {
|
||||
type: string;
|
||||
id?: string;
|
||||
retry?: number;
|
||||
data?: unknown;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface IGitZoneOpenCodeClient {
|
||||
onOpenCodeEvent(event: IGitZoneOpenCodeEvent): void;
|
||||
}
|
||||
|
||||
export interface IGitZoneOpenCodePromptBody {
|
||||
messageID?: string;
|
||||
model?: {
|
||||
providerID: string;
|
||||
modelID: string;
|
||||
};
|
||||
agent?: string;
|
||||
noReply?: boolean;
|
||||
system?: string;
|
||||
tools?: Record<string, boolean>;
|
||||
parts: Array<{ type: string; [key: string]: unknown }>;
|
||||
}
|
||||
|
||||
export interface IGitZoneOpenCodeServer {
|
||||
setClient(client: IGitZoneOpenCodeClient | undefined): void;
|
||||
getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo>;
|
||||
health(): Promise<unknown>;
|
||||
providers(): Promise<unknown>;
|
||||
agents(): Promise<unknown>;
|
||||
sessions(): Promise<unknown>;
|
||||
createSession(title?: string): Promise<unknown>;
|
||||
messages(sessionId: string, limit?: number): Promise<unknown>;
|
||||
prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown>;
|
||||
promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void>;
|
||||
command(sessionId: string, command: string, commandArguments?: string): Promise<unknown>;
|
||||
abort(sessionId: string): Promise<unknown>;
|
||||
diff(sessionId: string, messageId?: string): Promise<unknown>;
|
||||
todo(sessionId: string): Promise<unknown>;
|
||||
respondToPermission(sessionId: string, permissionId: string, response: string, remember?: boolean): Promise<unknown>;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application.js';
|
||||
import { ConnectionHandler } from '@theia/core/lib/common/messaging/handler.js';
|
||||
import { RpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory.js';
|
||||
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||
import {
|
||||
GitZoneOpenCodeServer,
|
||||
gitZoneOpenCodePath,
|
||||
type IGitZoneOpenCodeClient,
|
||||
type IGitZoneOpenCodeServer,
|
||||
} from '../common/gitzone-opencode-protocol.js';
|
||||
import { GitZoneOpenCodeNodeService } from './gitzone-opencode-node-service.js';
|
||||
|
||||
export default new ContainerModule((bind) => {
|
||||
bind(GitZoneOpenCodeNodeService).toSelf().inSingletonScope();
|
||||
bind(GitZoneOpenCodeServer).toService(GitZoneOpenCodeNodeService);
|
||||
bind(BackendApplicationContribution).toService(GitZoneOpenCodeNodeService);
|
||||
bind(ConnectionHandler)
|
||||
.toDynamicValue((context) =>
|
||||
new RpcConnectionHandler<IGitZoneOpenCodeClient>(gitZoneOpenCodePath, (client) => {
|
||||
const server = context.container.get<IGitZoneOpenCodeServer>(GitZoneOpenCodeServer);
|
||||
server.setClient(client);
|
||||
return server;
|
||||
}),
|
||||
)
|
||||
.inSingletonScope();
|
||||
});
|
||||
@@ -1,188 +0,0 @@
|
||||
import { OpenCodeServerClient } from '@git.zone/ide-opencode-bridge';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application.js';
|
||||
import { injectable } from '@theia/core/shared/inversify/index.js';
|
||||
import type {
|
||||
IGitZoneOpenCodeClient,
|
||||
IGitZoneOpenCodeConnectionInfo,
|
||||
IGitZoneOpenCodePromptBody,
|
||||
IGitZoneOpenCodeServer,
|
||||
} from '../common/gitzone-opencode-protocol.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
@injectable()
|
||||
export class GitZoneOpenCodeNodeService implements IGitZoneOpenCodeServer, BackendApplicationContribution {
|
||||
protected client: IGitZoneOpenCodeClient | undefined;
|
||||
protected eventAbortController: AbortController | undefined;
|
||||
protected openCodeProcess: plugins.childProcess.ChildProcess | undefined;
|
||||
|
||||
initialize(): void {
|
||||
if (this.autoStart) {
|
||||
void this.ensureOpenCodeStarted();
|
||||
}
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
this.eventAbortController?.abort();
|
||||
if (this.openCodeProcess && this.openCodeProcess.exitCode === null) {
|
||||
this.openCodeProcess.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
|
||||
setClient(client: IGitZoneOpenCodeClient | undefined): void {
|
||||
this.client = client;
|
||||
this.restartEventStream();
|
||||
}
|
||||
|
||||
async getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo> {
|
||||
return {
|
||||
baseUrl: this.baseUrl,
|
||||
port: this.port,
|
||||
workspacePath: this.workspacePath,
|
||||
autoStart: this.autoStart,
|
||||
};
|
||||
}
|
||||
|
||||
async health(): Promise<unknown> {
|
||||
await this.ensureOpenCodeStarted();
|
||||
return this.createClient().health();
|
||||
}
|
||||
|
||||
async providers(): Promise<unknown> {
|
||||
return this.createClient().providers();
|
||||
}
|
||||
|
||||
async agents(): Promise<unknown> {
|
||||
return this.createClient().agents();
|
||||
}
|
||||
|
||||
async sessions(): Promise<unknown> {
|
||||
return this.createClient().sessions();
|
||||
}
|
||||
|
||||
async createSession(title?: string): Promise<unknown> {
|
||||
return this.createClient().createSession(title ? { title } : {});
|
||||
}
|
||||
|
||||
async messages(sessionId: string, limit?: number): Promise<unknown> {
|
||||
return this.createClient().messages(sessionId, limit);
|
||||
}
|
||||
|
||||
async prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown> {
|
||||
return this.createClient().prompt(sessionId, body);
|
||||
}
|
||||
|
||||
async promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void> {
|
||||
await this.createClient().promptAsync(sessionId, body);
|
||||
}
|
||||
|
||||
async command(sessionId: string, command: string, commandArguments = ''): Promise<unknown> {
|
||||
return this.createClient().command(sessionId, { command, arguments: commandArguments });
|
||||
}
|
||||
|
||||
async abort(sessionId: string): Promise<unknown> {
|
||||
return this.createClient().abort(sessionId);
|
||||
}
|
||||
|
||||
async diff(sessionId: string, messageId?: string): Promise<unknown> {
|
||||
return this.createClient().diff(sessionId, messageId);
|
||||
}
|
||||
|
||||
async todo(sessionId: string): Promise<unknown> {
|
||||
return this.createClient().todo(sessionId);
|
||||
}
|
||||
|
||||
async respondToPermission(
|
||||
sessionId: string,
|
||||
permissionId: string,
|
||||
response: string,
|
||||
remember?: boolean,
|
||||
): Promise<unknown> {
|
||||
return this.createClient().respondToPermission(sessionId, permissionId, { response, remember });
|
||||
}
|
||||
|
||||
protected async ensureOpenCodeStarted(): Promise<void> {
|
||||
try {
|
||||
await this.createClient().health();
|
||||
return;
|
||||
} catch {
|
||||
if (!this.autoStart || this.openCodeProcess) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.openCodeProcess = plugins.childProcess.spawn(
|
||||
'opencode',
|
||||
['serve', '--hostname', '127.0.0.1', '--port', `${this.port}`],
|
||||
{
|
||||
cwd: this.workspacePath,
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_SERVER_USERNAME: this.username,
|
||||
OPENCODE_SERVER_PASSWORD: this.password,
|
||||
},
|
||||
shell: false,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
windowsHide: true,
|
||||
},
|
||||
);
|
||||
this.openCodeProcess.once('error', (error) => {
|
||||
console.warn(`OpenCode server autostart failed: ${error.message}`);
|
||||
this.openCodeProcess = undefined;
|
||||
});
|
||||
this.openCodeProcess.once('exit', () => {
|
||||
this.openCodeProcess = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
protected restartEventStream(): void {
|
||||
this.eventAbortController?.abort();
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
const abortController = new AbortController();
|
||||
this.eventAbortController = abortController;
|
||||
void (async () => {
|
||||
try {
|
||||
await this.ensureOpenCodeStarted();
|
||||
for await (const event of this.createClient().events(abortController.signal)) {
|
||||
this.client?.onOpenCodeEvent(event);
|
||||
}
|
||||
} catch {
|
||||
// The UI can explicitly call health() for detailed connection diagnostics.
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
protected createClient() {
|
||||
return new OpenCodeServerClient({
|
||||
baseUrl: this.baseUrl,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
});
|
||||
}
|
||||
|
||||
protected get workspacePath() {
|
||||
return process.env.GITZONE_IDE_WORKSPACE || process.cwd();
|
||||
}
|
||||
|
||||
protected get port() {
|
||||
const port = Number(process.env.GITZONE_IDE_OPENCODE_PORT || '4096');
|
||||
return Number.isInteger(port) && port > 0 ? port : 4096;
|
||||
}
|
||||
|
||||
protected get baseUrl() {
|
||||
return `http://127.0.0.1:${this.port}`;
|
||||
}
|
||||
|
||||
protected get username() {
|
||||
return process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
||||
}
|
||||
|
||||
protected get password() {
|
||||
return process.env.OPENCODE_SERVER_PASSWORD || '';
|
||||
}
|
||||
|
||||
protected get autoStart() {
|
||||
return process.env.GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART !== '1';
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import * as childProcess from 'node:child_process';
|
||||
|
||||
export { childProcess };
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"verbatimModuleSyntax": false,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||
}
|
||||
Reference in New Issue
Block a user