[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,147 @@
import type React from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { retainStableAiPanelContexts } from '../../domain/aiPanelContextsEqual';
import {
buildAITerminalSessionInfo,
type AIPanelContext,
} from '../../domain/buildAITerminalSessionInfo';
import { collectSessionIds } from '../../domain/workspace';
import type { TerminalContextReader } from '../../domain/terminalContextRead';
import { detectLocalOs } from '../../lib/localShell';
import type { Host, PortForwardingRule, TerminalSession, Workspace } from '../../types';
interface UseTerminalAiContextsOptions {
hosts: Host[];
hostsRef: React.MutableRefObject<Host[]>;
portForwardingRules: PortForwardingRule[];
portForwardingRulesRef: React.MutableRefObject<PortForwardingRule[]>;
mountedAiTabIds: string[];
sessionHostsMap: Map<string, Host>;
sessions: TerminalSession[];
sessionsRef: React.MutableRefObject<TerminalSession[]>;
terminalContextReadersRef: React.MutableRefObject<Map<string, TerminalContextReader>>;
workspaces: Workspace[];
workspacesRef: React.MutableRefObject<Workspace[]>;
}
export function useTerminalAiContexts({
hosts,
hostsRef,
portForwardingRules,
portForwardingRulesRef,
mountedAiTabIds,
sessionHostsMap,
sessions,
sessionsRef,
terminalContextReadersRef,
workspaces,
workspacesRef,
}: UseTerminalAiContextsOptions) {
const previousAiContextsRef = useRef<Map<string, AIPanelContext> | null>(null);
const aiContextsByTabId = useMemo(() => {
const localOs = detectLocalOs(navigator.userAgent || navigator.platform);
const sessionById = new Map<string, TerminalSession>(sessions.map((session) => [session.id, session]));
const workspaceById = new Map<string, Workspace>(workspaces.map((workspace) => [workspace.id, workspace]));
const allHosts = hosts;
const tabIds = new Set<string>(mountedAiTabIds);
const contexts = new Map<string, AIPanelContext>();
for (const tabId of tabIds) {
const workspace = workspaceById.get(tabId);
if (workspace) {
const sessionIds = collectSessionIds(workspace.root);
contexts.set(tabId, {
scopeType: 'workspace',
scopeTargetId: workspace.id,
scopeHostIds: sessionIds
.map((sessionId) => sessionById.get(sessionId)?.hostId)
.filter((hostId): hostId is string => !!hostId),
scopeLabel: workspace.title,
focusedSessionId: workspace.focusedSessionId,
terminalSessions: sessionIds.map((sessionId) =>
buildAITerminalSessionInfo(
sessionById.get(sessionId),
sessionHostsMap.get(sessionId),
localOs,
{ allHosts, portForwardingRules },
),
),
});
continue;
}
const session = sessionById.get(tabId);
if (!session) continue;
contexts.set(tabId, {
scopeType: 'terminal',
scopeTargetId: session.id,
scopeHostIds: session.hostId ? [session.hostId] : [],
scopeLabel: session.hostLabel ?? '',
terminalSessions: [
buildAITerminalSessionInfo(
session,
sessionHostsMap.get(session.id),
localOs,
{ allHosts, portForwardingRules },
),
],
});
}
const retained = retainStableAiPanelContexts(previousAiContextsRef.current, contexts);
previousAiContextsRef.current = retained;
return retained;
}, [sessions, workspaces, mountedAiTabIds, sessionHostsMap, hosts, portForwardingRules]);
const resolveAIExecutorContext = useCallback((scope: {
type: 'terminal' | 'workspace';
targetId?: string;
label?: string;
}) => {
const latestWorkspaces = workspacesRef.current;
const latestSessions = sessionsRef.current;
const latestHosts = hostsRef.current;
const localOs = detectLocalOs(navigator.userAgent || navigator.platform);
const sessionIds = scope.type === 'workspace'
? (() => {
const workspace = scope.targetId ? latestWorkspaces.find((w) => w.id === scope.targetId) : undefined;
return workspace?.root ? collectSessionIds(workspace.root) : [];
})()
: scope.targetId ? [scope.targetId] : [];
const workspaceName = scope.type === 'workspace'
? latestWorkspaces.find((w) => w.id === scope.targetId)?.title ?? scope.label
: undefined;
return {
sessions: sessionIds.map((sid) => {
const session = latestSessions.find((s) => s.id === sid);
const host = session ? sessionHostsMap.get(session.id) : undefined;
return buildAITerminalSessionInfo(session, host, localOs, {
allHosts: latestHosts,
portForwardingRules: portForwardingRulesRef.current,
});
}),
workspaceId: scope.type === 'workspace' ? scope.targetId : undefined,
workspaceName,
readTerminalContext: (request: Parameters<TerminalContextReader>[0]) => {
const reader = terminalContextReadersRef.current.get(request.sessionId);
if (!reader) {
return Promise.resolve({
ok: false as const,
error: `Terminal session "${request.sessionId}" has no readable terminal buffer.`,
});
}
return reader(request);
},
};
}, [hostsRef, portForwardingRulesRef, sessionHostsMap, sessionsRef, terminalContextReadersRef, workspacesRef]);
return {
aiContextsByTabId,
resolveAIExecutorContext,
};
}