@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 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, cfg.requestTimeoutMs, expectFocus);
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 expectFocus = isAlignSource ? focusTarget : null;
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.paused || this.desiredText === null) return;
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ondevplann/remotetype",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "RemoteType PC 客户端:npx 一键启动,接收手机端语音识别文本并注入到当前焦点输入框",
5
5
  "bin": {
6
6
  "remotetype": "./bin/index.js"