Move OpenCode UI into Electron shell

This commit is contained in:
2026-05-11 23:31:09 +00:00
parent 6f32a206b4
commit 08ed394737
40 changed files with 901 additions and 881 deletions
+8 -3
View File
@@ -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"
]
}
+16
View File
@@ -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);
+712 -25
View File
@@ -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');
-1
View File
@@ -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'));