[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,143 @@
import {
mergePluginCompletionItems,
normalizePluginCompletionResult,
} from '../../../domain/pluginTerminalProviders';
import type { PluginTerminalProviderRegistry } from '../../../application/state/pluginTerminalProviderRegistry';
import {
getCompletions,
type CompletionSuggestion,
} from './completionEngine';
import type { AutocompleteCwdSource } from './terminalAutocompleteLayout';
import type { AutocompleteHistoryScope, Snippet } from '../../../domain/models';
export interface TerminalCompletionProviderRequest {
input: string;
session: NetcattyTerminalSessionSnapshot;
hostGroup?: string;
hostOs: 'linux' | 'windows' | 'macos';
cwdSource?: AutocompleteCwdSource;
snippets?: Snippet[];
maximum: number;
/** Which history pool built-in suggestions draw from. */
historyScope?: AutocompleteHistoryScope;
/** Internal end-to-end wait bound; tests may lower it deterministically. */
pluginResponseTimeoutMs?: number;
/** Host security/session cancellation propagated to the plugin bridge. */
signal?: AbortSignal;
/**
* Forwarded to built-in getCompletions when a path listing finishes after the
* soft budget (cache-bypassed relative SSH cwd).
*/
onLatePathSuggestions?: (suggestions: CompletionSuggestion[]) => void;
}
const DEFAULT_PLUGIN_COMPLETION_RESPONSE_TIMEOUT_MS = 800;
type PluginCompletionResponse = Awaited<ReturnType<PluginTerminalProviderRegistry['request']>>;
function emptyPluginCompletionResponse(): PluginCompletionResponse {
return { requestId: '', stale: false, results: Object.freeze([]) };
}
async function waitForPluginCompletionResponse(
response: Promise<PluginCompletionResponse>,
timeoutMs: number,
onTimeout?: () => void,
): Promise<PluginCompletionResponse> {
let timer: ReturnType<typeof setTimeout> | undefined;
const timeout = new Promise<PluginCompletionResponse>((resolve) => {
timer = setTimeout(() => {
onTimeout?.();
resolve(emptyPluginCompletionResponse());
}, timeoutMs);
});
try {
return await Promise.race([response, timeout]);
} finally {
if (timer) clearTimeout(timer);
}
}
export async function provideTerminalCompletions(
registry: PluginTerminalProviderRegistry | null,
request: TerminalCompletionProviderRequest,
): Promise<CompletionSuggestion[]> {
const builtInPromise = getCompletions(request.input, {
hostId: request.session.hostId,
hostGroup: request.hostGroup,
os: request.hostOs,
maxResults: request.maximum,
sessionId: request.session.sessionId,
protocol: request.session.protocol,
cwd: request.session.cwd,
cwdSource: request.cwdSource,
snippets: request.snippets,
historyScope: request.historyScope,
onLatePathSuggestions: request.onLatePathSuggestions,
});
const pluginRequestController = new AbortController();
const abortPluginRequest = () => pluginRequestController.abort();
request.signal?.addEventListener('abort', abortPluginRequest, { once: true });
if (request.signal?.aborted) pluginRequestController.abort();
const pluginPromise = registry?.request({
kind: 'terminal.completion',
operation: 'provideCompletions',
session: request.session,
payload: {
input: request.input,
cursor: request.input.length,
hostOs: request.hostOs,
cwdSource: request.cwdSource ?? null,
maximum: request.maximum,
},
deadlineMs: 750,
}, { signal: pluginRequestController.signal }).catch(() => emptyPluginCompletionResponse())
?? Promise.resolve(emptyPluginCompletionResponse());
const pluginResponseTimeoutMs = Number.isFinite(request.pluginResponseTimeoutMs)
? Math.max(1, Math.min(5_000, Math.trunc(request.pluginResponseTimeoutMs ?? 0)))
: DEFAULT_PLUGIN_COMPLETION_RESPONSE_TIMEOUT_MS;
let builtIn: CompletionSuggestion[];
let pluginResponse: PluginCompletionResponse;
try {
[builtIn, pluginResponse] = await Promise.all([
builtInPromise,
waitForPluginCompletionResponse(
pluginPromise,
pluginResponseTimeoutMs,
() => pluginRequestController.abort(),
),
]);
} finally {
request.signal?.removeEventListener('abort', abortPluginRequest);
}
if (request.signal?.aborted || pluginRequestController.signal.aborted || pluginResponse.stale) {
return builtIn;
}
const pluginGroups = pluginResponse.results.map((result) => result.status === 'ok'
? normalizePluginCompletionResult(result.providerId, result.result)
: Object.freeze([]));
const pluginItems = mergePluginCompletionItems(pluginGroups, request.maximum);
const combined: CompletionSuggestion[] = [
...builtIn,
...pluginItems.map((item) => ({
text: item.text,
displayText: item.displayText,
...(item.description === undefined ? {} : { description: item.description }),
source: 'plugin' as const,
score: item.score,
providerId: item.providerId,
})),
];
const deduplicated = new Map<string, CompletionSuggestion>();
for (const item of combined) {
const existing = deduplicated.get(item.text);
if (!existing
|| (existing.source === 'plugin' && item.source !== 'plugin')
|| (existing.source === item.source && item.score > existing.score)) {
deduplicated.set(item.text, item);
}
}
return [...deduplicated.values()]
.sort((left, right) => right.score - left.score || left.text.localeCompare(right.text))
.slice(0, request.maximum);
}