@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.
- package/client/omp-config-styles.ts +284 -0
- package/client/omp-config-surface.tsx +1 -279
- package/client/omp-routing-editor.tsx +1 -1
- package/package.json +1 -1
- package/server/package-version.ts +1 -1
- package/server/provider/config-normalization.ts +1 -1
- package/server/provider/mcp-transport.ts +1 -1
- package/server/provider/omp-rpc-environment.ts +368 -0
- package/server/provider/omp-rpc-process.ts +119 -0
- package/server/provider/omp-rpc-values.ts +31 -0
- package/server/provider/omp-rpc.ts +22 -492
- package/server/provider/profile-providers.ts +2 -1
- package/server/provider/prompt-payload.ts +247 -0
- package/server/provider/session.ts +11 -243
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
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
|
|
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";
|