@omercnet/paseo-omp 0.4.0-next.119.1 → 0.4.0-next.120.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,30 @@
1
1
  import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
- import { isAbsolute, join } from "node:path";
3
+ import { join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { ompDataDir } from "../paths";
6
6
  import { isValidImagePayload } from "./image";
7
+ import {
8
+ buildOmpSpawnRequest,
9
+ type OmpSpawnRequest,
10
+ type OmpStartOptions,
11
+ } from "./omp-rpc-environment";
12
+ import {
13
+ isConfirmedNoProcessSpawnFailure,
14
+ PROCESS_STOP_TIMEOUT_MS,
15
+ type ProcessTreeCleanup,
16
+ stopWindowsTree,
17
+ terminatePosixProcessTree,
18
+ waitMs,
19
+ } from "./omp-rpc-process";
20
+ import {
21
+ MAX_ID_LENGTH,
22
+ MAX_NAME_LENGTH,
23
+ MAX_PATH_LENGTH,
24
+ MAX_TEXT_LENGTH,
25
+ OmpThinkingLevelSchema,
26
+ validateBoundedText,
27
+ } from "./omp-rpc-values";
7
28
  import {
8
29
  boundedJsonBytes,
9
30
  boundedJsonMetrics,
@@ -17,13 +38,10 @@ import {
17
38
  type OmpSessionListOptions,
18
39
  readOmpPersistedSessionTranscript,
19
40
  readOmpPersistedSubagentTranscript,
20
- validateNativeSessionId,
21
41
  } from "./session-descriptors";
22
- import type { OmpOutputRedaction } from "./settings";
23
42
 
24
43
  const READY_TIMEOUT_MS = 20_000;
25
44
  const REQUEST_TIMEOUT_MS = 60_000;
26
- const PROCESS_STOP_TIMEOUT_MS = 750;
27
45
  const CHUNK_STALE_MS = 30_000;
28
46
  const MAX_PHYSICAL_FRAME_BYTES = 1024 * 1024;
29
47
  const MAX_CHUNK_BYTES = 256 * 1024;
@@ -31,13 +49,8 @@ const MAX_ENCODED_CHUNK_BYTES = Math.ceil(MAX_CHUNK_BYTES / 3) * 4;
31
49
  const MAX_REASSEMBLED_FRAME_BYTES = 64 * 1024 * 1024;
32
50
  const MAX_SEMANTIC_FRAME_BYTES = 12 * 1024 * 1024;
33
51
  const MAX_CHUNK_COUNT = MAX_REASSEMBLED_FRAME_BYTES / MAX_CHUNK_BYTES;
34
- const MAX_ID_LENGTH = 256;
35
- const MAX_NAME_LENGTH = 256;
36
- const MAX_MODEL_SELECTOR_BYTES = MAX_NAME_LENGTH * 2 + 1;
37
52
  const MAX_CONFIG_EVENT_TEXT_BYTES = 64 * 1024;
38
- const MAX_TEXT_LENGTH = 1024 * 1024;
39
53
  const MAX_STREAM_TEXT_LENGTH = 4 * 1024 * 1024;
40
- const MAX_SYSTEM_PROMPT_LENGTH = 64 * 1024;
41
54
  const MAX_IMAGE_DATA_LENGTH = 8 * 1024 * 1024;
42
55
  const MAX_TOOL_PAYLOAD_LENGTH = 256 * 1024;
43
56
  const MAX_ACTIVE_TOOLS = 64;
@@ -75,11 +88,6 @@ const MAX_MODEL_CATALOG_ITEMS = 4_096;
75
88
  // primary resource bounds.
76
89
  export const OMP_MAX_CONTENT_PARTS = 4_096;
77
90
  const MAX_TODOS = 256;
78
- const MAX_ENV_ENTRIES = 256;
79
- const MAX_ENV_VALUE_LENGTH = 64 * 1024;
80
- const MAX_ENV_TOTAL_LENGTH = 1024 * 1024;
81
- const MAX_PATH_LENGTH = 4_096;
82
- const WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
83
91
  const MAX_TOKEN_COUNT = Number.MAX_SAFE_INTEGER;
84
92
  const MAX_COST_USD = 1_000_000_000;
85
93
  const MAX_RPC_ERROR_BYTES = 4_096;
@@ -610,7 +618,6 @@ function sanitizeLiveDisplayFrame(value: unknown): unknown {
610
618
  });
611
619
  return changed ? { ...frame, messages } : value;
612
620
  }
613
- const OmpThinkingLevelSchema = z.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
614
621
  const OmpCurrentThinkingLevelSchema = boundedString(32).optional().catch(undefined);
615
622
 
616
623
  function isBoundedJson(
@@ -1991,29 +1998,6 @@ export interface OmpPersistedSessionMessages {
1991
1998
  imageReplayWarning?: true;
1992
1999
  }
1993
2000
 
1994
- export interface OmpStartOptions {
1995
- cwd: string;
1996
- env?: Readonly<Record<string, string>>;
1997
- outputRedaction?: OmpOutputRedaction;
1998
- inheritEnv?: readonly string[];
1999
- /** Server-owned environment source; tests provide isolated roots instead of ambient process.env. */
2000
- environment?: NodeJS.ProcessEnv;
2001
- command?: readonly string[];
2002
- model?: string;
2003
- mode?: "full" | "write" | "ask";
2004
- thinkingOption?: string;
2005
- systemPrompt?: string;
2006
- roleModels?: Readonly<{ smol?: string; slow?: string; plan?: string }>;
2007
- tools?: readonly string[];
2008
- sessionDir?: string;
2009
- readyTimeoutMs?: number;
2010
- requestTimeoutMs?: number;
2011
- /** Resume this exact native OMP session; never use this to start a new conversation. */
2012
- resumeSessionId?: string;
2013
- noSession?: boolean;
2014
- signal?: AbortSignal;
2015
- }
2016
-
2017
2001
  export type OmpAvailableCommand = z.infer<typeof OmpAvailableCommandSchema>;
2018
2002
  export type OmpImage = { type: "image"; data: string; mimeType: string };
2019
2003
  export type OmpExtensionUiResponse =
@@ -2083,15 +2067,6 @@ export interface OmpRuntime {
2083
2067
  }): Promise<OmpPersistedSubagentMessages>;
2084
2068
  }
2085
2069
 
2086
- export interface OmpSpawnRequest {
2087
- command: string;
2088
- args: string[];
2089
- cwd: string;
2090
- env: NodeJS.ProcessEnv;
2091
- detached: boolean;
2092
- inheritedRedactionValues: string[];
2093
- }
2094
-
2095
2070
  export interface OmpRpcRuntimeOptions {
2096
2071
  spawnProcess?: (request: OmpSpawnRequest) => ChildProcessWithoutNullStreams;
2097
2072
  terminateProcessTree?: (pid: number) => Promise<boolean | "uncertain">;
@@ -2148,339 +2123,6 @@ type ChunkState = {
2148
2123
  timer: TimerHandle;
2149
2124
  };
2150
2125
 
2151
- // The daemon contributes only process/runtime discovery variables plus provider authentication
2152
- // families. Session-scoped values are explicit host input and are overlaid after rejecting loader
2153
- // and executable-resolution controls; this keeps provider credentials available without copying
2154
- const INHERITED_RUNTIME_ENV: Readonly<Record<string, true>> = {
2155
- ALL_PROXY: true,
2156
- APPDATA: true,
2157
- COLORTERM: true,
2158
- HOME: true,
2159
- HTTPS_PROXY: true,
2160
- HTTP_PROXY: true,
2161
- LANG: true,
2162
- LC_ALL: true,
2163
- LC_CTYPE: true,
2164
- LOCALAPPDATA: true,
2165
- LOGNAME: true,
2166
- NO_PROXY: true,
2167
- OMP_PROFILE: true,
2168
- PATH: true,
2169
- PATHEXT: true,
2170
- PI_CODING_AGENT_DIR: true,
2171
- PI_CONFIG_DIR: true,
2172
- PI_PROFILE: true,
2173
- SHELL: true,
2174
- SSH_AUTH_SOCK: true,
2175
- SSL_CERT_DIR: true,
2176
- SSL_CERT_FILE: true,
2177
- SYSTEMROOT: true,
2178
- TEMP: true,
2179
- TMP: true,
2180
- TMPDIR: true,
2181
- TZ: true,
2182
- USER: true,
2183
- USERPROFILE: true,
2184
- XDG_CACHE_HOME: true,
2185
- XDG_CONFIG_HOME: true,
2186
- XDG_DATA_HOME: true,
2187
- XDG_STATE_HOME: true,
2188
- XDG_RUNTIME_DIR: true,
2189
- };
2190
- const INHERITED_PROVIDER_AUTH_ENV: Readonly<Record<string, true>> = {
2191
- AI_GATEWAY_API_KEY: true,
2192
- AIAND_API_KEY: true,
2193
- ALIBABA_CODING_PLAN_API_KEY: true,
2194
- ALIBABA_TOKEN_PLAN_API_KEY: true,
2195
- ANTHROPIC_API_KEY: true,
2196
- ANTHROPIC_FOUNDRY_API_KEY: true,
2197
- ANTHROPIC_OAUTH_TOKEN: true,
2198
- AWS_ACCESS_KEY_ID: true,
2199
- AWS_DEFAULT_REGION: true,
2200
- AWS_PROFILE: true,
2201
- AWS_REGION: true,
2202
- AWS_SECRET_ACCESS_KEY: true,
2203
- AWS_SESSION_TOKEN: true,
2204
- AZURE_CLIENT_ID: true,
2205
- AZURE_CLIENT_SECRET: true,
2206
- AZURE_OPENAI_API_KEY: true,
2207
- AZURE_OPENAI_ENDPOINT: true,
2208
- AZURE_TENANT_ID: true,
2209
- BAILIAN_TOKEN_PLAN_API_KEY: true,
2210
- BASETEN_API_KEY: true,
2211
- CEREBRAS_API_KEY: true,
2212
- CHARM_HYPER_API_KEY: true,
2213
- CLINE_API_KEY: true,
2214
- CLOUDFLARE_AI_GATEWAY_API_KEY: true,
2215
- COHERE_API_KEY: true,
2216
- COMMAND_CODE_API_KEY: true,
2217
- COREWEAVE_API_KEY: true,
2218
- CURSOR_ACCESS_TOKEN: true,
2219
- DEEPINFRA_API_KEY: true,
2220
- DEEPSEEK_API_KEY: true,
2221
- DEVIN_API_KEY: true,
2222
- FIREPASS_API_KEY: true,
2223
- FIREWORKS_API_KEY: true,
2224
- FUGU_API_KEY: true,
2225
- GEMINI_API_KEY: true,
2226
- GMI_API_KEY: true,
2227
- GOOGLE_API_KEY: true,
2228
- GOOGLE_APPLICATION_CREDENTIALS: true,
2229
- GROQ_API_KEY: true,
2230
- HF_TOKEN: true,
2231
- HUGGINGFACE_HUB_TOKEN: true,
2232
- LLAMA_CPP_API_KEY: true,
2233
- LM_STUDIO_API_KEY: true,
2234
- META_API_KEY: true,
2235
- MINIMAX_API_KEY: true,
2236
- MINIMAX_CODE_API_KEY: true,
2237
- MINIMAX_CODE_CN_API_KEY: true,
2238
- MISTRAL_API_KEY: true,
2239
- MODEL_API_KEY: true,
2240
- MOONSHOT_API_KEY: true,
2241
- NANO_GPT_API_KEY: true,
2242
- NVIDIA_API_KEY: true,
2243
- NOVITA_API_KEY: true,
2244
- OLLAMA_API_KEY: true,
2245
- OLLAMA_CLOUD_API_KEY: true,
2246
- OLLAMA_HOST: true,
2247
- OMP_AUTH_BROKER_TOKEN: true,
2248
- OMP_AUTH_BROKER_URL: true,
2249
- OPENCODE_API_KEY: true,
2250
- OPENAI_API_KEY: true,
2251
- OPENAI_CODEX_OAUTH_TOKEN: true,
2252
- OPENROUTER_API_KEY: true,
2253
- PLEXUS_API_KEY: true,
2254
- QIANFAN_API_KEY: true,
2255
- QWEN_OAUTH_TOKEN: true,
2256
- QWEN_PORTAL_API_KEY: true,
2257
- SAKANA_API_KEY: true,
2258
- SILICONFLOW_API_KEY: true,
2259
- SILICONFLOW_CN_API_KEY: true,
2260
- SYNTHETIC_API_KEY: true,
2261
- TOGETHER_API_KEY: true,
2262
- UMANS_AI_CODING_PLAN_API_KEY: true,
2263
- VENICE_API_KEY: true,
2264
- VLLM_API_KEY: true,
2265
- WAFER_SERVERLESS_API_KEY: true,
2266
- WANDB_API_KEY: true,
2267
- XAI_API_KEY: true,
2268
- XAI_OAUTH_TOKEN: true,
2269
- XIAOMI_API_KEY: true,
2270
- XIAOMI_TOKEN_PLAN_AMS_API_KEY: true,
2271
- XIAOMI_TOKEN_PLAN_CN_API_KEY: true,
2272
- XIAOMI_TOKEN_PLAN_SGP_API_KEY: true,
2273
- YOLO_AUTO_API_KEY: true,
2274
- ZAI_API_KEY: true,
2275
- ZENMUX_API_KEY: true,
2276
- ZHIPU_API_KEY: true,
2277
- };
2278
- const BLOCKED_SESSION_ENV =
2279
- /^(?:BASH_ENV|BUN_INSTALL.*|BUN_OPTIONS|CLASSPATH|CLAUDE_BASH_NO_CI|CLAUDE_BASH_NO_LOGIN|CLAUDE_CODE_SHELL_PREFIX|DYLD_.*|EDITOR|ELECTRON_RUN_AS_NODE|ENV|GEM_HOME|GEM_PATH|GIT_CONFIG.*|GIT_SSH_COMMAND|HOME|JAVA_TOOL_OPTIONS|LD_.*|NODE_OPTIONS|NODE_PATH|NPM_CONFIG_.*|OMP_AUTORESEARCH_DB_DIR|OMP_COMMAND|OMP_GITHUB_CACHE_DB|OMP_PROFILE|OMP_WORKTREE_DIR|PATH|PATHEXT|PERL5LIB|PERL5OPT|PI_BASH_NO_CI|PI_BASH_NO_LOGIN|PI_CODING_AGENT_DIR|PI_CODING_AGENT_SESSION_DIR|PI_CONFIG_DIR|PI_CONFIG_FILES|PI_GIT_COMMON_DIR|PI_PACKAGE_DIR|PI_PROFILE|PI_PROJECT_DIR|PI_SESSION_ID|PI_SHELL_PREFIX|PI_SUBPROCESS_CMD|PI_WORKTREE_DIR|PWD|PYTHONHOME|PYTHONINSPECT|PYTHONPATH|PYTHONSTARTUP|RUBYLIB|RUBYOPT|SHELL|SYSTEMROOT|USERPROFILE|VISUAL|XDG_CACHE_HOME|XDG_CONFIG_HOME|XDG_DATA_HOME|XDG_RUNTIME_DIR|XDG_STATE_HOME|_JAVA_OPTIONS)$/u;
2280
- const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
2281
-
2282
- function validateBoundedText(value: unknown, field: string, maxBytes: number): string {
2283
- if (
2284
- typeof value !== "string" ||
2285
- utf8Bytes(value) === 0 ||
2286
- utf8Bytes(value) > maxBytes ||
2287
- value.includes("\0")
2288
- ) {
2289
- throw new Error(`Invalid OMP ${field}`);
2290
- }
2291
- return value;
2292
- }
2293
-
2294
- function inheritedEnvironmentNames(inheritEnv: readonly string[] | undefined): ReadonlySet<string> {
2295
- if (inheritEnv === undefined) return new Set();
2296
- if (!Array.isArray(inheritEnv)) {
2297
- throw new Error("OMP inherited environment names are invalid");
2298
- }
2299
- if (inheritEnv.length > MAX_ENV_ENTRIES) {
2300
- throw new Error("OMP inherited environment has too many entries");
2301
- }
2302
- const names = new Set<string>();
2303
- for (const name of inheritEnv) {
2304
- if (typeof name !== "string" || !ENV_NAME.test(name)) {
2305
- throw new Error("OMP inherited environment contains an invalid name");
2306
- }
2307
- if (BLOCKED_SESSION_ENV.test(name.toUpperCase())) {
2308
- throw new Error("OMP inherited environment contains a forbidden variable");
2309
- }
2310
- names.add(name);
2311
- }
2312
- return names;
2313
- }
2314
-
2315
- function buildOmpEnvironment(
2316
- sessionEnv: Readonly<Record<string, string>> | undefined,
2317
- inheritEnv: readonly string[] | undefined,
2318
- sourceEnv: NodeJS.ProcessEnv,
2319
- ): { env: NodeJS.ProcessEnv; inheritedRedactionValues: string[] } {
2320
- if (
2321
- sessionEnv !== undefined &&
2322
- (sessionEnv === null || typeof sessionEnv !== "object" || Array.isArray(sessionEnv))
2323
- ) {
2324
- throw new Error("OMP session environment is invalid");
2325
- }
2326
- const explicitlyInherited = inheritedEnvironmentNames(inheritEnv);
2327
- const explicitlyInheritedNormalized = new Set(
2328
- [...explicitlyInherited].map((name) => name.toUpperCase()),
2329
- );
2330
- const explicitNames = new Set(Object.keys(sessionEnv ?? {}).map((name) => name.toUpperCase()));
2331
- const env: NodeJS.ProcessEnv = {};
2332
- const inheritedRedactionValues: string[] = [];
2333
- let totalBytes = 0;
2334
- for (const [name, value] of Object.entries(sourceEnv)) {
2335
- if (value === undefined || name.toUpperCase() === "OMP_COMMAND") continue;
2336
- const normalizedName = name.toUpperCase();
2337
- const isRuntime =
2338
- process.platform === "win32"
2339
- ? normalizedName in INHERITED_RUNTIME_ENV
2340
- : name in INHERITED_RUNTIME_ENV;
2341
- const isProviderAuth =
2342
- process.platform === "win32"
2343
- ? normalizedName in INHERITED_PROVIDER_AUTH_ENV
2344
- : name in INHERITED_PROVIDER_AUTH_ENV;
2345
- const isExplicitlyInherited =
2346
- process.platform === "win32"
2347
- ? explicitlyInheritedNormalized.has(normalizedName)
2348
- : explicitlyInherited.has(name);
2349
- if (!isRuntime && !isProviderAuth && !isExplicitlyInherited) continue;
2350
- if (explicitNames.has(normalizedName)) continue;
2351
- const valueBytes = utf8Bytes(value);
2352
- if (!ENV_NAME.test(name) || valueBytes > MAX_ENV_VALUE_LENGTH || value.includes("\0")) {
2353
- if (isExplicitlyInherited) {
2354
- throw new Error("OMP inherited environment contains an invalid value");
2355
- }
2356
- continue;
2357
- }
2358
- if (isExplicitlyInherited && valueBytes > 0 && valueBytes < 4) {
2359
- throw new OmpPublicError("OMP inherited environment value is too short for safe redaction");
2360
- }
2361
- totalBytes += utf8Bytes(name) + valueBytes;
2362
- if (totalBytes > MAX_ENV_TOTAL_LENGTH) {
2363
- throw new Error("OMP inherited environment is too large");
2364
- }
2365
- env[name] = value;
2366
- if (isExplicitlyInherited && valueBytes > 0) inheritedRedactionValues.push(value);
2367
- }
2368
- let entryCount = 0;
2369
- for (const name in sessionEnv ?? {}) {
2370
- if (!Object.hasOwn(sessionEnv ?? {}, name)) continue;
2371
- entryCount += 1;
2372
- if (entryCount > MAX_ENV_ENTRIES)
2373
- throw new Error("OMP session environment has too many entries");
2374
- const value = (sessionEnv as Readonly<Record<string, string>>)[name];
2375
- const normalizedName = name.toUpperCase();
2376
- if (!ENV_NAME.test(name) || BLOCKED_SESSION_ENV.test(normalizedName)) {
2377
- throw new Error("OMP session environment contains a forbidden variable");
2378
- }
2379
- if (
2380
- typeof value !== "string" ||
2381
- utf8Bytes(value) > MAX_ENV_VALUE_LENGTH ||
2382
- value.includes("\0")
2383
- ) {
2384
- throw new Error("OMP session environment contains an invalid value");
2385
- }
2386
- const valueBytes = utf8Bytes(value);
2387
- totalBytes += utf8Bytes(name) + valueBytes;
2388
- if (totalBytes > MAX_ENV_TOTAL_LENGTH) throw new Error("OMP session environment is too large");
2389
- for (const inheritedName of Object.keys(env)) {
2390
- if (inheritedName !== name && inheritedName.toUpperCase() === normalizedName) {
2391
- delete env[inheritedName];
2392
- }
2393
- }
2394
- env[name] = value;
2395
- }
2396
- return { env, inheritedRedactionValues };
2397
- }
2398
-
2399
- export function buildOmpSpawnRequest(
2400
- options: OmpStartOptions,
2401
- sourceEnv: NodeJS.ProcessEnv = process.env,
2402
- ): OmpSpawnRequest {
2403
- const environmentSource = options.environment ?? sourceEnv;
2404
- const cwd = validateBoundedText(options.cwd, "working directory", MAX_PATH_LENGTH);
2405
- if (!isAbsolute(cwd)) throw new Error("OMP working directory must be absolute");
2406
- const commandPrefix = options.command ?? [environmentSource.OMP_COMMAND ?? "omp"];
2407
- if (commandPrefix.length === 0) throw new Error("Invalid OMP command");
2408
- const [rawCommand, ...rawPrefixArgs] = commandPrefix;
2409
- const command = validateBoundedText(rawCommand, "command", MAX_PATH_LENGTH);
2410
- const args = rawPrefixArgs.map((argument) =>
2411
- validateBoundedText(argument, "command argument", MAX_PATH_LENGTH),
2412
- );
2413
- if (/[\r\n]/u.test(command)) throw new Error("Invalid OMP command");
2414
- const mode = options.mode ?? "full";
2415
- if (mode !== "full" && mode !== "write" && mode !== "ask") throw new Error("Invalid OMP mode");
2416
- const approvalMode = mode === "full" ? "yolo" : mode === "write" ? "write" : "always-ask";
2417
- if (!args.some((argument) => argument === "--mode" || argument.startsWith("--mode="))) {
2418
- args.push("--mode", "rpc-ui");
2419
- }
2420
- args.push("--approval-mode", approvalMode);
2421
- if (options.tools) {
2422
- if (options.tools.length === 0) args.push("--no-tools");
2423
- else args.push("--tools", options.tools.join(","));
2424
- }
2425
- if (options.model !== undefined) {
2426
- args.push("--model", validateBoundedText(options.model, "model", MAX_MODEL_SELECTOR_BYTES));
2427
- }
2428
- if (options.thinkingOption !== undefined) {
2429
- const thinking = OmpThinkingLevelSchema.safeParse(options.thinkingOption);
2430
- if (!thinking.success) throw new Error("Invalid OMP thinking option");
2431
- args.push("--thinking", thinking.data);
2432
- }
2433
- if (options.roleModels?.smol) {
2434
- args.push(
2435
- "--smol",
2436
- validateBoundedText(options.roleModels.smol, "smol model", MAX_MODEL_SELECTOR_BYTES),
2437
- );
2438
- }
2439
- if (options.roleModels?.slow) {
2440
- args.push(
2441
- "--slow",
2442
- validateBoundedText(options.roleModels.slow, "slow model", MAX_MODEL_SELECTOR_BYTES),
2443
- );
2444
- }
2445
- if (options.roleModels?.plan) {
2446
- args.push(
2447
- "--plan",
2448
- validateBoundedText(options.roleModels.plan, "plan model", MAX_MODEL_SELECTOR_BYTES),
2449
- );
2450
- }
2451
- if (options.sessionDir !== undefined) {
2452
- args.push(
2453
- "--session-dir",
2454
- validateBoundedText(options.sessionDir, "session directory", MAX_PATH_LENGTH),
2455
- );
2456
- }
2457
- if (options.resumeSessionId !== undefined) {
2458
- args.push("--resume", validateNativeSessionId(options.resumeSessionId));
2459
- }
2460
- if (options.noSession) args.push("--no-session");
2461
- const systemPrompt = options.systemPrompt?.trim();
2462
- if (systemPrompt) {
2463
- args.push(
2464
- "--append-system-prompt",
2465
- validateBoundedText(systemPrompt, "system prompt", MAX_SYSTEM_PROMPT_LENGTH),
2466
- );
2467
- }
2468
- const { env, inheritedRedactionValues } = buildOmpEnvironment(
2469
- options.env,
2470
- options.inheritEnv,
2471
- environmentSource,
2472
- );
2473
- env.OMP_NO_WEBP = "1";
2474
- return {
2475
- command,
2476
- args,
2477
- cwd,
2478
- env,
2479
- inheritedRedactionValues,
2480
- detached: process.platform !== "win32",
2481
- };
2482
- }
2483
-
2484
2126
  function waitWithTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
2485
2127
  const result = Promise.withResolvers<T>();
2486
2128
  const timer = setTimeout(() => result.reject(new Error(message)), timeoutMs);
@@ -2497,118 +2139,6 @@ function waitWithTimeout<T>(promise: Promise<T>, timeoutMs: number, message: str
2497
2139
  return result.promise;
2498
2140
  }
2499
2141
 
2500
- function waitMs(ms: number): Promise<void> {
2501
- const result = Promise.withResolvers<void>();
2502
- setTimeout(result.resolve, ms);
2503
- return result.promise;
2504
- }
2505
-
2506
- function processIsGone(error: unknown): boolean {
2507
- return (error as NodeJS.ErrnoException)?.code === "ESRCH";
2508
- }
2509
-
2510
- function isConfirmedNoProcessSpawnFailure(error: unknown): boolean {
2511
- const code = (error as NodeJS.ErrnoException)?.code;
2512
- return code === "ENOENT" || code === "EACCES" || code === "EPERM";
2513
- }
2514
-
2515
- /**
2516
- * Terminates the detached process group created for OMP. This covers descendants that remain in
2517
- * that group after the leader exits; descendants that deliberately re-parent into another process
2518
- * group are outside this transport's containment boundary.
2519
- */
2520
- export async function terminatePosixProcessTree(
2521
- pid: number,
2522
- graceMs: number,
2523
- signalProcess: (pid: number, signal: NodeJS.Signals | 0) => void = process.kill,
2524
- wait: (ms: number) => Promise<void> = waitMs,
2525
- ): Promise<boolean> {
2526
- try {
2527
- signalProcess(-pid, 0);
2528
- } catch (error) {
2529
- return processIsGone(error);
2530
- }
2531
- try {
2532
- signalProcess(-pid, "SIGTERM");
2533
- } catch (error) {
2534
- if (!processIsGone(error)) return false;
2535
- }
2536
- await wait(graceMs);
2537
- try {
2538
- signalProcess(-pid, 0);
2539
- } catch (error) {
2540
- return processIsGone(error);
2541
- }
2542
- try {
2543
- signalProcess(-pid, "SIGKILL");
2544
- } catch (error) {
2545
- if (!processIsGone(error)) return false;
2546
- }
2547
- await wait(graceMs);
2548
- try {
2549
- signalProcess(-pid, 0);
2550
- return false;
2551
- } catch (error) {
2552
- return processIsGone(error);
2553
- }
2554
- }
2555
-
2556
- type ProcessTreeCleanup = "verified" | "uncertain" | "failed";
2557
-
2558
- async function stopWindowsTree(pid: number): Promise<ProcessTreeCleanup> {
2559
- const result = Promise.withResolvers<ProcessTreeCleanup>();
2560
- const systemRoot = process.env.SystemRoot ?? WINDOWS_DEFAULT_SYSTEM_ROOT;
2561
- let taskkill: ChildProcessWithoutNullStreams;
2562
- try {
2563
- taskkill = spawn(
2564
- join(systemRoot, "System32", "taskkill.exe"),
2565
- ["/PID", String(pid), "/T", "/F"],
2566
- {
2567
- stdio: ["pipe", "pipe", "pipe"],
2568
- windowsHide: true,
2569
- env: { SystemRoot: systemRoot },
2570
- },
2571
- );
2572
- } catch {
2573
- return "failed";
2574
- }
2575
- taskkill.stdout.resume();
2576
- taskkill.stderr.resume();
2577
- let settled = false;
2578
- let deadline: TimerHandle | undefined;
2579
- let finalDeadline: TimerHandle | undefined;
2580
- const finish = (outcome: ProcessTreeCleanup) => {
2581
- if (settled) return;
2582
- settled = true;
2583
- clearTimeout(deadline);
2584
- clearTimeout(finalDeadline);
2585
- result.resolve(outcome);
2586
- };
2587
- deadline = setTimeout(() => {
2588
- taskkill.kill("SIGKILL");
2589
- finalDeadline = setTimeout(() => finish("failed"), PROCESS_STOP_TIMEOUT_MS);
2590
- }, PROCESS_STOP_TIMEOUT_MS);
2591
- taskkill.once("error", () => finish("failed"));
2592
- taskkill.once("close", (code, signal) => {
2593
- finish(
2594
- code === 0 && signal === null
2595
- ? "verified"
2596
- : code === 128 && signal === null
2597
- ? "uncertain"
2598
- : "failed",
2599
- );
2600
- });
2601
- return result.promise;
2602
- }
2603
-
2604
- export async function terminateSpawnedProcessTree(
2605
- pid: number,
2606
- platform: NodeJS.Platform = process.platform,
2607
- ): Promise<boolean> {
2608
- if (platform === "win32") return (await stopWindowsTree(pid)) === "verified";
2609
- return await terminatePosixProcessTree(pid, PROCESS_STOP_TIMEOUT_MS);
2610
- }
2611
-
2612
2142
  class OmpRpcProcess {
2613
2143
  readonly ready: Promise<ReadyFrame>;
2614
2144
  readonly inheritedRedactionValues: readonly string[];
@@ -4,7 +4,8 @@ import { homedir } from "node:os";
4
4
  import { basename, join, resolve } from "node:path";
5
5
  import { isOmpProfileName } from "../../shared/omp-store";
6
6
  import { ompAgentDir, ompCacheDir, ompDataDir, ompSessionDir, ompStateDir } from "../paths";
7
- import { OmpRpcRuntime, type OmpRuntime, type OmpStartOptions } from "./omp-rpc";
7
+ import { OmpRpcRuntime, type OmpRuntime } from "./omp-rpc";
8
+ import type { OmpStartOptions } from "./omp-rpc-environment";
8
9
  import { parseOmpProviderOptions } from "./provider-options";
9
10
  import { createOmpProvider, type OmpProviderOptions } from "./registration";
10
11
  import { OmpPublicError } from "./security";