[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
import type { ToolCall, ToolResult, AIPermissionMode, WebSearchConfig } from '../types';
import type {
TerminalContextReader,
} from '../../../domain/terminalContextRead';
import {
executeTerminalExecute,
executeWorkspaceGetInfo,
executeWorkspaceGetSessionInfo,
executeWebSearch,
executeUrlFetch,
type ToolDeps,
type ToolExecResult,
} from '../shared/toolExecutors';
import { fitTerminalExecuteResultForModel } from '../harness/terminalCompression';
/**
* Bridge interface for Catty Agent to interact with the Electron main process.
* This mirrors the AI-related subset of window.netcatty from electron/preload.cjs.
*/
export interface NetcattyBridge {
aiExec(
sessionId: string,
command: string,
chatSessionId?: string,
): Promise<{
ok: boolean;
stdout?: string;
stderr?: string;
exitCode?: number;
error?: string;
}>;
/**
* Cancel any in-flight Catty Agent command execution scoped to the
* given chat session. Idempotent — safe to call when nothing is
* running. Used by tools to re-issue cancel during the IPC transit
* window if the user clicks Stop after we've already dispatched
* `aiExec` but before the main process has registered it.
*/
aiCattyCancelExec?(chatSessionId: string): Promise<unknown>;
aiSetChatSessionCancelled?(chatSessionId: string, cancelled?: boolean): Promise<{ ok: boolean; error?: string }>;
aiCapability?(
rpcMethod: string,
params: Record<string, unknown>,
chatSessionId?: string,
): Promise<unknown>;
}
// Workspace context provided to the executor
export interface ExecutorContext {
// Available sessions in scope
sessions: Array<{
sessionId: string;
hostId: string;
hostname: string;
label: string;
os?: string;
username?: string;
protocol?: string;
shellType?: string;
deviceType?: string;
connected: boolean;
}>;
// Workspace info
workspaceId?: string;
workspaceName?: string;
readTerminalContext?: TerminalContextReader;
}
/** Convert a shared ToolExecResult into the executor's ToolResult format. */
function toToolResult(toolCallId: string, r: ToolExecResult): ToolResult {
if (r.ok === false) {
if (
typeof r.data === 'object'
&& r.data !== null
&& 'stdout' in r.data
&& 'stderr' in r.data
&& 'exitCode' in r.data
) {
const fitted = fitTerminalExecuteResultForModel(r.data as {
stdout: string;
stderr: string;
exitCode: number | null;
});
const output = [
r.error,
fitted.stdout ? `Partial output:\n${fitted.stdout}` : '',
fitted.stderr ? `Stderr:\n${fitted.stderr}` : '',
].filter(Boolean).join('\n\n');
return { toolCallId, content: output, isError: true };
}
return { toolCallId, content: r.error, isError: true };
}
// For terminal_execute, format as the legacy STDOUT/STDERR/exitCode text block
if (
typeof r.data === 'object' &&
r.data !== null &&
'stdout' in r.data &&
'stderr' in r.data &&
'exitCode' in r.data
) {
const d = r.data as { stdout: string; stderr: string; exitCode: number };
const output = [
d.stdout ? `STDOUT:\n${d.stdout}` : '',
d.stderr ? `STDERR:\n${d.stderr}` : '',
`Exit code: ${d.exitCode === -1 ? 'unknown' : d.exitCode}`,
]
.filter(Boolean)
.join('\n\n');
return { toolCallId, content: output || 'Command completed (no output)' };
}
// Default: JSON-serialize the data
return { toolCallId, content: JSON.stringify(r.data, null, 2) };
}
/**
* Create a tool executor function for the Catty Agent.
* This bridges tool calls to the netcatty Electron IPC layer.
*/
export function createToolExecutor(
bridge: NetcattyBridge | undefined,
context: ExecutorContext,
commandBlocklist?: string[],
permissionMode: AIPermissionMode = 'confirm',
webSearchConfig?: WebSearchConfig,
chatSessionId?: string,
): (toolCall: ToolCall) => Promise<ToolResult> {
return async (toolCall: ToolCall): Promise<ToolResult> => {
if (!bridge) {
return {
toolCallId: toolCall.id,
content: 'Netcatty bridge is not available',
isError: true,
};
}
const deps: ToolDeps = { bridge, context, commandBlocklist, permissionMode, webSearchConfig, chatSessionId };
const args = toolCall.arguments;
try {
switch (toolCall.name) {
case 'terminal_execute': {
const r = await executeTerminalExecute(deps, {
sessionId: String(args.sessionId || ''),
command: String(args.command || ''),
});
return toToolResult(toolCall.id, r);
}
case 'workspace_get_info': {
const r = executeWorkspaceGetInfo(deps);
return toToolResult(toolCall.id, r);
}
case 'workspace_get_session_info': {
const r = executeWorkspaceGetSessionInfo(deps, {
sessionId: String(args.sessionId || ''),
});
return toToolResult(toolCall.id, r);
}
case 'web_search': {
const r = await executeWebSearch(deps, {
query: String(args.query || ''),
maxResults: Number(args.maxResults) || 5,
});
return toToolResult(toolCall.id, r);
}
case 'url_fetch': {
const r = await executeUrlFetch(deps, {
url: String(args.url || ''),
maxLength: Number(args.maxLength) || 50000,
});
return toToolResult(toolCall.id, r);
}
default:
return {
toolCallId: toolCall.id,
content: `Unknown tool: ${toolCall.name}`,
isError: true,
};
}
} catch (err) {
return {
toolCallId: toolCall.id,
content: `Tool execution error: ${err instanceof Error ? err.message : String(err)}`,
isError: true,
};
}
};
}