@remcp/runtime 0.2.225 → 0.2.227
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
|
@@ -340,21 +340,31 @@ export async function uiSnapshot(args = {}) {
|
|
|
340
340
|
? args.active_only === true
|
|
341
341
|
: (!explicitlyScoped && args.scope !== 'desktop');
|
|
342
342
|
let effective = { ...args, active_only:activeOnly };
|
|
343
|
-
|
|
343
|
+
// For wait_for_ui, share one deadline across native capture and Linux retries.
|
|
344
|
+
const deadline = args.timeout_ms != null && Number.isFinite(Number(args.timeout_ms))
|
|
345
|
+
? Date.now() + clamp(args.timeout_ms, 30_000, 100, 120_000) : null;
|
|
346
|
+
const canRetry = () => deadline === null || Date.now() + 100 < deadline;
|
|
347
|
+
const capture = () => adapter.uiSnapshot({
|
|
348
|
+
...effective,
|
|
349
|
+
...(deadline === null ? {} : { timeout_ms:Math.max(100, deadline - Date.now()) }),
|
|
350
|
+
});
|
|
351
|
+
let raw = await capture();
|
|
344
352
|
let value = resultValue(raw);
|
|
345
353
|
const requestedDepth = Number(effective.max_depth);
|
|
346
354
|
const isEmptySnapshot = payload => Array.isArray(payload)
|
|
347
355
|
? payload.length === 0
|
|
348
356
|
: Array.isArray(payload?.nodes) && payload.nodes.length === 0;
|
|
349
|
-
if (process.platform === 'linux' && isEmptySnapshot(value)) {
|
|
350
|
-
// GNOME/AT-SPI can transiently report an empty tree
|
|
351
|
-
//
|
|
352
|
-
await sleep(40);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
357
|
+
if (process.platform === 'linux' && isEmptySnapshot(value) && canRetry()) {
|
|
358
|
+
// GNOME/AT-SPI can transiently report an empty tree, but retries must
|
|
359
|
+
// never start after the caller's remaining wait budget is exhausted.
|
|
360
|
+
await sleep(deadline === null ? 40 : Math.min(40, Math.max(0, deadline - Date.now() - 100)));
|
|
361
|
+
if (canRetry()) {
|
|
362
|
+
raw = await capture();
|
|
363
|
+
value = resultValue(raw);
|
|
364
|
+
}
|
|
365
|
+
if (isEmptySnapshot(value) && Number.isFinite(requestedDepth) && requestedDepth > 12 && canRetry()) {
|
|
356
366
|
effective = { ...effective, max_depth:12 };
|
|
357
|
-
raw = await
|
|
367
|
+
raw = await capture();
|
|
358
368
|
value = resultValue(raw);
|
|
359
369
|
}
|
|
360
370
|
}
|
|
@@ -265,7 +265,7 @@ while queue and len(out)<${maxNodes}:
|
|
|
265
265
|
for i in range(e.childCount):queue.append((e.getChildAtIndex(i),d+1,app_name,pid,active,window_name,node_path+"."+str(i)))
|
|
266
266
|
except:pass
|
|
267
267
|
print(json.dumps(out))`;
|
|
268
|
-
const result = await runFile('python3', ['-c', script], { label: 'AT-SPI snapshot', timeout: 30_000, allowFailure:
|
|
268
|
+
const result = await runFile('python3', ['-c', script], { label: 'AT-SPI snapshot', timeout:clamp(args.timeout_ms, 30_000, 100, 30_000), allowFailure:true });
|
|
269
269
|
let parsed = null;
|
|
270
270
|
try { parsed = JSON.parse(result.stdout || ''); } catch {}
|
|
271
271
|
if (Array.isArray(parsed)) return jsonResult({ platform: 'linux', count: parsed.length, nodes: parsed });
|
|
@@ -52,6 +52,7 @@ export async function uiMatches(args = {}) {
|
|
|
52
52
|
...(resolved.app ? { app: resolved.app } : {}),
|
|
53
53
|
...((resolved.window_title || resolved.windowTitle) ? { window_title: resolved.window_title || resolved.windowTitle } : {}),
|
|
54
54
|
...(resolved.browser_dom === true ? { browser_dom:true } : {}),
|
|
55
|
+
...(resolved.timeout_ms != null ? { timeout_ms:resolved.timeout_ms } : {}),
|
|
55
56
|
});
|
|
56
57
|
const payload = resultValue(result) || {};
|
|
57
58
|
const nodes = Array.isArray(payload) ? payload : payload.nodes || [];
|
|
@@ -108,6 +109,7 @@ export async function uiConditionMatches(args, condition) {
|
|
|
108
109
|
...(args.app ? { app:args.app } : {}),
|
|
109
110
|
...((args.window_title || args.windowTitle) ? { window_title:args.window_title || args.windowTitle } : {}),
|
|
110
111
|
...(args.browser_dom === true ? { browser_dom:true } : {}),
|
|
112
|
+
...(args.timeout_ms != null ? { timeout_ms:args.timeout_ms } : {}),
|
|
111
113
|
})) || {};
|
|
112
114
|
nodes = Array.isArray(snapshot) ? snapshot : snapshot.nodes || [];
|
|
113
115
|
}
|
|
@@ -142,8 +144,27 @@ export async function waitForUi(args = {}) {
|
|
|
142
144
|
if (['element_exists','element_enabled'].includes(condition) && !hasSelector) throw new Error(`wait_for_ui condition=${condition} requires a semantic target`);
|
|
143
145
|
if (condition === 'active_window' && !(args.text || args.name || args.window_title || args.windowTitle)) throw new Error('wait_for_ui condition=active_window requires text, name, or window_title');
|
|
144
146
|
if (['present','absent'].includes(state) && !hasSelector) throw new Error(`wait_for_ui state=${state} requires a semantic target`);
|
|
145
|
-
const deadline = Date.now() + timeoutMs;
|
|
146
147
|
const startedAt = Date.now();
|
|
148
|
+
const deadline = startedAt + timeoutMs;
|
|
149
|
+
const expired = Symbol('wait_for_ui deadline');
|
|
150
|
+
// Resolve snapshot labels once, before invalidation; subsequent reads use the
|
|
151
|
+
// stable semantic locator rather than a label from a discarded tree.
|
|
152
|
+
const target = args.label != null ? { ...withResolvedUiLabel(args), label:undefined } : args;
|
|
153
|
+
const probe = async read => {
|
|
154
|
+
const remaining = deadline - Date.now();
|
|
155
|
+
if (remaining <= 0) return expired;
|
|
156
|
+
try {
|
|
157
|
+
const value = await read({ ...target, timeout_ms:Math.max(100, remaining) });
|
|
158
|
+
return Date.now() >= deadline ? expired : value;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (Date.now() >= deadline) return expired;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const pause = async () => {
|
|
165
|
+
const remaining = deadline - Date.now();
|
|
166
|
+
if (remaining > 0) await sleep(Math.min(pollMs, remaining));
|
|
167
|
+
};
|
|
147
168
|
let attempts = 0;
|
|
148
169
|
|
|
149
170
|
if (condition) {
|
|
@@ -152,7 +173,9 @@ export async function waitForUi(args = {}) {
|
|
|
152
173
|
attempts += 1;
|
|
153
174
|
// A wait must observe fresh state, not repeatedly query the same 5-second cache.
|
|
154
175
|
invalidateUiSnapshotCache();
|
|
155
|
-
|
|
176
|
+
const observed = await probe(options => uiConditionMatches(options, condition));
|
|
177
|
+
if (observed === expired) break;
|
|
178
|
+
evaluation = observed;
|
|
156
179
|
if (evaluation.matched) {
|
|
157
180
|
return jsonResult({
|
|
158
181
|
condition,
|
|
@@ -164,7 +187,7 @@ export async function waitForUi(args = {}) {
|
|
|
164
187
|
...(evaluation.detail ? { detail:evaluation.detail } : {}),
|
|
165
188
|
});
|
|
166
189
|
}
|
|
167
|
-
await
|
|
190
|
+
await pause();
|
|
168
191
|
} while (Date.now() < deadline);
|
|
169
192
|
return jsonResult({
|
|
170
193
|
condition,
|
|
@@ -178,13 +201,20 @@ export async function waitForUi(args = {}) {
|
|
|
178
201
|
}
|
|
179
202
|
|
|
180
203
|
invalidateUiSnapshotCache();
|
|
181
|
-
const
|
|
204
|
+
const first = state === 'changed' ? await probe(uiMatches) : [];
|
|
205
|
+
const baseline = first === expired ? [] : first;
|
|
206
|
+
if (first === expired) {
|
|
207
|
+
return jsonResult({ state:'timeout', wanted:state, matched:0, attempts,
|
|
208
|
+
elapsed_ms:Date.now() - startedAt, nodes:[], before:[] });
|
|
209
|
+
}
|
|
182
210
|
const baselineFingerprint = state === 'changed' ? uiFingerprint(baseline) : null;
|
|
183
211
|
let nodes = baseline;
|
|
184
212
|
do {
|
|
185
213
|
attempts += 1;
|
|
186
214
|
invalidateUiSnapshotCache();
|
|
187
|
-
|
|
215
|
+
const observed = await probe(uiMatches);
|
|
216
|
+
if (observed === expired) break;
|
|
217
|
+
nodes = observed;
|
|
188
218
|
const changed = state === 'changed' && uiFingerprint(nodes) !== baselineFingerprint;
|
|
189
219
|
if ((state === 'present' && nodes.length) || (state === 'absent' && !nodes.length) || changed) {
|
|
190
220
|
return jsonResult({
|
|
@@ -196,7 +226,7 @@ export async function waitForUi(args = {}) {
|
|
|
196
226
|
...(state === 'changed' ? { before: baseline } : {}),
|
|
197
227
|
});
|
|
198
228
|
}
|
|
199
|
-
await
|
|
229
|
+
await pause();
|
|
200
230
|
} while (Date.now() < deadline);
|
|
201
231
|
return jsonResult({ state:'timeout', wanted:state, matched:nodes.length, attempts, elapsed_ms:Date.now() - startedAt, nodes, ...(state === 'changed' ? { before:baseline } : {}) });
|
|
202
232
|
}
|