@omercnet/paseo-omp 5.1.1-next.350.1 → 5.1.1-next.355.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/package.json +2 -2
- package/scripts/prepare-dependencies.mjs +7 -10
- package/server/package-version.ts +1 -1
- package/server/provider/connection.ts +2 -0
- package/server/provider/omp-rpc-process.ts +9 -9
- package/server/provider/registration.ts +9 -0
- package/server/provider/session-descriptors.ts +36 -30
- package/server/provider/session-history.ts +2 -0
- package/server/provider/session.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "5.1.1-next.
|
|
3
|
+
"version": "5.1.1-next.355.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"tsx": "^4.20.6",
|
|
78
78
|
"typescript": "^7.0.2",
|
|
79
79
|
"vitest": "^5.0.0",
|
|
80
|
-
"zod": "4.
|
|
80
|
+
"zod": "4.6.6"
|
|
81
81
|
},
|
|
82
82
|
"overrides": {
|
|
83
83
|
"qs": "6.16.1"
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
6
|
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
// Run through the shell so Windows resolves npm.cmd: Node refuses to spawn .cmd files without one.
|
|
8
|
+
const INSTALL_COMMAND = "npm ci --omit=dev --ignore-scripts --workspaces=false";
|
|
7
9
|
|
|
8
10
|
function loadRootCatalog(root) {
|
|
9
11
|
for (let directory = root; ; directory = dirname(directory)) {
|
|
@@ -27,6 +29,8 @@ function stageManifest(root, catalog, install) {
|
|
|
27
29
|
manifest.dependencies[name] = catalog[name];
|
|
28
30
|
}
|
|
29
31
|
delete manifest.devDependencies;
|
|
32
|
+
// npm-shrinkwrap.json is generated without overrides, so npm ci rejects a manifest that keeps them.
|
|
33
|
+
delete manifest.overrides;
|
|
30
34
|
|
|
31
35
|
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
32
36
|
try {
|
|
@@ -36,18 +40,11 @@ function stageManifest(root, catalog, install) {
|
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
export function prepareDependencies(root = projectRoot, execute =
|
|
43
|
+
export function prepareDependencies(root = projectRoot, execute = execSync) {
|
|
40
44
|
const catalog = loadRootCatalog(root);
|
|
41
45
|
if (!catalog) return false;
|
|
42
46
|
stageManifest(root, catalog, () => {
|
|
43
|
-
execute(
|
|
44
|
-
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
45
|
-
["ci", "--omit=dev", "--ignore-scripts", "--workspaces=false"],
|
|
46
|
-
{
|
|
47
|
-
cwd: root,
|
|
48
|
-
stdio: "inherit",
|
|
49
|
-
},
|
|
50
|
-
);
|
|
47
|
+
execute(INSTALL_COMMAND, { cwd: root, stdio: "inherit" });
|
|
51
48
|
});
|
|
52
49
|
return true;
|
|
53
50
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "5.1.1-next.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "5.1.1-next.355.1"; // x-release-please-version
|
|
@@ -893,6 +893,7 @@ export function createOmpConnection(
|
|
|
893
893
|
query: input.query,
|
|
894
894
|
limit: input.limit,
|
|
895
895
|
sessionDir: listingConfig.sessionDir,
|
|
896
|
+
signal: shutdown.signal,
|
|
896
897
|
})
|
|
897
898
|
).map((session) => ({
|
|
898
899
|
persistence: { version: 1, data: { sessionId: session.id } },
|
|
@@ -997,6 +998,7 @@ export function createOmpConnection(
|
|
|
997
998
|
environment,
|
|
998
999
|
hostInheritEnv,
|
|
999
1000
|
scheduler,
|
|
1001
|
+
controller.signal,
|
|
1000
1002
|
)
|
|
1001
1003
|
: await OmpProviderSession.open(
|
|
1002
1004
|
input,
|
|
@@ -64,12 +64,15 @@ export async function terminatePosixProcessTree(
|
|
|
64
64
|
|
|
65
65
|
export type ProcessTreeCleanup = "verified" | "uncertain" | "failed";
|
|
66
66
|
|
|
67
|
-
export async function stopWindowsTree(
|
|
67
|
+
export async function stopWindowsTree(
|
|
68
|
+
pid: number,
|
|
69
|
+
spawnTaskkill: typeof spawn = spawn,
|
|
70
|
+
): Promise<ProcessTreeCleanup> {
|
|
68
71
|
const result = Promise.withResolvers<ProcessTreeCleanup>();
|
|
69
72
|
const systemRoot = process.env.SystemRoot ?? WINDOWS_DEFAULT_SYSTEM_ROOT;
|
|
70
73
|
let taskkill: ChildProcessWithoutNullStreams;
|
|
71
74
|
try {
|
|
72
|
-
taskkill =
|
|
75
|
+
taskkill = spawnTaskkill(
|
|
73
76
|
join(systemRoot, "System32", "taskkill.exe"),
|
|
74
77
|
["/PID", String(pid), "/T", "/F"],
|
|
75
78
|
{
|
|
@@ -99,13 +102,10 @@ export async function stopWindowsTree(pid: number): Promise<ProcessTreeCleanup>
|
|
|
99
102
|
}, PROCESS_STOP_TIMEOUT_MS);
|
|
100
103
|
taskkill.once("error", () => finish("failed"));
|
|
101
104
|
taskkill.once("close", (code, signal) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
? "uncertain"
|
|
107
|
-
: "failed",
|
|
108
|
-
);
|
|
105
|
+
// 128 means taskkill found no such process because it already exited; retrying only repeats
|
|
106
|
+
// 128. Callers still wait for the root OMP process's own exit, so a live root cannot pass.
|
|
107
|
+
// Descendants of an already exited root cannot be enumerated by taskkill and are not checked.
|
|
108
|
+
finish((code === 0 || code === 128) && signal === null ? "verified" : "failed");
|
|
109
109
|
});
|
|
110
110
|
return result.promise;
|
|
111
111
|
}
|
|
@@ -118,6 +118,7 @@ export interface OmpProviderOptions {
|
|
|
118
118
|
timelineScheduler?: OmpTimelineScheduler;
|
|
119
119
|
replayTimeoutMs?: number;
|
|
120
120
|
environment?: NodeJS.ProcessEnv;
|
|
121
|
+
platform?: NodeJS.Platform;
|
|
121
122
|
/** Fixed, non-secret profile/store identity for providers whose configuration is server-owned. */
|
|
122
123
|
catalogIdentity?: Readonly<Record<string, string>>;
|
|
123
124
|
mcpInitializationTimeoutMs?: number;
|
|
@@ -156,6 +157,7 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
156
157
|
});
|
|
157
158
|
const nativeReservations = new OmpNativeSessionReservations();
|
|
158
159
|
const sessionLimit = options.sessionLimit ?? new OmpSessionLimit();
|
|
160
|
+
const platform = options.platform ?? process.platform;
|
|
159
161
|
const checkAvailability: NonNullable<ProviderRegistrationCompat["checkAvailability"]> = async (
|
|
160
162
|
catalogOptions,
|
|
161
163
|
context,
|
|
@@ -165,6 +167,12 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
165
167
|
}
|
|
166
168
|
const providerOptions = parseOmpProviderOptions(catalogOptions.providerOptions);
|
|
167
169
|
const environment = { ...(options.environment ?? process.env), ...providerOptions.env };
|
|
170
|
+
// Windows env names are case-insensitive, but a copy keeps only the literal key (often
|
|
171
|
+
// `Path`), so `environment.PATH` would miss it and OMP would look not installed.
|
|
172
|
+
if (platform === "win32" && environment.PATH === undefined) {
|
|
173
|
+
const pathKey = Object.keys(environment).find((name) => name.toUpperCase() === "PATH");
|
|
174
|
+
if (pathKey !== undefined) environment.PATH = environment[pathKey];
|
|
175
|
+
}
|
|
168
176
|
const configuredCommand: readonly [string, ...string[]] = providerOptions.command?.[0]
|
|
169
177
|
? [providerOptions.command[0], ...providerOptions.command.slice(1)]
|
|
170
178
|
: [environment.OMP_COMMAND || "omp"];
|
|
@@ -173,6 +181,7 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
173
181
|
cwd:
|
|
174
182
|
catalogOptions.scope === "workspace" && catalogOptions.cwd ? catalogOptions.cwd : homedir(),
|
|
175
183
|
environment,
|
|
184
|
+
platform,
|
|
176
185
|
timeoutMs: context?.timeoutMs,
|
|
177
186
|
});
|
|
178
187
|
};
|
|
@@ -10,12 +10,11 @@ const MAX_DESCRIPTOR_PREFIX_BYTES = 64 * 1024;
|
|
|
10
10
|
const MAX_DESCRIPTOR_SUFFIX_BYTES = 64 * 1024;
|
|
11
11
|
const MAX_PROMPT_PREVIEW_CHARS = 160;
|
|
12
12
|
const MAX_DIRECTORY_DEPTH = 8;
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// No wall-clock cutoff: it silently drops sessions on a slow disk. OMP keeps one directory per
|
|
14
|
+
// workspace, so the count caps must exceed real session trees or lookups silently miss.
|
|
15
15
|
const MAX_SCAN_DIRECTORIES = 65_536;
|
|
16
16
|
const MAX_SCAN_FILES = 262_144;
|
|
17
17
|
const MAX_SCAN_BYTES = 16 * 1024 * 1024;
|
|
18
|
-
const MAX_SCAN_MS = 1_000;
|
|
19
18
|
const SCAN_YIELD_INTERVAL = 128;
|
|
20
19
|
const MAX_LIST_RESULTS = 500;
|
|
21
20
|
const MAX_CHILD_TRANSCRIPT_BYTES = 16 * 1024 * 1024;
|
|
@@ -59,10 +58,10 @@ export interface OmpSessionListOptions {
|
|
|
59
58
|
limit?: number;
|
|
60
59
|
sessionId?: string;
|
|
61
60
|
sessionDir?: string;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
interface ScanBudget {
|
|
65
|
-
startedAt: number;
|
|
66
65
|
directories: number;
|
|
67
66
|
files: number;
|
|
68
67
|
bytes: number;
|
|
@@ -155,8 +154,7 @@ function budgetExceeded(budget: ScanBudget): boolean {
|
|
|
155
154
|
budget.exhausted ||
|
|
156
155
|
budget.directories >= MAX_SCAN_DIRECTORIES ||
|
|
157
156
|
budget.files >= MAX_SCAN_FILES ||
|
|
158
|
-
budget.bytes >= MAX_SCAN_BYTES
|
|
159
|
-
Date.now() - budget.startedAt >= MAX_SCAN_MS
|
|
157
|
+
budget.bytes >= MAX_SCAN_BYTES
|
|
160
158
|
);
|
|
161
159
|
}
|
|
162
160
|
|
|
@@ -418,11 +416,13 @@ async function scanSessionFiles(
|
|
|
418
416
|
root: string,
|
|
419
417
|
budget: ScanBudget,
|
|
420
418
|
visit: (file: string) => Promise<boolean>,
|
|
419
|
+
signal?: AbortSignal,
|
|
421
420
|
): Promise<void> {
|
|
422
421
|
const pending: Array<{ path: string; depth: number }> = [{ path: root, depth: 0 }];
|
|
423
422
|
while (pending.length > 0 && !budgetExceeded(budget)) {
|
|
424
423
|
const current = pending.pop();
|
|
425
424
|
if (!current) return;
|
|
425
|
+
signal?.throwIfAborted();
|
|
426
426
|
budget.directories += 1;
|
|
427
427
|
let directory: Dir;
|
|
428
428
|
try {
|
|
@@ -432,6 +432,7 @@ async function scanSessionFiles(
|
|
|
432
432
|
}
|
|
433
433
|
try {
|
|
434
434
|
for await (const entry of directory) {
|
|
435
|
+
signal?.throwIfAborted();
|
|
435
436
|
budget.entries += 1;
|
|
436
437
|
if (budget.entries % SCAN_YIELD_INTERVAL === 0) await yieldToEventLoop();
|
|
437
438
|
if (budgetExceeded(budget)) return;
|
|
@@ -469,7 +470,6 @@ export async function listOmpSessionDescriptors(
|
|
|
469
470
|
const query = options.query?.trim().toLowerCase();
|
|
470
471
|
const matches: OmpSessionDescriptor[] = [];
|
|
471
472
|
const budget: ScanBudget = {
|
|
472
|
-
startedAt: Date.now(),
|
|
473
473
|
directories: 0,
|
|
474
474
|
files: 0,
|
|
475
475
|
bytes: 0,
|
|
@@ -481,29 +481,35 @@ export async function listOmpSessionDescriptors(
|
|
|
481
481
|
? join(environment.HOME ?? environment.USERPROFILE ?? homedir(), options.sessionDir.slice(2))
|
|
482
482
|
: resolve(options.cwd ?? homedir(), options.sessionDir)
|
|
483
483
|
: ompSessionDir(environment);
|
|
484
|
-
await scanSessionFiles(
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
!
|
|
491
|
-
|
|
492
|
-
(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
484
|
+
await scanSessionFiles(
|
|
485
|
+
root,
|
|
486
|
+
budget,
|
|
487
|
+
async (file) => {
|
|
488
|
+
const fileName = basename(file);
|
|
489
|
+
const stem = fileName.slice(0, -".jsonl".length);
|
|
490
|
+
if (requestedId && !stem.endsWith(`_${requestedId}`)) return true;
|
|
491
|
+
const descriptor = await parseDescriptor(file, budget);
|
|
492
|
+
if (
|
|
493
|
+
!descriptor ||
|
|
494
|
+
!stem.endsWith(`_${descriptor.id}`) ||
|
|
495
|
+
(options.cwd !== undefined && descriptor.cwd !== options.cwd)
|
|
496
|
+
)
|
|
497
|
+
return !budgetExceeded(budget);
|
|
498
|
+
if (
|
|
499
|
+
query &&
|
|
500
|
+
!descriptor.id.toLowerCase().includes(query) &&
|
|
501
|
+
!descriptor.title?.toLowerCase().includes(query) &&
|
|
502
|
+
!descriptor.firstPromptPreview?.toLowerCase().includes(query) &&
|
|
503
|
+
!descriptor.lastPromptPreview?.toLowerCase().includes(query)
|
|
504
|
+
) {
|
|
505
|
+
return !budgetExceeded(budget);
|
|
506
|
+
}
|
|
507
|
+
retainNewest(matches, descriptor, retentionLimit);
|
|
508
|
+
return !requestedId || matches.length < 2;
|
|
509
|
+
},
|
|
510
|
+
options.signal,
|
|
511
|
+
);
|
|
512
|
+
options.signal?.throwIfAborted();
|
|
507
513
|
return matches;
|
|
508
514
|
}
|
|
509
515
|
|
|
@@ -119,6 +119,7 @@ export class OmpHistorySession implements OmpOpenedSession {
|
|
|
119
119
|
environment?: NodeJS.ProcessEnv,
|
|
120
120
|
hostInheritEnv: readonly string[] = [],
|
|
121
121
|
scheduler: OmpTimelineScheduler = defaultOmpTimelineScheduler,
|
|
122
|
+
signal?: AbortSignal,
|
|
122
123
|
): Promise<OmpHistorySession> {
|
|
123
124
|
const nativeSessionId = ompPersistenceSessionId(input);
|
|
124
125
|
if (!nativeSessionId || input.history !== "replay" || !input.config.persist) {
|
|
@@ -141,6 +142,7 @@ export class OmpHistorySession implements OmpOpenedSession {
|
|
|
141
142
|
nativeSessionId,
|
|
142
143
|
input.config.cwd,
|
|
143
144
|
normalized.sessionDir,
|
|
145
|
+
signal,
|
|
144
146
|
);
|
|
145
147
|
if (!descriptor.transcriptFile) {
|
|
146
148
|
throw new OmpPublicError("OMP session transcript could not be resolved in this workspace");
|
|
@@ -228,8 +228,9 @@ export async function authorizeNativeSession(
|
|
|
228
228
|
sessionId: string,
|
|
229
229
|
cwd: string,
|
|
230
230
|
sessionDir?: string,
|
|
231
|
+
signal?: AbortSignal,
|
|
231
232
|
): Promise<OmpSessionDescriptor> {
|
|
232
|
-
const matches = await runtime.listSessions({ sessionId, cwd, limit: 2, sessionDir });
|
|
233
|
+
const matches = await runtime.listSessions({ sessionId, cwd, limit: 2, sessionDir, signal });
|
|
233
234
|
const descriptor = matches[0];
|
|
234
235
|
if (matches.length !== 1 || descriptor?.id !== sessionId) {
|
|
235
236
|
throw new OmpPublicError("OMP session could not be resolved in this workspace");
|
|
@@ -542,6 +543,7 @@ export class OmpProviderSession implements OmpOpenedSession, OmpSubagentControls
|
|
|
542
543
|
resumeSessionId,
|
|
543
544
|
input.config.cwd,
|
|
544
545
|
normalizedConfig.sessionDir,
|
|
546
|
+
signal,
|
|
545
547
|
)
|
|
546
548
|
: undefined;
|
|
547
549
|
validateOmpHostToolConfig(effectiveConfig);
|