Support remote project tabs with local OpenCode bridge
Keeps provider credentials local while executing OpenCode shell and file tools against the selected remote workspace over SSH.
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git.zone/ide-protocol": "workspace:*",
|
"@git.zone/ide-protocol": "workspace:*",
|
||||||
|
"@git.zone/ide-opencode-bridge": "workspace:*",
|
||||||
"@git.zone/ide-server-installer": "workspace:*",
|
"@git.zone/ide-server-installer": "workspace:*",
|
||||||
"@git.zone/ide-ssh": "workspace:*",
|
"@git.zone/ide-ssh": "workspace:*",
|
||||||
"electron": "^42.0.1"
|
"electron": "^42.0.1"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ contextBridge.exposeInMainWorld('gitZoneIde', {
|
|||||||
listHosts: () => ipcRenderer.invoke('gitzone:list-hosts'),
|
listHosts: () => ipcRenderer.invoke('gitzone:list-hosts'),
|
||||||
saveHost: (input) => ipcRenderer.invoke('gitzone:save-host', input),
|
saveHost: (input) => ipcRenderer.invoke('gitzone:save-host', input),
|
||||||
connect: (input) => ipcRenderer.invoke('gitzone:connect', input),
|
connect: (input) => ipcRenderer.invoke('gitzone:connect', input),
|
||||||
|
addProject: (input) => ipcRenderer.invoke('gitzone:add-project', input),
|
||||||
|
openProject: (input) => ipcRenderer.invoke('gitzone:open-project', input),
|
||||||
onConnectProgress: (callback) => {
|
onConnectProgress: (callback) => {
|
||||||
const listener = (_event, message) => callback(message);
|
const listener = (_event, message) => callback(message);
|
||||||
ipcRenderer.on('gitzone:connect-progress', listener);
|
ipcRenderer.on('gitzone:connect-progress', listener);
|
||||||
|
|||||||
+1104
-162
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
|||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
|
import * as http from 'node:http';
|
||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
|
import * as ideOpenCodeBridge from '@git.zone/ide-opencode-bridge';
|
||||||
import * as ideServerInstaller from '@git.zone/ide-server-installer';
|
import * as ideServerInstaller from '@git.zone/ide-server-installer';
|
||||||
import * as ideSsh from '@git.zone/ide-ssh';
|
import * as ideSsh from '@git.zone/ide-ssh';
|
||||||
|
|
||||||
export { crypto, electron, ideServerInstaller, ideSsh };
|
export { crypto, electron, http, ideOpenCodeBridge, ideServerInstaller, ideSsh };
|
||||||
|
|||||||
@@ -41,12 +41,14 @@
|
|||||||
"target": "browser",
|
"target": "browser",
|
||||||
"backend": {
|
"backend": {
|
||||||
"config": {
|
"config": {
|
||||||
|
"singleInstance": false,
|
||||||
"configurationFolder": ".git.zone/ide/theia"
|
"configurationFolder": ".git.zone/ide/theia"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"config": {
|
"config": {
|
||||||
"applicationName": "Git.Zone IDE",
|
"applicationName": "Git.Zone IDE",
|
||||||
|
"defaultTheme": "dark",
|
||||||
"preferencesDirName": ".git-zone-ide"
|
"preferencesDirName": ".git-zone-ide"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const { BackendApplicationConfigProvider } = require('@theia/core/lib/node/backe
|
|||||||
const main = require('@theia/core/lib/node/main');
|
const main = require('@theia/core/lib/node/main');
|
||||||
|
|
||||||
BackendApplicationConfigProvider.set({
|
BackendApplicationConfigProvider.set({
|
||||||
"singleInstance": true,
|
"singleInstance": false,
|
||||||
"frontendConnectionTimeout": 0,
|
"frontendConnectionTimeout": 0,
|
||||||
"configurationFolder": ".git.zone/ide/theia"
|
"configurationFolder": ".git.zone/ide/theia"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ const { FrontendApplicationConfigProvider } = require('@theia/core/lib/browser/f
|
|||||||
|
|
||||||
FrontendApplicationConfigProvider.set({
|
FrontendApplicationConfigProvider.set({
|
||||||
"applicationName": "Git.Zone IDE",
|
"applicationName": "Git.Zone IDE",
|
||||||
"defaultTheme": {
|
"defaultTheme": "dark",
|
||||||
"light": "light",
|
|
||||||
"dark": "dark"
|
|
||||||
},
|
|
||||||
"defaultIconTheme": "theia-file-icons",
|
"defaultIconTheme": "theia-file-icons",
|
||||||
"electron": {
|
"electron": {
|
||||||
"windowOptions": {},
|
"windowOptions": {},
|
||||||
|
|||||||
@@ -287,6 +287,155 @@ export class OpenCodeServerClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const openCodeBridgeToolNames = [
|
||||||
|
'bash',
|
||||||
|
'read',
|
||||||
|
'write',
|
||||||
|
'edit',
|
||||||
|
'grep',
|
||||||
|
'glob',
|
||||||
|
'apply_patch',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type TOpenCodeBridgeToolName = typeof openCodeBridgeToolNames[number];
|
||||||
|
|
||||||
|
export interface IOpenCodeBridgeConfigRenderOptions {
|
||||||
|
bridgeUrlEnvName?: string;
|
||||||
|
bridgeTokenEnvName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderOpenCodeBridgeConfigContent = () => `${JSON.stringify({
|
||||||
|
$schema: 'https://opencode.ai/config.json',
|
||||||
|
autoupdate: false,
|
||||||
|
snapshot: false,
|
||||||
|
permission: {
|
||||||
|
lsp: 'deny',
|
||||||
|
skill: 'deny',
|
||||||
|
},
|
||||||
|
}, undefined, 2)}\n`;
|
||||||
|
|
||||||
|
export const renderOpenCodeBridgeToolFiles = (options: IOpenCodeBridgeConfigRenderOptions = {}) => {
|
||||||
|
const files: Record<string, string> = {};
|
||||||
|
for (const toolName of openCodeBridgeToolNames) {
|
||||||
|
files[`tools/${toolName}.js`] = renderOpenCodeBridgeToolFile(toolName, options);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderOpenCodeBridgeToolFile = (
|
||||||
|
toolName: TOpenCodeBridgeToolName,
|
||||||
|
options: IOpenCodeBridgeConfigRenderOptions = {},
|
||||||
|
) => {
|
||||||
|
const bridgeUrlEnvName = options.bridgeUrlEnvName ?? 'GITZONE_IDE_TOOL_BRIDGE_URL';
|
||||||
|
const bridgeTokenEnvName = options.bridgeTokenEnvName ?? 'GITZONE_IDE_TOOL_BRIDGE_TOKEN';
|
||||||
|
const definition = openCodeBridgeToolDefinitions[toolName];
|
||||||
|
return `import { tool } from "@opencode-ai/plugin";
|
||||||
|
|
||||||
|
const bridgeUrlEnvName = ${JSON.stringify(bridgeUrlEnvName)};
|
||||||
|
const bridgeTokenEnvName = ${JSON.stringify(bridgeTokenEnvName)};
|
||||||
|
|
||||||
|
const forwardTool = async (toolName, args, context) => {
|
||||||
|
const bridgeUrl = process.env[bridgeUrlEnvName];
|
||||||
|
const bridgeToken = process.env[bridgeTokenEnvName];
|
||||||
|
if (!bridgeUrl || !bridgeToken) {
|
||||||
|
throw new Error("Git.Zone OpenCode tool bridge is not configured.");
|
||||||
|
}
|
||||||
|
const response = await fetch(new URL("/tool/" + encodeURIComponent(toolName), bridgeUrl), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"authorization": "Bearer " + bridgeToken,
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
args,
|
||||||
|
context: {
|
||||||
|
sessionID: context.sessionID,
|
||||||
|
messageID: context.messageID,
|
||||||
|
agent: context.agent,
|
||||||
|
directory: context.directory,
|
||||||
|
worktree: context.worktree,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const responseText = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(responseText || ("Git.Zone OpenCode tool bridge failed with HTTP " + response.status));
|
||||||
|
}
|
||||||
|
return responseText ? JSON.parse(responseText) : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: ${JSON.stringify(definition.description)},
|
||||||
|
args: {
|
||||||
|
${definition.args.map((arg) => ` ${arg.name}: ${arg.schema},`).join('\n')}
|
||||||
|
},
|
||||||
|
async execute(args, context) {
|
||||||
|
return forwardTool(${JSON.stringify(toolName)}, args, context);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCodeBridgeToolDefinitions: Record<TOpenCodeBridgeToolName, {
|
||||||
|
description: string;
|
||||||
|
args: Array<{ name: string; schema: string }>;
|
||||||
|
}> = {
|
||||||
|
bash: {
|
||||||
|
description: 'Execute a shell command on the selected remote Git.Zone workspace over SSH. The command runs on the remote host, not on the local proxy workspace. Use workdir for a remote working directory when needed.',
|
||||||
|
args: [
|
||||||
|
{ name: 'command', schema: 'tool.schema.string().describe("The command to execute on the remote host")' },
|
||||||
|
{ name: 'timeout', schema: 'tool.schema.number().optional().describe("Optional timeout in milliseconds")' },
|
||||||
|
{ name: 'workdir', schema: 'tool.schema.string().optional().describe("Remote working directory. Defaults to the selected remote project path.")' },
|
||||||
|
{ name: 'description', schema: 'tool.schema.string().describe("Clear, concise description of what this command does in 5-10 words")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
description: 'Read a file or directory from the selected remote Git.Zone workspace. Paths may be remote absolute paths or paths relative to the selected remote project.',
|
||||||
|
args: [
|
||||||
|
{ name: 'filePath', schema: 'tool.schema.string().describe("Remote file or directory path to read")' },
|
||||||
|
{ name: 'offset', schema: 'tool.schema.number().optional().describe("The line number to start reading from (1-indexed)")' },
|
||||||
|
{ name: 'limit', schema: 'tool.schema.number().optional().describe("The maximum number of lines to read")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
write: {
|
||||||
|
description: 'Create or overwrite a file in the selected remote Git.Zone workspace.',
|
||||||
|
args: [
|
||||||
|
{ name: 'filePath', schema: 'tool.schema.string().describe("Remote file path to write")' },
|
||||||
|
{ name: 'content', schema: 'tool.schema.string().describe("The content to write to the remote file")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
description: 'Modify an existing remote file using exact string replacement.',
|
||||||
|
args: [
|
||||||
|
{ name: 'filePath', schema: 'tool.schema.string().describe("Remote file path to modify")' },
|
||||||
|
{ name: 'oldString', schema: 'tool.schema.string().describe("The text to replace")' },
|
||||||
|
{ name: 'newString', schema: 'tool.schema.string().describe("The replacement text")' },
|
||||||
|
{ name: 'replaceAll', schema: 'tool.schema.boolean().optional().describe("Replace all occurrences of oldString")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
grep: {
|
||||||
|
description: 'Search remote file contents in the selected Git.Zone workspace using ripgrep on the remote host.',
|
||||||
|
args: [
|
||||||
|
{ name: 'pattern', schema: 'tool.schema.string().describe("The regex pattern to search for in file contents")' },
|
||||||
|
{ name: 'path', schema: 'tool.schema.string().optional().describe("Remote directory or file to search. Defaults to the selected remote project path.")' },
|
||||||
|
{ name: 'include', schema: 'tool.schema.string().optional().describe("File glob to include, for example *.ts or *.{ts,tsx}")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
glob: {
|
||||||
|
description: 'Find remote files by glob pattern in the selected Git.Zone workspace using ripgrep on the remote host.',
|
||||||
|
args: [
|
||||||
|
{ name: 'pattern', schema: 'tool.schema.string().describe("The glob pattern to match remote files against")' },
|
||||||
|
{ name: 'path', schema: 'tool.schema.string().optional().describe("Remote directory to search. Defaults to the selected remote project path.")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
apply_patch: {
|
||||||
|
description: 'Apply a stripped-down file patch to the selected remote Git.Zone workspace. Patch paths are relative to the selected remote project unless absolute remote paths are supplied.',
|
||||||
|
args: [
|
||||||
|
{ name: 'patchText', schema: 'tool.schema.string().describe("The full patch text that describes all remote file changes to make")' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const parseServerSentEvent = (raw: string): IOpenCodeEvent | undefined => {
|
export const parseServerSentEvent = (raw: string): IOpenCodeEvent | undefined => {
|
||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface IRemoteServerBootstrapOptions {
|
|||||||
opencodePassword: string;
|
opencodePassword: string;
|
||||||
installRoot?: string;
|
installRoot?: string;
|
||||||
nodeEnv?: string;
|
nodeEnv?: string;
|
||||||
|
theiaColorTheme?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRemoteEphemeralBootstrapOptions extends IRemoteServerBootstrapOptions {
|
export interface IRemoteEphemeralBootstrapOptions extends IRemoteServerBootstrapOptions {
|
||||||
@@ -60,9 +61,36 @@ export interface IRemoteEphemeralRuntimeMarkOptions {
|
|||||||
nodePath?: string;
|
nodePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IRemoteEphemeralPortAllocationOptions {
|
||||||
|
runtimeRoot: string;
|
||||||
|
count?: number;
|
||||||
|
nodePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteProjectRegistryOptions {
|
||||||
|
runtimeRoot: string;
|
||||||
|
ideDataRoot?: string;
|
||||||
|
nodePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteProjectUpsertOptions extends IRemoteProjectRegistryOptions {
|
||||||
|
projectPath: string;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteOpenCodeToolCommandOptions {
|
||||||
|
runtimeRoot: string;
|
||||||
|
workspacePath: string;
|
||||||
|
toolName: string;
|
||||||
|
nodePath?: string;
|
||||||
|
rgPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultIdeDataRoot = '~/.git.zone/ide';
|
export const defaultIdeDataRoot = '~/.git.zone/ide';
|
||||||
export const defaultInstallRoot = '~/.git.zone/ide/server';
|
export const defaultInstallRoot = '~/.git.zone/ide/server';
|
||||||
export const remoteEphemeralRuntimeMarkerFileName = '.gitzone-runtime-sha256';
|
export const remoteEphemeralRuntimeMarkerFileName = '.gitzone-runtime-sha256';
|
||||||
|
export const remoteProjectsFileName = 'projects.json';
|
||||||
|
export const defaultTheiaColorTheme = 'dark';
|
||||||
|
|
||||||
export const createRemoteServerInstallPlan = (
|
export const createRemoteServerInstallPlan = (
|
||||||
options: IRemoteServerInstallPlanOptions,
|
options: IRemoteServerInstallPlanOptions,
|
||||||
@@ -120,6 +148,7 @@ export const createRemoteBootstrapCommand = (options: IRemoteServerBootstrapOpti
|
|||||||
const env = {
|
const env = {
|
||||||
GITZONE_IDE_WORKSPACE: options.workspacePath,
|
GITZONE_IDE_WORKSPACE: options.workspacePath,
|
||||||
GITZONE_IDE_OPENCODE_PORT: `${options.opencodePort}`,
|
GITZONE_IDE_OPENCODE_PORT: `${options.opencodePort}`,
|
||||||
|
GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART: '1',
|
||||||
OPENCODE_SERVER_USERNAME: options.opencodeUsername,
|
OPENCODE_SERVER_USERNAME: options.opencodeUsername,
|
||||||
OPENCODE_SERVER_PASSWORD: options.opencodePassword,
|
OPENCODE_SERVER_PASSWORD: options.opencodePassword,
|
||||||
NODE_ENV: options.nodeEnv ?? 'production',
|
NODE_ENV: options.nodeEnv ?? 'production',
|
||||||
@@ -148,10 +177,27 @@ export const createRemoteEphemeralBootstrapCommand = (options: IRemoteEphemeralB
|
|||||||
const ideDataRoot = options.ideDataRoot ?? defaultIdeDataRoot;
|
const ideDataRoot = options.ideDataRoot ?? defaultIdeDataRoot;
|
||||||
const logsDir = joinRemotePath(ideDataRoot, 'logs');
|
const logsDir = joinRemotePath(ideDataRoot, 'logs');
|
||||||
const theiaConfigDir = joinRemotePath(ideDataRoot, 'theia');
|
const theiaConfigDir = joinRemotePath(ideDataRoot, 'theia');
|
||||||
|
const theiaSettingsPath = joinRemotePath(theiaConfigDir, 'settings.json');
|
||||||
const logFile = joinRemotePath(logsDir, `theia-${options.theiaPort}.log`);
|
const logFile = joinRemotePath(logsDir, `theia-${options.theiaPort}.log`);
|
||||||
|
const theiaColorTheme = options.theiaColorTheme ?? defaultTheiaColorTheme;
|
||||||
|
const themePreferenceScript = [
|
||||||
|
"const fs = require('fs');",
|
||||||
|
'const settingsPath = process.env.GITZONE_IDE_THEIA_SETTINGS;',
|
||||||
|
'const colorTheme = process.env.GITZONE_IDE_THEIA_COLOR_THEME;',
|
||||||
|
'let settings = {};',
|
||||||
|
'try {',
|
||||||
|
" const raw = fs.readFileSync(settingsPath, 'utf8').trim();",
|
||||||
|
' settings = raw ? JSON.parse(raw) : {};',
|
||||||
|
'} catch (error) {',
|
||||||
|
" if (!error || error.code !== 'ENOENT') throw error;",
|
||||||
|
'}',
|
||||||
|
"settings['workbench.colorTheme'] = colorTheme;",
|
||||||
|
"fs.writeFileSync(settingsPath, `${JSON.stringify(settings, undefined, 2)}\\n`);",
|
||||||
|
].join('\n');
|
||||||
const env = {
|
const env = {
|
||||||
GITZONE_IDE_WORKSPACE: options.workspacePath,
|
GITZONE_IDE_WORKSPACE: options.workspacePath,
|
||||||
GITZONE_IDE_OPENCODE_PORT: `${options.opencodePort}`,
|
GITZONE_IDE_OPENCODE_PORT: `${options.opencodePort}`,
|
||||||
|
GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART: '1',
|
||||||
OPENCODE_SERVER_USERNAME: options.opencodeUsername,
|
OPENCODE_SERVER_USERNAME: options.opencodeUsername,
|
||||||
OPENCODE_SERVER_PASSWORD: options.opencodePassword,
|
OPENCODE_SERVER_PASSWORD: options.opencodePassword,
|
||||||
NODE_ENV: options.nodeEnv ?? 'production',
|
NODE_ENV: options.nodeEnv ?? 'production',
|
||||||
@@ -160,11 +206,14 @@ export const createRemoteEphemeralBootstrapCommand = (options: IRemoteEphemeralB
|
|||||||
return [
|
return [
|
||||||
'set -euo pipefail',
|
'set -euo pipefail',
|
||||||
`mkdir -p ${quoteRemotePath(logsDir)} ${quoteRemotePath(theiaConfigDir)}`,
|
`mkdir -p ${quoteRemotePath(logsDir)} ${quoteRemotePath(theiaConfigDir)}`,
|
||||||
|
`export LD_LIBRARY_PATH=${quoteRemotePath(joinRemotePath(options.runtimeRoot, 'node/lib'))}:\${LD_LIBRARY_PATH:-}`,
|
||||||
`test -x ${quoteRemotePath(nodePath)} || { printf 'bundled node not executable: %s\n' ${quoteShellArg(nodePath)} >&2; exit 1; }`,
|
`test -x ${quoteRemotePath(nodePath)} || { printf 'bundled node not executable: %s\n' ${quoteShellArg(nodePath)} >&2; exit 1; }`,
|
||||||
`test -f ${quoteRemotePath(joinRemotePath(appDir, 'lib/backend/main.js'))} || { printf 'bundled Theia backend missing: %s\n' ${quoteShellArg(appDir)} >&2; exit 1; }`,
|
`test -f ${quoteRemotePath(joinRemotePath(appDir, 'lib/backend/main.js'))} || { printf 'bundled Theia backend missing: %s\n' ${quoteShellArg(appDir)} >&2; exit 1; }`,
|
||||||
`test -d ${quoteRemotePath(options.workspacePath)} || { printf 'workspace path not found: %s\n' ${quoteShellArg(options.workspacePath)} >&2; exit 1; }`,
|
`test -d ${quoteRemotePath(options.workspacePath)} || { printf 'workspace path not found: %s\n' ${quoteShellArg(options.workspacePath)} >&2; exit 1; }`,
|
||||||
|
`GITZONE_IDE_THEIA_SETTINGS=${quoteRemotePath(theiaSettingsPath)} GITZONE_IDE_THEIA_COLOR_THEME=${quoteShellArg(theiaColorTheme)} ${quoteRemotePath(nodePath)} <<'GITZONE_IDE_THEME'`,
|
||||||
|
themePreferenceScript,
|
||||||
|
'GITZONE_IDE_THEME',
|
||||||
`cd ${quoteRemotePath(options.workspacePath)}`,
|
`cd ${quoteRemotePath(options.workspacePath)}`,
|
||||||
`export LD_LIBRARY_PATH=${quoteRemotePath(joinRemotePath(options.runtimeRoot, 'node/lib'))}:\${LD_LIBRARY_PATH:-}`,
|
|
||||||
`export THEIA_CONFIG_DIR=${quoteRemotePath(theiaConfigDir)}`,
|
`export THEIA_CONFIG_DIR=${quoteRemotePath(theiaConfigDir)}`,
|
||||||
...Object.entries(env).map(([key, value]) => {
|
...Object.entries(env).map(([key, value]) => {
|
||||||
const renderedValue = key === 'GITZONE_IDE_WORKSPACE' ? quoteRemotePath(value) : quoteShellArg(value);
|
const renderedValue = key === 'GITZONE_IDE_WORKSPACE' ? quoteRemotePath(value) : quoteShellArg(value);
|
||||||
@@ -233,6 +282,107 @@ export const createRemoteEphemeralRuntimeMarkCommand = (options: IRemoteEphemera
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createRemoteEphemeralPortAllocationCommand = (options: IRemoteEphemeralPortAllocationOptions) => {
|
||||||
|
const nodePath = options.nodePath ?? joinRemotePath(options.runtimeRoot, 'node/bin/node');
|
||||||
|
const count = options.count ?? 1;
|
||||||
|
const script = [
|
||||||
|
"const net = require('net');",
|
||||||
|
`const count = ${JSON.stringify(count)};`,
|
||||||
|
'const ports = [];',
|
||||||
|
'const servers = [];',
|
||||||
|
'const listen = () => new Promise((resolve, reject) => {',
|
||||||
|
' const server = net.createServer();',
|
||||||
|
" server.on('error', reject);",
|
||||||
|
" server.listen(0, '127.0.0.1', () => {",
|
||||||
|
' ports.push(server.address().port);',
|
||||||
|
' servers.push(server);',
|
||||||
|
' resolve();',
|
||||||
|
' });',
|
||||||
|
'});',
|
||||||
|
'(async () => {',
|
||||||
|
' for (let index = 0; index < count; index++) await listen();',
|
||||||
|
" console.log(`ports=${ports.join(',')}`);",
|
||||||
|
' await Promise.all(servers.map((server) => new Promise((resolve) => server.close(resolve))));',
|
||||||
|
'})().catch((error) => { console.error(error.stack || String(error)); process.exit(1); });',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`export LD_LIBRARY_PATH=${quoteRemotePath(joinRemotePath(options.runtimeRoot, 'node/lib'))}:\${LD_LIBRARY_PATH:-}`,
|
||||||
|
`${quoteRemotePath(nodePath)} -e ${quoteShellArg(script)}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRemoteOpenCodeToolCommand = (options: IRemoteOpenCodeToolCommandOptions) => {
|
||||||
|
const nodePath = options.nodePath ?? joinRemotePath(options.runtimeRoot, 'node/bin/node');
|
||||||
|
const rgPath = options.rgPath ?? joinRemotePath(options.runtimeRoot, 'applications/remote-theia/lib/backend/native/rg');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`export LD_LIBRARY_PATH=${quoteRemotePath(joinRemotePath(options.runtimeRoot, 'node/lib'))}:\${LD_LIBRARY_PATH:-}`,
|
||||||
|
`export GITZONE_IDE_WORKSPACE=${quoteRemotePath(options.workspacePath)}`,
|
||||||
|
`export GITZONE_IDE_TOOL_NAME=${quoteShellArg(options.toolName)}`,
|
||||||
|
`export GITZONE_IDE_RG_PATH=${quoteRemotePath(rgPath)}`,
|
||||||
|
`${quoteRemotePath(nodePath)} -e ${quoteShellArg(remoteOpenCodeToolScript)}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRemoteProjectListCommand = (options: IRemoteProjectRegistryOptions) => {
|
||||||
|
const projectsFile = joinRemotePath(options.ideDataRoot ?? defaultIdeDataRoot, remoteProjectsFileName);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`if test -f ${quoteRemotePath(projectsFile)}; then`,
|
||||||
|
` cat ${quoteRemotePath(projectsFile)}`,
|
||||||
|
'else',
|
||||||
|
" printf '{\"projects\":[]}\n'",
|
||||||
|
'fi',
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRemoteProjectUpsertCommand = (options: IRemoteProjectUpsertOptions) => {
|
||||||
|
const ideDataRoot = options.ideDataRoot ?? defaultIdeDataRoot;
|
||||||
|
const projectsFile = joinRemotePath(ideDataRoot, remoteProjectsFileName);
|
||||||
|
const nodePath = options.nodePath ?? joinRemotePath(options.runtimeRoot, 'node/bin/node');
|
||||||
|
const script = [
|
||||||
|
"const crypto = require('crypto');",
|
||||||
|
"const fs = require('fs');",
|
||||||
|
"const path = require('path');",
|
||||||
|
'const projectsFile = process.env.GITZONE_IDE_PROJECTS_FILE;',
|
||||||
|
'const projectPath = process.env.GITZONE_IDE_PROJECT_PATH;',
|
||||||
|
'const title = process.env.GITZONE_IDE_PROJECT_TITLE || path.basename(projectPath) || projectPath;',
|
||||||
|
"let registry = { projects: [] };",
|
||||||
|
"try { registry = JSON.parse(fs.readFileSync(projectsFile, 'utf8')); } catch {}",
|
||||||
|
"if (!Array.isArray(registry.projects)) registry.projects = [];",
|
||||||
|
"const id = crypto.createHash('sha256').update(projectPath).digest('hex').slice(0, 16);",
|
||||||
|
'const now = new Date().toISOString();',
|
||||||
|
'const existing = registry.projects.find((project) => project.id === id || project.path === projectPath);',
|
||||||
|
'if (existing) {',
|
||||||
|
' existing.id = id;',
|
||||||
|
' existing.path = projectPath;',
|
||||||
|
' existing.title = title;',
|
||||||
|
' existing.updatedAt = now;',
|
||||||
|
'} else {',
|
||||||
|
' registry.projects.push({ id, path: projectPath, title, createdAt: now, updatedAt: now });',
|
||||||
|
'}',
|
||||||
|
'registry.projects.sort((left, right) => left.title.localeCompare(right.title));',
|
||||||
|
'fs.mkdirSync(path.dirname(projectsFile), { recursive: true });',
|
||||||
|
"fs.writeFileSync(`${projectsFile}.tmp`, `${JSON.stringify(registry, undefined, 2)}\\n`);",
|
||||||
|
"fs.renameSync(`${projectsFile}.tmp`, projectsFile);",
|
||||||
|
'console.log(JSON.stringify(registry));',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`export GITZONE_IDE_PROJECTS_FILE=${quoteRemotePath(projectsFile)}`,
|
||||||
|
`export GITZONE_IDE_PROJECT_PATH=${quoteRemotePath(options.projectPath)}`,
|
||||||
|
`export GITZONE_IDE_PROJECT_TITLE=${quoteShellArg(options.title ?? '')}`,
|
||||||
|
`test -d "$GITZONE_IDE_PROJECT_PATH" || { printf 'workspace path not found: %s\n' "$GITZONE_IDE_PROJECT_PATH" >&2; exit 1; }`,
|
||||||
|
`export LD_LIBRARY_PATH=${quoteRemotePath(joinRemotePath(options.runtimeRoot, 'node/lib'))}:\${LD_LIBRARY_PATH:-}`,
|
||||||
|
`${quoteRemotePath(nodePath)} -e ${quoteShellArg(script)}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
export const createRemoteHealthCommand = (serverVersion: string, installRoot = defaultInstallRoot) => {
|
export const createRemoteHealthCommand = (serverVersion: string, installRoot = defaultInstallRoot) => {
|
||||||
const plan = createRemoteServerInstallPlan({
|
const plan = createRemoteServerInstallPlan({
|
||||||
serverVersion,
|
serverVersion,
|
||||||
@@ -246,6 +396,199 @@ export const createRemoteHealthCommand = (serverVersion: string, installRoot = d
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const remoteOpenCodeToolScript = [
|
||||||
|
"const fs = require('fs');",
|
||||||
|
"const path = require('path');",
|
||||||
|
"const childProcess = require('child_process');",
|
||||||
|
"const toolName = process.env.GITZONE_IDE_TOOL_NAME;",
|
||||||
|
"const workspacePath = expandHome(process.env.GITZONE_IDE_WORKSPACE || process.cwd());",
|
||||||
|
"const rgPath = process.env.GITZONE_IDE_RG_PATH || 'rg';",
|
||||||
|
"const inputText = fs.readFileSync(0, 'utf8');",
|
||||||
|
"const request = inputText.trim() ? JSON.parse(inputText) : {};",
|
||||||
|
"const args = request.args || {};",
|
||||||
|
"const MAX_LINES = 2000;",
|
||||||
|
"const MAX_LINE_LENGTH = 2000;",
|
||||||
|
"const MAX_BYTES = 50 * 1024;",
|
||||||
|
"function expandHome(value) {",
|
||||||
|
" const text = String(value || '');",
|
||||||
|
" if (text === '~' || text === '$HOME') return process.env.HOME || text;",
|
||||||
|
" if (text.startsWith('~/')) return path.join(process.env.HOME || '', text.slice(2));",
|
||||||
|
" if (text.startsWith('$HOME/')) return path.join(process.env.HOME || '', text.slice(6));",
|
||||||
|
" return text;",
|
||||||
|
"}",
|
||||||
|
"function normalizeRemotePath(value, fallback) {",
|
||||||
|
" if (!value) return fallback;",
|
||||||
|
" const expanded = expandHome(value);",
|
||||||
|
" return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(fallback, expanded);",
|
||||||
|
"}",
|
||||||
|
"function limitLine(line) {",
|
||||||
|
" return line.length > MAX_LINE_LENGTH ? line.slice(0, MAX_LINE_LENGTH) + '... (line truncated to 2000 chars)' : line;",
|
||||||
|
"}",
|
||||||
|
"function writeJson(result) { process.stdout.write(JSON.stringify(result)); }",
|
||||||
|
"function ensureParent(filePath) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); }",
|
||||||
|
"function toLines(text) {",
|
||||||
|
" if (!text) return [];",
|
||||||
|
" const lines = text.split(/\\r?\\n/);",
|
||||||
|
" if (text.endsWith('\\n')) lines.pop();",
|
||||||
|
" return lines;",
|
||||||
|
"}",
|
||||||
|
"function readTool() {",
|
||||||
|
" const filePath = normalizeRemotePath(args.filePath, workspacePath);",
|
||||||
|
" const stat = fs.statSync(filePath);",
|
||||||
|
" const offset = Math.max(1, Number(args.offset || 1));",
|
||||||
|
" const limit = Math.max(0, Number(args.limit || MAX_LINES));",
|
||||||
|
" if (stat.isDirectory()) {",
|
||||||
|
" const entries = fs.readdirSync(filePath, { withFileTypes: true }).map((entry) => entry.name + (entry.isDirectory() ? '/' : '')).sort((a, b) => a.localeCompare(b));",
|
||||||
|
" const sliced = entries.slice(offset - 1, offset - 1 + limit);",
|
||||||
|
" const truncated = offset - 1 + sliced.length < entries.length;",
|
||||||
|
" return { output: ['<path>' + filePath + '</path>', '<type>directory</type>', '<entries>', sliced.join('\\n'), truncated ? '\\n(Showing ' + sliced.length + ' of ' + entries.length + ' entries. Use offset=' + (offset + sliced.length) + ' to continue.)' : '\\n(' + entries.length + ' entries)', '</entries>'].join('\\n'), metadata: { preview: sliced.slice(0, 20).join('\\n'), truncated } };",
|
||||||
|
" }",
|
||||||
|
" const buffer = fs.readFileSync(filePath);",
|
||||||
|
" if (buffer.includes(0)) throw new Error('Cannot read binary file: ' + filePath);",
|
||||||
|
" const lines = toLines(buffer.toString('utf8'));",
|
||||||
|
" if (offset > lines.length && !(lines.length === 0 && offset === 1)) throw new Error('Offset ' + offset + ' is out of range for this file (' + lines.length + ' lines)');",
|
||||||
|
" const raw = [];",
|
||||||
|
" let bytes = 0;",
|
||||||
|
" let cut = false;",
|
||||||
|
" for (const line of lines.slice(offset - 1)) {",
|
||||||
|
" if (raw.length >= limit) break;",
|
||||||
|
" const next = limitLine(line);",
|
||||||
|
" const size = Buffer.byteLength(next, 'utf8') + (raw.length > 0 ? 1 : 0);",
|
||||||
|
" if (bytes + size > MAX_BYTES) { cut = true; break; }",
|
||||||
|
" raw.push(next);",
|
||||||
|
" bytes += size;",
|
||||||
|
" }",
|
||||||
|
" const last = offset + raw.length - 1;",
|
||||||
|
" const more = cut || last < lines.length;",
|
||||||
|
" let output = '<path>' + filePath + '</path>\\n<type>file</type>\\n<content>\\n';",
|
||||||
|
" output += raw.map((line, index) => (index + offset) + ': ' + line).join('\\n');",
|
||||||
|
" output += more ? '\\n\\n(Showing lines ' + offset + '-' + last + ' of ' + lines.length + '. Use offset=' + (last + 1) + ' to continue.)' : '\\n\\n(End of file - total ' + lines.length + ' lines)';",
|
||||||
|
" output += '\\n</content>';",
|
||||||
|
" return { output, metadata: { preview: raw.slice(0, 20).join('\\n'), truncated: more } };",
|
||||||
|
"}",
|
||||||
|
"function writeTool() {",
|
||||||
|
" const filePath = normalizeRemotePath(args.filePath, workspacePath);",
|
||||||
|
" const existed = fs.existsSync(filePath);",
|
||||||
|
" ensureParent(filePath);",
|
||||||
|
" fs.writeFileSync(filePath, String(args.content || ''));",
|
||||||
|
" return { output: 'Wrote file successfully.', metadata: { filepath: filePath, exists: existed } };",
|
||||||
|
"}",
|
||||||
|
"function editTool() {",
|
||||||
|
" const filePath = normalizeRemotePath(args.filePath, workspacePath);",
|
||||||
|
" if (args.oldString === args.newString) throw new Error('No changes to apply: oldString and newString are identical.');",
|
||||||
|
" let content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';",
|
||||||
|
" let next;",
|
||||||
|
" if (args.oldString === '') {",
|
||||||
|
" next = String(args.newString || '');",
|
||||||
|
" } else {",
|
||||||
|
" const oldString = String(args.oldString || '');",
|
||||||
|
" const newString = String(args.newString || '');",
|
||||||
|
" const matches = content.split(oldString).length - 1;",
|
||||||
|
" if (matches === 0) throw new Error('Could not find oldString in the file. It must match exactly.');",
|
||||||
|
" if (!args.replaceAll && matches > 1) throw new Error('Found multiple matches for oldString. Provide more surrounding context or set replaceAll.');",
|
||||||
|
" next = args.replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);",
|
||||||
|
" }",
|
||||||
|
" ensureParent(filePath);",
|
||||||
|
" fs.writeFileSync(filePath, next);",
|
||||||
|
" return { output: 'Edit applied successfully.', metadata: { filepath: filePath } };",
|
||||||
|
"}",
|
||||||
|
"function bashTool() {",
|
||||||
|
" const cwd = normalizeRemotePath(args.workdir, workspacePath);",
|
||||||
|
" const timeout = Number(args.timeout || 120000);",
|
||||||
|
" const shell = process.env.SHELL || '/bin/sh';",
|
||||||
|
" const result = childProcess.spawnSync(shell, ['-lc', String(args.command || '')], { cwd, encoding: 'utf8', timeout, maxBuffer: 10 * 1024 * 1024 });",
|
||||||
|
" const outputText = [result.stdout || '', result.stderr || ''].filter(Boolean).join('');",
|
||||||
|
" const metadata = [];",
|
||||||
|
" if (result.error && result.error.code === 'ETIMEDOUT') metadata.push('remote shell tool terminated command after exceeding timeout ' + timeout + ' ms.');",
|
||||||
|
" metadata.push('exit=' + (result.status === null || result.status === undefined ? 1 : result.status));",
|
||||||
|
" const output = (outputText.trim() ? outputText.replace(/\\s+$/g, '') : '(no output)') + '\\n\\n<shell_metadata>\\n' + metadata.join('\\n') + '\\n</shell_metadata>';",
|
||||||
|
" return { output, metadata: { exit: result.status, cwd } };",
|
||||||
|
"}",
|
||||||
|
"function grepTool() {",
|
||||||
|
" if (!args.pattern) throw new Error('pattern is required');",
|
||||||
|
" const search = normalizeRemotePath(args.path, workspacePath);",
|
||||||
|
" const executable = fs.existsSync(rgPath) ? rgPath : 'rg';",
|
||||||
|
" const rgArgs = ['--line-number', '--with-filename', '--color', 'never', '--no-heading'];",
|
||||||
|
" if (args.include) rgArgs.push('--glob', String(args.include));",
|
||||||
|
" rgArgs.push('--', String(args.pattern), search);",
|
||||||
|
" const result = childProcess.spawnSync(executable, rgArgs, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 });",
|
||||||
|
" if (result.status !== 0 && result.status !== 1) throw new Error(result.stderr || ('ripgrep failed with exit ' + result.status));",
|
||||||
|
" const rows = (result.stdout || '').split(/\\r?\\n/).filter(Boolean).map((line) => { const match = line.match(/^(.*?):(\\d+):(.*)$/); if (!match) return undefined; const filePath = path.resolve(match[1]); let mtime = 0; try { mtime = fs.statSync(filePath).mtimeMs; } catch {} return { path: filePath, line: Number(match[2]), text: match[3], mtime }; }).filter(Boolean);",
|
||||||
|
" rows.sort((left, right) => right.mtime - left.mtime);",
|
||||||
|
" const limit = 100;",
|
||||||
|
" const truncated = rows.length > limit;",
|
||||||
|
" const finalRows = truncated ? rows.slice(0, limit) : rows;",
|
||||||
|
" if (finalRows.length === 0) return { output: 'No files found', metadata: { matches: 0, truncated: false } };",
|
||||||
|
" const output = ['Found ' + rows.length + ' matches' + (truncated ? ' (showing first ' + limit + ')' : '')];",
|
||||||
|
" let current = '';",
|
||||||
|
" for (const row of finalRows) { if (current !== row.path) { if (current) output.push(''); current = row.path; output.push(row.path + ':'); } output.push(' Line ' + row.line + ': ' + limitLine(row.text)); }",
|
||||||
|
" if (truncated) output.push('', '(Results truncated: showing ' + limit + ' of ' + rows.length + ' matches.)');",
|
||||||
|
" return { output: output.join('\\n'), metadata: { matches: rows.length, truncated } };",
|
||||||
|
"}",
|
||||||
|
"function globTool() {",
|
||||||
|
" if (!args.pattern) throw new Error('pattern is required');",
|
||||||
|
" const search = normalizeRemotePath(args.path, workspacePath);",
|
||||||
|
" const executable = fs.existsSync(rgPath) ? rgPath : 'rg';",
|
||||||
|
" const result = childProcess.spawnSync(executable, ['--files', '--color', 'never', '--glob', String(args.pattern), '--', search], { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 });",
|
||||||
|
" if (result.status !== 0 && result.status !== 1) throw new Error(result.stderr || ('ripgrep failed with exit ' + result.status));",
|
||||||
|
" const files = (result.stdout || '').split(/\\r?\\n/).filter(Boolean).map((entry) => path.isAbsolute(entry) ? entry : path.resolve(search, entry)).map((filePath) => { let mtime = 0; try { mtime = fs.statSync(filePath).mtimeMs; } catch {} return { path: filePath, mtime }; }).sort((left, right) => right.mtime - left.mtime);",
|
||||||
|
" const limit = 100;",
|
||||||
|
" const truncated = files.length > limit;",
|
||||||
|
" const finalFiles = truncated ? files.slice(0, limit) : files;",
|
||||||
|
" const output = finalFiles.length ? finalFiles.map((file) => file.path) : ['No files found'];",
|
||||||
|
" if (truncated) output.push('', '(Results are truncated: showing first ' + limit + ' results.)');",
|
||||||
|
" return { output: output.join('\\n'), metadata: { count: finalFiles.length, truncated } };",
|
||||||
|
"}",
|
||||||
|
"function parsePatch(text) {",
|
||||||
|
" const lines = String(text || '').replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n').split('\\n');",
|
||||||
|
" if (lines[0] !== '*** Begin Patch') throw new Error('apply_patch verification failed: missing begin marker');",
|
||||||
|
" const ops = [];",
|
||||||
|
" let index = 1;",
|
||||||
|
" while (index < lines.length) {",
|
||||||
|
" const line = lines[index];",
|
||||||
|
" if (line === '*** End Patch') break;",
|
||||||
|
" if (line.startsWith('*** Add File: ')) { const file = line.slice(14); const body = []; index++; while (index < lines.length && !lines[index].startsWith('*** ')) { if (!lines[index].startsWith('+')) throw new Error('add file lines must start with +'); body.push(lines[index].slice(1)); index++; } ops.push({ type: 'add', file, body }); continue; }",
|
||||||
|
" if (line.startsWith('*** Delete File: ')) { ops.push({ type: 'delete', file: line.slice(17) }); index++; continue; }",
|
||||||
|
" if (line.startsWith('*** Update File: ')) { const file = line.slice(17); const body = []; let moveTo; index++; if (lines[index] && lines[index].startsWith('*** Move to: ')) { moveTo = lines[index].slice(13); index++; } while (index < lines.length && !lines[index].startsWith('*** ')) { body.push(lines[index]); index++; } ops.push({ type: 'update', file, moveTo, body }); continue; }",
|
||||||
|
" throw new Error('apply_patch verification failed: unknown patch line ' + line);",
|
||||||
|
" }",
|
||||||
|
" if (ops.length === 0) throw new Error('patch rejected: empty patch');",
|
||||||
|
" return ops;",
|
||||||
|
"}",
|
||||||
|
"function replaceHunk(content, oldLines, newLines) {",
|
||||||
|
" const oldText = oldLines.join('\\n');",
|
||||||
|
" const newText = newLines.join('\\n');",
|
||||||
|
" const candidates = oldLines.length === 0 ? [['', newText]] : [[oldText + '\\n', newText + '\\n'], [oldText, newText]];",
|
||||||
|
" for (const pair of candidates) { const from = pair[0]; const to = pair[1]; const position = from === '' ? content.length : content.indexOf(from); if (position !== -1) return content.slice(0, position) + to + content.slice(position + from.length); }",
|
||||||
|
" throw new Error('apply_patch verification failed: hunk context not found');",
|
||||||
|
"}",
|
||||||
|
"function applyPatchTool() {",
|
||||||
|
" const ops = parsePatch(args.patchText);",
|
||||||
|
" const summary = [];",
|
||||||
|
" for (const op of ops) {",
|
||||||
|
" const filePath = normalizeRemotePath(op.file, workspacePath);",
|
||||||
|
" if (op.type === 'add') { ensureParent(filePath); fs.writeFileSync(filePath, op.body.join('\\n') + (op.body.length ? '\\n' : '')); summary.push('A ' + path.relative(workspacePath, filePath)); continue; }",
|
||||||
|
" if (op.type === 'delete') { fs.rmSync(filePath, { force: true }); summary.push('D ' + path.relative(workspacePath, filePath)); continue; }",
|
||||||
|
" let content = fs.readFileSync(filePath, 'utf8');",
|
||||||
|
" const groups = []; let current = [];",
|
||||||
|
" for (const line of op.body) { if (line.startsWith('@@')) { if (current.length) groups.push(current); current = []; continue; } current.push(line); }",
|
||||||
|
" if (current.length) groups.push(current);",
|
||||||
|
" for (const group of groups) { const oldLines = []; const newLines = []; for (const line of group) { if (!line) continue; const marker = line[0]; const value = line.slice(1); if (marker === ' ') { oldLines.push(value); newLines.push(value); } else if (marker === '-') { oldLines.push(value); } else if (marker === '+') { newLines.push(value); } else if (line.startsWith('\\\\ No newline')) { } else { throw new Error('apply_patch verification failed: invalid hunk line ' + line); } } content = replaceHunk(content, oldLines, newLines); }",
|
||||||
|
" const targetPath = op.moveTo ? normalizeRemotePath(op.moveTo, workspacePath) : filePath;",
|
||||||
|
" ensureParent(targetPath); fs.writeFileSync(targetPath, content); if (op.moveTo) fs.rmSync(filePath, { force: true }); summary.push('M ' + path.relative(workspacePath, targetPath));",
|
||||||
|
" }",
|
||||||
|
" return { output: 'Success. Updated the following files:\\n' + summary.join('\\n'), metadata: { files: summary } };",
|
||||||
|
"}",
|
||||||
|
"try {",
|
||||||
|
" const handlers = { bash: bashTool, read: readTool, write: writeTool, edit: editTool, grep: grepTool, glob: globTool, apply_patch: applyPatchTool };",
|
||||||
|
" if (!handlers[toolName]) throw new Error('Unsupported Git.Zone OpenCode tool: ' + toolName);",
|
||||||
|
" writeJson(handlers[toolName]());",
|
||||||
|
"} catch (error) {",
|
||||||
|
" console.error(error && error.stack ? error.stack : String(error));",
|
||||||
|
" process.exit(1);",
|
||||||
|
"}",
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
export const quoteShellArg = (value: string | number | boolean) => {
|
export const quoteShellArg = (value: string | number | boolean) => {
|
||||||
const stringValue = String(value);
|
const stringValue = String(value);
|
||||||
if (stringValue.length === 0) {
|
if (stringValue.length === 0) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface ISshRunOptions {
|
|||||||
batchMode?: boolean;
|
batchMode?: boolean;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
env?: NodeJS.ProcessEnv;
|
env?: NodeJS.ProcessEnv;
|
||||||
|
stdin?: string | Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISshRunResult {
|
export interface ISshRunResult {
|
||||||
@@ -232,7 +233,7 @@ export const runSshCommand = async (
|
|||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
env,
|
env,
|
||||||
shell: false,
|
shell: false,
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: options.stdin === undefined ? ['ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'],
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -247,8 +248,11 @@ export const runSshCommand = async (
|
|||||||
}, options.timeoutMs)
|
}, options.timeoutMs)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));
|
child.stdout!.on('data', (chunk: Buffer) => stdout.push(chunk));
|
||||||
child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));
|
child.stderr!.on('data', (chunk: Buffer) => stderr.push(chunk));
|
||||||
|
if (options.stdin !== undefined) {
|
||||||
|
child.stdin!.end(options.stdin);
|
||||||
|
}
|
||||||
child.on('error', (error) => {
|
child.on('error', (error) => {
|
||||||
finished = true;
|
finished = true;
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
|
|||||||
Generated
+3
@@ -32,6 +32,9 @@ importers:
|
|||||||
|
|
||||||
applications/electron-shell:
|
applications/electron-shell:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@git.zone/ide-opencode-bridge':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/opencode-bridge
|
||||||
'@git.zone/ide-protocol':
|
'@git.zone/ide-protocol':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/protocol
|
version: link:../../packages/protocol
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as childProcess from 'node:child_process';
|
||||||
|
import * as fs from 'node:fs/promises';
|
||||||
|
import * as os from 'node:os';
|
||||||
|
import * as path from 'node:path';
|
||||||
import {
|
import {
|
||||||
createRemoteEphemeralBootstrapCommand,
|
createRemoteEphemeralBootstrapCommand,
|
||||||
createRemoteEphemeralReadinessCommand,
|
createRemoteEphemeralReadinessCommand,
|
||||||
|
createRemoteEphemeralPortAllocationCommand,
|
||||||
createRemoteEphemeralRuntimeCacheCheckCommand,
|
createRemoteEphemeralRuntimeCacheCheckCommand,
|
||||||
createRemoteEphemeralRuntimeMarkCommand,
|
createRemoteEphemeralRuntimeMarkCommand,
|
||||||
|
createRemoteOpenCodeToolCommand,
|
||||||
|
createRemoteProjectListCommand,
|
||||||
|
createRemoteProjectUpsertCommand,
|
||||||
|
remoteOpenCodeToolScript,
|
||||||
createRemoteBootstrapCommand,
|
createRemoteBootstrapCommand,
|
||||||
createRemoteInstallCommand,
|
createRemoteInstallCommand,
|
||||||
createRemoteServerInstallPlan,
|
createRemoteServerInstallPlan,
|
||||||
@@ -51,6 +60,7 @@ tap.test('should render install and bootstrap commands', async () => {
|
|||||||
expect(installCommand).toInclude('GITZONE_IDE_MANIFEST');
|
expect(installCommand).toInclude('GITZONE_IDE_MANIFEST');
|
||||||
expect(installCommand).toInclude('"$HOME"/\'.git.zone/ide/server/0.1.0\'');
|
expect(installCommand).toInclude('"$HOME"/\'.git.zone/ide/server/0.1.0\'');
|
||||||
expect(bootstrapCommand).toInclude('GITZONE_IDE_OPENCODE_PORT');
|
expect(bootstrapCommand).toInclude('GITZONE_IDE_OPENCODE_PORT');
|
||||||
|
expect(bootstrapCommand).toInclude('GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART');
|
||||||
expect(bootstrapCommand).toInclude('pnpm --dir');
|
expect(bootstrapCommand).toInclude('pnpm --dir');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,6 +95,9 @@ tap.test('should render ephemeral runtime bootstrap without remote pnpm', async
|
|||||||
expect(bootstrapCommand).not.toInclude('pnpm');
|
expect(bootstrapCommand).not.toInclude('pnpm');
|
||||||
expect(bootstrapCommand).toInclude('LD_LIBRARY_PATH');
|
expect(bootstrapCommand).toInclude('LD_LIBRARY_PATH');
|
||||||
expect(bootstrapCommand).toInclude('THEIA_CONFIG_DIR="$HOME"/\'.git.zone/ide/theia\'');
|
expect(bootstrapCommand).toInclude('THEIA_CONFIG_DIR="$HOME"/\'.git.zone/ide/theia\'');
|
||||||
|
expect(bootstrapCommand).toInclude('GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART=\'1\'');
|
||||||
|
expect(bootstrapCommand).toInclude('GITZONE_IDE_THEIA_COLOR_THEME=\'dark\'');
|
||||||
|
expect(bootstrapCommand).toInclude("settings['workbench.colorTheme'] = colorTheme");
|
||||||
expect(bootstrapCommand).toInclude('"$HOME"/\'.git.zone/ide/logs\'');
|
expect(bootstrapCommand).toInclude('"$HOME"/\'.git.zone/ide/logs\'');
|
||||||
expect(bootstrapCommand).toInclude('runtimeRoot=');
|
expect(bootstrapCommand).toInclude('runtimeRoot=');
|
||||||
});
|
});
|
||||||
@@ -126,4 +139,86 @@ tap.test('should render ephemeral runtime mark command', async () => {
|
|||||||
expect(markCommand).toInclude('runtimeCache=stored');
|
expect(markCommand).toInclude('runtimeCache=stored');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tap.test('should render remote port allocation command', async () => {
|
||||||
|
const portCommand = createRemoteEphemeralPortAllocationCommand({
|
||||||
|
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
|
||||||
|
count: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(portCommand).toInclude('/tmp/gitzone-ide-runtime-test/node/bin/node');
|
||||||
|
expect(portCommand).toInclude('ports=');
|
||||||
|
expect(portCommand).toInclude('LD_LIBRARY_PATH');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should render remote OpenCode tool bridge command', async () => {
|
||||||
|
const command = createRemoteOpenCodeToolCommand({
|
||||||
|
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
|
||||||
|
workspacePath: '$HOME/project',
|
||||||
|
toolName: 'read',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(command).toInclude('/tmp/gitzone-ide-runtime-test/node/bin/node');
|
||||||
|
expect(command).toInclude('GITZONE_IDE_TOOL_NAME=\'read\'');
|
||||||
|
expect(command).toInclude('GITZONE_IDE_WORKSPACE="$HOME"/\'project\'');
|
||||||
|
expect(command).toInclude('GITZONE_IDE_RG_PATH');
|
||||||
|
expect(command).toInclude('fs.readFileSync(0');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should execute remote OpenCode tool script with stdin payloads', async () => {
|
||||||
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitzone-opencode-tool-'));
|
||||||
|
const filePath = path.join(tempDir, 'sample.txt');
|
||||||
|
await fs.writeFile(filePath, 'hello\nworld\n');
|
||||||
|
|
||||||
|
const readResult = runRemoteOpenCodeToolScript('read', tempDir, { filePath: 'sample.txt' });
|
||||||
|
expect(readResult.output).toInclude('1: hello');
|
||||||
|
|
||||||
|
const editResult = runRemoteOpenCodeToolScript('edit', tempDir, {
|
||||||
|
filePath: 'sample.txt',
|
||||||
|
oldString: 'world',
|
||||||
|
newString: 'remote',
|
||||||
|
});
|
||||||
|
expect(editResult.output).toInclude('Edit applied successfully');
|
||||||
|
expect(await fs.readFile(filePath, 'utf8')).toEqual('hello\nremote\n');
|
||||||
|
|
||||||
|
const patchResult = runRemoteOpenCodeToolScript('apply_patch', tempDir, {
|
||||||
|
patchText: '*** Begin Patch\n*** Add File: nested/new.txt\n+created remotely\n*** End Patch',
|
||||||
|
});
|
||||||
|
expect(patchResult.output).toInclude('A nested/new.txt');
|
||||||
|
expect(await fs.readFile(path.join(tempDir, 'nested', 'new.txt'), 'utf8')).toEqual('created remotely\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should render remote project registry commands', async () => {
|
||||||
|
const listCommand = createRemoteProjectListCommand({
|
||||||
|
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
|
||||||
|
});
|
||||||
|
const upsertCommand = createRemoteProjectUpsertCommand({
|
||||||
|
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
|
||||||
|
projectPath: '$HOME/project',
|
||||||
|
title: 'Project',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listCommand).toInclude('projects.json');
|
||||||
|
expect(listCommand).toInclude('{"projects":[]}');
|
||||||
|
expect(upsertCommand).toInclude('GITZONE_IDE_PROJECT_PATH="$HOME"/\'project\'');
|
||||||
|
expect(upsertCommand).toInclude('test -d "$GITZONE_IDE_PROJECT_PATH"');
|
||||||
|
expect(upsertCommand).toInclude('crypto.createHash');
|
||||||
|
});
|
||||||
|
|
||||||
|
const runRemoteOpenCodeToolScript = (toolName: string, workspacePath: string, args: Record<string, unknown>) => {
|
||||||
|
const result = childProcess.spawnSync(process.execPath, ['-e', remoteOpenCodeToolScript], {
|
||||||
|
input: JSON.stringify({ args }),
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
GITZONE_IDE_TOOL_NAME: toolName,
|
||||||
|
GITZONE_IDE_WORKSPACE: workspacePath,
|
||||||
|
GITZONE_IDE_RG_PATH: '/not-found/rg',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error(result.stderr || `tool script failed with ${result.status}`);
|
||||||
|
}
|
||||||
|
return JSON.parse(result.stdout) as { output: string; metadata?: Record<string, unknown> };
|
||||||
|
};
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import {
|
||||||
|
openCodeBridgeToolNames,
|
||||||
|
renderOpenCodeBridgeConfigContent,
|
||||||
|
renderOpenCodeBridgeToolFile,
|
||||||
|
renderOpenCodeBridgeToolFiles,
|
||||||
|
} from '../packages/opencode-bridge/ts/index.js';
|
||||||
|
|
||||||
|
tap.test('should render managed OpenCode bridge config', async () => {
|
||||||
|
const config = JSON.parse(renderOpenCodeBridgeConfigContent());
|
||||||
|
|
||||||
|
expect(config.snapshot).toEqual(false);
|
||||||
|
expect(config.autoupdate).toEqual(false);
|
||||||
|
expect(config.permission.lsp).toEqual('deny');
|
||||||
|
expect(config.permission.skill).toEqual('deny');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should render tool overrides for remote bridge', async () => {
|
||||||
|
const files = renderOpenCodeBridgeToolFiles();
|
||||||
|
|
||||||
|
for (const toolName of openCodeBridgeToolNames) {
|
||||||
|
expect(files[`tools/${toolName}.js`]).toInclude(`forwardTool(${JSON.stringify(toolName)}`);
|
||||||
|
}
|
||||||
|
expect(files['tools/bash.js']).toInclude('GITZONE_IDE_TOOL_BRIDGE_URL');
|
||||||
|
expect(files['tools/apply_patch.js']).toInclude('patchText');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should allow custom bridge environment names', async () => {
|
||||||
|
const toolFile = renderOpenCodeBridgeToolFile('read', {
|
||||||
|
bridgeUrlEnvName: 'CUSTOM_BRIDGE_URL',
|
||||||
|
bridgeTokenEnvName: 'CUSTOM_BRIDGE_TOKEN',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(toolFile).toInclude('CUSTOM_BRIDGE_URL');
|
||||||
|
expect(toolFile).toInclude('CUSTOM_BRIDGE_TOKEN');
|
||||||
|
expect(toolFile).toInclude('filePath');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -8,7 +8,9 @@ tap.test('should keep Theia backend config under Git.Zone IDE home path', async
|
|||||||
const packageJsonPath = path.join(process.cwd(), 'applications', 'remote-theia', 'package.json');
|
const packageJsonPath = path.join(process.cwd(), 'applications', 'remote-theia', 'package.json');
|
||||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
expect(packageJson.theia.backend.config.singleInstance).toEqual(false);
|
||||||
expect(packageJson.theia.backend.config.configurationFolder).toEqual('.git.zone/ide/theia');
|
expect(packageJson.theia.backend.config.configurationFolder).toEqual('.git.zone/ide/theia');
|
||||||
|
expect(packageJson.theia.frontend.config.defaultTheme).toEqual('dark');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should avoid legacy .theia workspace preference folders', async () => {
|
tap.test('should avoid legacy .theia workspace preference folders', async () => {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"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;IAIlB,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;IA2BtD,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"}
|
{"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"}
|
||||||
@@ -38,8 +38,10 @@ let GitZoneOpenCodeNodeService = class GitZoneOpenCodeNodeService {
|
|||||||
eventAbortController;
|
eventAbortController;
|
||||||
openCodeProcess;
|
openCodeProcess;
|
||||||
initialize() {
|
initialize() {
|
||||||
|
if (this.autoStart) {
|
||||||
void this.ensureOpenCodeStarted();
|
void this.ensureOpenCodeStarted();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
onStop() {
|
onStop() {
|
||||||
this.eventAbortController?.abort();
|
this.eventAbortController?.abort();
|
||||||
if (this.openCodeProcess && this.openCodeProcess.exitCode === null) {
|
if (this.openCodeProcess && this.openCodeProcess.exitCode === null) {
|
||||||
@@ -119,6 +121,13 @@ let GitZoneOpenCodeNodeService = class GitZoneOpenCodeNodeService {
|
|||||||
stdio: ['ignore', 'ignore', 'ignore'],
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
windowsHide: true,
|
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() {
|
restartEventStream() {
|
||||||
this.eventAbortController?.abort();
|
this.eventAbortController?.abort();
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"gitzone-opencode-node-service.js","sourceRoot":"","sources":["../../src/node/gitzone-opencode-node-service.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"gitzone-opencode-node-service.js","sourceRoot":"","sources":["../../src/node/gitzone-opencode-node-service.ts"],"names":[],"mappings":"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"}
|
||||||
@@ -16,8 +16,10 @@ export class GitZoneOpenCodeNodeService implements IGitZoneOpenCodeServer, Backe
|
|||||||
protected openCodeProcess: plugins.childProcess.ChildProcess | undefined;
|
protected openCodeProcess: plugins.childProcess.ChildProcess | undefined;
|
||||||
|
|
||||||
initialize(): void {
|
initialize(): void {
|
||||||
|
if (this.autoStart) {
|
||||||
void this.ensureOpenCodeStarted();
|
void this.ensureOpenCodeStarted();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onStop(): void {
|
onStop(): void {
|
||||||
this.eventAbortController?.abort();
|
this.eventAbortController?.abort();
|
||||||
@@ -123,6 +125,13 @@ export class GitZoneOpenCodeNodeService implements IGitZoneOpenCodeServer, Backe
|
|||||||
windowsHide: true,
|
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 {
|
protected restartEventStream(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user