[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
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:
192
infrastructure/ai/cattyAgent/executor.ts
Normal file
192
infrastructure/ai/cattyAgent/executor.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user