@ondevplann/remotetype 0.3.1 → 0.4.0
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/bin/index.js +2 -73
- package/lib/rt-align.js +1 -48
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -230,59 +230,6 @@ async function cmdServe(cfg) {
|
|
|
230
230
|
// 本批按键计划中注入失败过的 seq:commit 到来时走 commitFailed
|
|
231
231
|
// (mirror 不推进、重试),而不是 commitApplied(误以为已注入 → 永久错位)
|
|
232
232
|
const failedAlignSeqs = new Set();
|
|
233
|
-
// 焦点绑定:首个成功注入的窗口标识记为对齐目标,之后每条 op 携带
|
|
234
|
-
// expect_focus 校验;rust 拒注时挂起 align 会话+通知手机,避免
|
|
235
|
-
// 电脑切焦点后误删误打。新会话(对齐模式)建立时重置。
|
|
236
|
-
let focusTarget = null;
|
|
237
|
-
let focusPaused = false;
|
|
238
|
-
let focusPollTimer = null;
|
|
239
|
-
|
|
240
|
-
/** 焦点匹配成功:首次绑定,后续直接用。每次顺手更新到目标(target 句柄可能跨进程稳定)。 */
|
|
241
|
-
function bindFocus(current) {
|
|
242
|
-
if (current && !focusTarget) {
|
|
243
|
-
focusTarget = current;
|
|
244
|
-
logger.info('焦点已绑定(首个注入目标)', { target: focusTarget });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** 焦点漂移:挂起 align 会话 + 通知手机 + 启动轮询。同一 seq 走 commitFailed 不推进 mirror。 */
|
|
249
|
-
function handleFocusDrift(seq, currentFocus) {
|
|
250
|
-
if (focusPaused) return; // 已经在挂起,重复漂移不重复发 ctrl
|
|
251
|
-
focusPaused = true;
|
|
252
|
-
logger.warn('焦点漂移,注入已暂停', { target: focusTarget, current: currentFocus, seq });
|
|
253
|
-
if (seq !== undefined) failedAlignSeqs.add(seq);
|
|
254
|
-
if (align) align.pause();
|
|
255
|
-
// 通知手机:把 ctrl 信封通过 sendAck 同一个 transport 推上去(共用 c2p 方向)
|
|
256
|
-
if (align?.sendCtrl) {
|
|
257
|
-
align.sendCtrl('focus-paused').catch((e) => logger.warn('ctrl 通知失败', { error: e.message }));
|
|
258
|
-
}
|
|
259
|
-
// 启动轮询:每 2s 询问 rust 当前焦点,回到目标则恢复
|
|
260
|
-
focusPollTimer = setInterval(() => {
|
|
261
|
-
agent.send({ action: 'focus' }, cfg.requestTimeoutMs)
|
|
262
|
-
.then((res) => {
|
|
263
|
-
if (res?.focus && res.focus === focusTarget) {
|
|
264
|
-
resumeFromFocus();
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
.catch((e) => logger.debug('焦点轮询失败', { error: e.message }));
|
|
268
|
-
}, 2000);
|
|
269
|
-
focusPollTimer.unref?.();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/** 焦点回归:取消轮询、通知手机、恢复 align 会话(会重新 dispatch 当前 desired)。 */
|
|
273
|
-
function resumeFromFocus() {
|
|
274
|
-
if (!focusPaused) return;
|
|
275
|
-
focusPaused = false;
|
|
276
|
-
if (focusPollTimer) {
|
|
277
|
-
clearInterval(focusPollTimer);
|
|
278
|
-
focusPollTimer = null;
|
|
279
|
-
}
|
|
280
|
-
logger.info('焦点已恢复,注入恢复', { target: focusTarget });
|
|
281
|
-
if (align?.sendCtrl) {
|
|
282
|
-
align.sendCtrl('focus-resumed').catch((e) => logger.warn('ctrl 通知失败', { error: e.message }));
|
|
283
|
-
}
|
|
284
|
-
if (align) align.resume();
|
|
285
|
-
}
|
|
286
233
|
|
|
287
234
|
// ---- 消息队列:串行消费,逐条注入 ----
|
|
288
235
|
// serve 明文模式 item = {text, clientId};对齐模式 item =
|
|
@@ -308,16 +255,11 @@ async function cmdServe(cfg) {
|
|
|
308
255
|
|
|
309
256
|
// backspace:删除原语(relay 对齐模式)
|
|
310
257
|
if (item.op === 'backspace') {
|
|
311
|
-
const expectFocus = meta.source === 'align' ? focusTarget : null;
|
|
312
258
|
try {
|
|
313
|
-
const res = await agent.backspace(item.count
|
|
259
|
+
const res = await agent.backspace(item.count);
|
|
314
260
|
if (res?.ok) {
|
|
315
261
|
logger.debug('退格完成', { count: item.count, queueWaitMs });
|
|
316
262
|
store.append({ status: 'backspaced', count: item.count, source: meta.source, seq: item.seq });
|
|
317
|
-
if (meta.source === 'align') bindFocus(res.focus);
|
|
318
|
-
} else if (res?.msg === 'focus_drift') {
|
|
319
|
-
handleFocusDrift(item.seq, res.focus);
|
|
320
|
-
store.append({ status: 'backspace_focus_drift', count: item.count, source: meta.source, seq: item.seq, currentFocus: res.focus });
|
|
321
263
|
} else {
|
|
322
264
|
logger.error('退格失败', { count: item.count, msg: res?.msg });
|
|
323
265
|
if (item.seq !== undefined) failedAlignSeqs.add(item.seq);
|
|
@@ -374,12 +316,10 @@ async function cmdServe(cfg) {
|
|
|
374
316
|
});
|
|
375
317
|
|
|
376
318
|
try {
|
|
377
|
-
const
|
|
378
|
-
const res = await agent.typeText(cleaned.text, cfg.requestTimeoutMs, expectFocus);
|
|
319
|
+
const res = await agent.typeText(cleaned.text);
|
|
379
320
|
const durationMs = Date.now() - startedAt;
|
|
380
321
|
if (res?.ok) {
|
|
381
322
|
logger.info('注入完成', { durationMs, textLen: cleaned.text.length });
|
|
382
|
-
if (isAlignSource) bindFocus(res.focus);
|
|
383
323
|
store.append({
|
|
384
324
|
text: cleaned.text,
|
|
385
325
|
status: 'injected',
|
|
@@ -389,17 +329,6 @@ async function cmdServe(cfg) {
|
|
|
389
329
|
durationMs,
|
|
390
330
|
truncated: Boolean(cleaned.truncated),
|
|
391
331
|
});
|
|
392
|
-
} else if (res?.msg === 'focus_drift' && isAlignSource) {
|
|
393
|
-
handleFocusDrift(item.seq, res.focus);
|
|
394
|
-
store.append({
|
|
395
|
-
text: cleaned.text,
|
|
396
|
-
status: 'inject_focus_drift',
|
|
397
|
-
clientId: item.clientId,
|
|
398
|
-
source: meta.source,
|
|
399
|
-
queueWaitMs,
|
|
400
|
-
durationMs,
|
|
401
|
-
currentFocus: res.focus,
|
|
402
|
-
});
|
|
403
332
|
} else {
|
|
404
333
|
logger.error('注入失败', { msg: res?.msg, durationMs });
|
|
405
334
|
if (meta.source === 'align' && item.seq !== undefined) failedAlignSeqs.add(item.seq);
|
package/lib/rt-align.js
CHANGED
|
@@ -78,7 +78,6 @@ class RtAlignSession extends EventEmitter {
|
|
|
78
78
|
this.pendingFails = 0; // 当前 desired 连续注入失败次数(成功 commit 或新快照归零)
|
|
79
79
|
this.ackSeq = 0;
|
|
80
80
|
this.sid = crypto.randomBytes(4).toString('hex');
|
|
81
|
-
this.paused = false; // 焦点漂移期间挂起(focus-paused),挂起期间 _dispatch 直接丢弃
|
|
82
81
|
|
|
83
82
|
this.stats = {
|
|
84
83
|
envelopesOk: 0, envelopesBad: 0, syncsApplied: 0, acksSent: 0, plaintextRejected: 0,
|
|
@@ -158,7 +157,7 @@ class RtAlignSession extends EventEmitter {
|
|
|
158
157
|
* busy 期间只更新 desiredText,commit 后自动续跑(全文快照语义下永远收敛)。
|
|
159
158
|
*/
|
|
160
159
|
_dispatch() {
|
|
161
|
-
if (this.busy || this.
|
|
160
|
+
if (this.busy || this.desiredText === null) return;
|
|
162
161
|
const plan = planTransition(this.mirror, this.desiredText);
|
|
163
162
|
if (plan.backspaces === 0 && plan.insert === '') {
|
|
164
163
|
// 目标已达成(重复快照),仍需补 ACK 推进手机端确认
|
|
@@ -249,52 +248,6 @@ class RtAlignSession extends EventEmitter {
|
|
|
249
248
|
this.busySince = 0;
|
|
250
249
|
this._dispatch();
|
|
251
250
|
}
|
|
252
|
-
|
|
253
|
-
// ================= 挂起/恢复(焦点漂移) =================
|
|
254
|
-
|
|
255
|
-
/** 挂起对齐会话:焦点漂移期间停止派发按键计划,避免误删误打。 */
|
|
256
|
-
pause() {
|
|
257
|
-
if (this.paused) return;
|
|
258
|
-
this.paused = true;
|
|
259
|
-
this.logger?.info('align session paused (focus drift)');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* 恢复对齐会话:重新派发当前 desired,焦点已回归到目标输入框。
|
|
264
|
-
* 关键:desiredText / desiredSeq 都保留着(之前的快照),所以新一次
|
|
265
|
-
* dispatch 从当前 mirror 重新 diff,自然处理「挂起期间手机又改了几个字」。
|
|
266
|
-
*/
|
|
267
|
-
resume() {
|
|
268
|
-
if (!this.paused) return;
|
|
269
|
-
this.paused = false;
|
|
270
|
-
this.logger?.info('align session resumed (focus restored)');
|
|
271
|
-
if (this.desiredText !== null) this._dispatch();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* 发送 ctrl 信封到手机(共用 sendAck 同一传输层 + keyPcToPhone 方向,
|
|
276
|
-
* 走 c2p 单调 seq 域:手机端会判 ackSeq 顺序与去重)。
|
|
277
|
-
* @param {string} event 事件名,如 'focus-paused' / 'focus-resumed'
|
|
278
|
-
*/
|
|
279
|
-
sendCtrl(event) {
|
|
280
|
-
if (typeof this._sendAckTransport !== 'function') return Promise.resolve();
|
|
281
|
-
this.ackSeq += 1;
|
|
282
|
-
const envelope = seal(
|
|
283
|
-
this.keyPcToPhone,
|
|
284
|
-
buildAad(this.room, DIR_C2P, this.ackSeq),
|
|
285
|
-
{ event, ts: Date.now() },
|
|
286
|
-
{ sid: this.sid, seq: this.ackSeq },
|
|
287
|
-
);
|
|
288
|
-
return new Promise((resolve) => {
|
|
289
|
-
try {
|
|
290
|
-
this._sendAckTransport(envelope);
|
|
291
|
-
this.logger?.info('ctrl sent', { event, seq: this.ackSeq });
|
|
292
|
-
} catch (e) {
|
|
293
|
-
this.logger?.warn('ctrl send failed', { event, error: e.message });
|
|
294
|
-
}
|
|
295
|
-
resolve();
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
251
|
}
|
|
299
252
|
|
|
300
253
|
module.exports = { RtAlignSession, looksLikeEnvelope };
|