@remcp/runtime 0.2.229 → 0.2.230
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
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { msaaBrowserSnapshot, windowsUiRootScript } from './desktop-windows-msaa.mjs';
|
|
2
|
+
import { quoteWindowsArgument } from './windows-argv.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import process from 'node:process';
|
|
4
5
|
|
|
@@ -75,7 +76,8 @@ public static class RM {
|
|
|
75
76
|
if(target!=0&¤t!=target)attachTarget=AttachThreadInput(current,target,true);
|
|
76
77
|
if(foreground!=0&¤t!=foreground&&foreground!=target)attachForeground=AttachThreadInput(current,foreground,true);
|
|
77
78
|
ShowWindow(h,9);AllowSetForegroundWindow(-1);BringWindowToTop(h);
|
|
78
|
-
|
|
79
|
+
// HWND_TOP preserves the window's existing topmost status.
|
|
80
|
+
SetWindowPos(h,IntPtr.Zero,0,0,0,0,0x0001|0x0002|0x0010|0x0040);
|
|
79
81
|
return SetForegroundWindow(h);
|
|
80
82
|
}finally{
|
|
81
83
|
if(attachForeground)AttachThreadInput(current,foreground,false);
|
|
@@ -445,8 +447,8 @@ export async function notification(args = {}) {
|
|
|
445
447
|
|
|
446
448
|
export async function launchApp(app,argv=[],options={}) {
|
|
447
449
|
const query=escapePowerShellSingle(String(app));
|
|
448
|
-
const argsText=argv.map(
|
|
449
|
-
const argumentClause=argv.length?`-ArgumentList
|
|
450
|
+
const argsText=escapePowerShellSingle(argv.map(quoteWindowsArgument).join(' '));
|
|
451
|
+
const argumentClause=argv.length?`-ArgumentList '${argsText}'`:'';
|
|
450
452
|
const hasArgs=argv.length?'$true':'$false';
|
|
451
453
|
const cwd=options.cwd?escapePowerShellSingle(String(options.cwd)):'';
|
|
452
454
|
const hasCwd=cwd?'$true':'$false';
|
|
@@ -98,21 +98,36 @@ export async function serviceTool(args) {
|
|
|
98
98
|
return text(`${policy?.note ? `${policy.note}\n` : ''}${action} requested for service ${name}.`);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Windows Get-WinEvent raises NoMatchingEventsFound (and exits nonzero) when a
|
|
102
|
+
// bounded time range has no events. Empty logs are not failed diagnostics, but
|
|
103
|
+
// access denied/invalid log queries must still fail closed.
|
|
104
|
+
export function buildWindowsEventLogScript({ log, seconds, filter, limit }) {
|
|
105
|
+
const safeLog = escapePowerShellSingle(log);
|
|
106
|
+
const where = filter ? ` | Where-Object { $_.Message -like '*${escapePowerShellSingle(filter)}*' -or $_.ProviderName -like '*${escapePowerShellSingle(filter)}*' }` : '';
|
|
107
|
+
const pipeline = `Get-WinEvent -FilterHashtable @{LogName='${safeLog}';StartTime=(Get-Date).AddSeconds(-${seconds})} -ErrorAction Stop${where} | Select-Object -First ${limit} TimeCreated,Id,LevelDisplayName,ProviderName,Message | ConvertTo-Json -Compress -Depth 3`;
|
|
108
|
+
return `try { ${pipeline} } catch { if ([string]$_.FullyQualifiedErrorId -like 'NoMatchingEventsFound,*') { '[]' } else { throw } }`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Unified log records the invocation of `/usr/bin/log show` itself, whose
|
|
112
|
+
// arguments contain the sought text. Exclude that self-generated event.
|
|
113
|
+
export function macosEventLogPredicate(filter) {
|
|
114
|
+
return `eventMessage CONTAINS[c] ${JSON.stringify(filter)} AND process != "log"`;
|
|
115
|
+
}
|
|
116
|
+
|
|
101
117
|
export async function eventLog(args) {
|
|
102
118
|
const limit = clamp(args.limit, 200, 1, 5000);
|
|
103
119
|
const since = durationSpec(args.since);
|
|
104
120
|
const filter = optionalString(args.filter || args.query);
|
|
105
121
|
if (process.platform === 'win32') {
|
|
106
|
-
const log =
|
|
122
|
+
const log = optionalString(args.log) || 'System';
|
|
107
123
|
const n = Number.parseInt(since, 10), unit = since.at(-1);
|
|
108
124
|
const seconds = n * ({ s:1, m:60, h:3600, d:86400 }[unit] || 60);
|
|
109
|
-
const
|
|
110
|
-
const script = `Get-WinEvent -FilterHashtable @{LogName='${log}';StartTime=(Get-Date).AddSeconds(-${seconds})} -ErrorAction SilentlyContinue${where} | Select-Object -First ${limit} TimeCreated,Id,LevelDisplayName,ProviderName,Message | ConvertTo-Json -Compress -Depth 3`;
|
|
125
|
+
const script = buildWindowsEventLogScript({ log, seconds, filter, limit });
|
|
111
126
|
return text((await runPowerShell(script, { label: 'event log', timeout: 30_000 })).stdout.trim() || '[]');
|
|
112
127
|
}
|
|
113
128
|
if (process.platform === 'darwin') {
|
|
114
129
|
const argv = ['show','--last',since,'--style','ndjson'];
|
|
115
|
-
if (filter) argv.push('--predicate',
|
|
130
|
+
if (filter) argv.push('--predicate', macosEventLogPredicate(filter));
|
|
116
131
|
const { stdout } = await runFileHeadLines('/usr/bin/log', argv, limit, { label: 'macOS unified log', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 });
|
|
117
132
|
return text(stdout);
|
|
118
133
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Start-Process joins ArgumentList into one command-line string. Encode each
|
|
2
|
+
// argument using the Windows CRT quoting rules before handing that string over.
|
|
3
|
+
export function quoteWindowsArgument(value) {
|
|
4
|
+
const argument = String(value);
|
|
5
|
+
if (argument.includes('\0')) throw new Error('Application arguments must not contain NUL');
|
|
6
|
+
const escaped = argument.replace(/(\\*)"/g, (_, slashes) => `${slashes}${slashes}\\"`)
|
|
7
|
+
.replace(/\\+$/g, slashes => slashes + slashes);
|
|
8
|
+
return `"${escaped}"`;
|
|
9
|
+
}
|
|
@@ -334,14 +334,28 @@ export async function moveToTrashTool(args) {
|
|
|
334
334
|
destination = trash;
|
|
335
335
|
} catch { destination = null; }
|
|
336
336
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
337
|
+
const localTrash = path.join(path.dirname(source), '.remcp-trash');
|
|
338
|
+
if (!destination) destination = localTrash;
|
|
339
|
+
const prepareTrash = async candidate => {
|
|
340
|
+
const existing = await lstat(candidate).catch(() => null);
|
|
341
|
+
if (existing?.isSymbolicLink()) fail('Refusing to use a symlink as the trash directory');
|
|
342
|
+
if (existing && !existing.isDirectory()) fail('Trash destination is not a directory');
|
|
343
|
+
await ensureDirectoryPath(candidate);
|
|
344
|
+
const safe = await resolveSafePath(candidate, 'trash');
|
|
345
|
+
const info = await lstat(safe);
|
|
346
|
+
if (!info.isDirectory() || info.isSymbolicLink()) fail('Refusing to use a symlink as the trash directory');
|
|
347
|
+
return { path: safe, device: info.dev };
|
|
348
|
+
};
|
|
349
|
+
const sourceInfo = await lstat(source).catch(() => fail(`Path not found: ${displayPath(source)}`));
|
|
350
|
+
if (sourceInfo.isSymbolicLink()) fail('source cannot be a symbolic link');
|
|
351
|
+
let prepared = await prepareTrash(destination);
|
|
352
|
+
// A same-volume rename keeps an entire directory recoverable without copying it.
|
|
353
|
+
// If the home trash is on another filesystem, use a guarded sibling trash.
|
|
354
|
+
if (prepared.device !== sourceInfo.dev && sourceInfo.isDirectory()) {
|
|
355
|
+
prepared = await prepareTrash(localTrash);
|
|
356
|
+
if (prepared.device !== sourceInfo.dev) fail('No writable trash directory on the source filesystem');
|
|
357
|
+
}
|
|
358
|
+
destination = prepared.path;
|
|
345
359
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
346
360
|
let target = path.join(destination, `${stamp}-${path.basename(source)}`);
|
|
347
361
|
let counter = 1;
|