@zeph-to/cli 2.1.0 → 2.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-rules-fetch.d.ts","sourceRoot":"","sources":["../src/agent-rules-fetch.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEH,KAAK,iBAAiB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,eAAO,MAAM,gBAAgB,QAAuC,CAAC;AACrE,eAAO,MAAM,yBAAyB,QAAqB,CAAC;AA6C5D;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,iBAAiB,GAAG,IAWrE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAU9D,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAK5D,eAAO,MAAM,iBAAiB,QAAO,iBAAmC,CAAC;AACzE,eAAO,MAAM,uBAAuB,QAAO,cAA8B,CAAC;AAS1E,iBAAiB;AACjB,eAAO,MAAM,mBAAmB,QAAO,IAEtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,cAWxC,CAAC;AAWF,eAAO,MAAM,QAAQ,QAAO,MAK3B,CAAC;AAEF,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAU,YAAW,OAAO,KAAa,KAAG,OAAO,CAAC,aAAa,CA4C5F,CAAC"}
1
+ {"version":3,"file":"agent-rules-fetch.d.ts","sourceRoot":"","sources":["../src/agent-rules-fetch.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEH,KAAK,iBAAiB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,eAAO,MAAM,gBAAgB,QAAuC,CAAC;AACrE,eAAO,MAAM,yBAAyB,QAAqB,CAAC;AAkD5D;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,iBAAiB,GAAG,IAWrE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAU9D,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAK5D,eAAO,MAAM,iBAAiB,QAAO,iBAAmC,CAAC;AACzE,eAAO,MAAM,uBAAuB,QAAO,cAA8B,CAAC;AAS1E,iBAAiB;AACjB,eAAO,MAAM,mBAAmB,QAAO,IAEtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,cAWxC,CAAC;AAWF,eAAO,MAAM,QAAQ,QAAO,MAK3B,CAAC;AAEF,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAU,YAAW,OAAO,KAAa,KAAG,OAAO,CAAC,aAAa,CA4C5F,CAAC"}
@@ -55,11 +55,16 @@ const isRule = (v) => {
55
55
  return false;
56
56
  if (typeof r.state !== 'string' || !VALID_STATES.has(r.state))
57
57
  return false;
58
- if (typeof r.priority !== 'number' || !Number.isFinite(r.priority))
58
+ // Safe integers, not merely finite. These numbers arrive from the server
59
+ // and decide behavior: a fractional priority orders rules by values no
60
+ // author can reason about, 1e21 outranks every rule ever written including
61
+ // the kill-switch, and a fractional tailLines slices a capture at a
62
+ // fraction of a line.
63
+ if (!Number.isSafeInteger(r.priority))
59
64
  return false;
60
65
  if (r.region !== undefined && r.region !== 'tail' && r.region !== 'whole')
61
66
  return false;
62
- if (r.tailLines !== undefined && (typeof r.tailLines !== 'number' || r.tailLines < 1 || r.tailLines > 200))
67
+ if (r.tailLines !== undefined && (!Number.isSafeInteger(r.tailLines) || r.tailLines < 1 || r.tailLines > 200))
63
68
  return false;
64
69
  if (r.contains !== undefined && !isStringArray(r.contains))
65
70
  return false;
package/dist/crypto.d.ts CHANGED
@@ -122,10 +122,6 @@ export interface EncryptedEphemeralPayload extends EncryptedPayload {
122
122
  */
123
123
  export declare const initDeviceCrypto: () => Promise<string>;
124
124
  export declare const getDevicePublicKey: () => string | null;
125
- /**
126
- * Encrypt an ephemeral payload (e.g. a stream frame) for one recipient
127
- * device. Requires initDeviceCrypto() to have completed.
128
- */
129
125
  export declare const encryptEphemeral: (plaintext: string, recipientPublicKeyRaw: string) => Promise<EncryptedEphemeralPayload>;
130
126
  /**
131
127
  * Open an ephemeral envelope addressed to this device — the exact inverse of
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+EH,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAsED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAyB5E,CAAC;AAsCF,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAClD,eAAe,EAG8C,CAAC;AA2CjE,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAkD,CAAC;AACjG,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA4D,CAAC;AAEtG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QAAO,IAEhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAkBA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,GAAG,MAAM,EACxB,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,CAsBxE,CAAC;AAcF;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE,eAAe,EAAE,MAAM,CAAC;CACzB;AAsBD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,OAAO,CAAC,MAAM,CAoBjD,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAAO,MAAM,GAAG,IAA+B,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,WAAW,MAAM,EACjB,uBAAuB,MAAM,KAC5B,OAAO,CAAC,yBAAyB,CAKnC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,yBAAyB,KACjC,OAAO,CAAC,MAAM,CAIhB,CAAC"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+EH,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AA0ED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAyB5E,CAAC;AAsCF,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAClD,eAAe,EAG8C,CAAC;AA2CjE,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAkD,CAAC;AACjG,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA4D,CAAC;AAEtG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QAAO,IAEhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAkBA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,GAAG,MAAM,EACxB,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,CAsBxE,CAAC;AAcF;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE,eAAe,EAAE,MAAM,CAAC;CACzB;AAsBD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,OAAO,CAAC,MAAM,CAoBjD,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAAO,MAAM,GAAG,IAA+B,CAAC;AAyB/E,eAAO,MAAM,gBAAgB,GAC3B,WAAW,MAAM,EACjB,uBAAuB,MAAM,KAC5B,OAAO,CAAC,yBAAyB,CAKnC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,yBAAyB,KACjC,OAAO,CAAC,MAAM,CAIhB,CAAC"}
package/dist/crypto.js CHANGED
@@ -76,11 +76,13 @@ const importKeyPair = async (exported) => {
76
76
  return { publicKey, privateKey };
77
77
  };
78
78
  const deriveAesKey = async (privateKey, publicKey) => crypto.subtle.deriveKey({ name: 'ECDH', public: publicKey }, privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
79
- const encrypt = async (plaintext, senderPrivateKey, recipientPublicKey) => {
79
+ const encrypt = async (plaintext, senderPrivateKey, recipientPublicKey,
80
+ /** Pre-derived ECDH secret for this pair, when the caller keeps one. */
81
+ shared) => {
80
82
  const messageKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
81
83
  const iv = crypto.getRandomValues(new Uint8Array(12));
82
84
  const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, messageKey, new TextEncoder().encode(plaintext));
83
- const sharedKey = await deriveAesKey(senderPrivateKey, recipientPublicKey);
85
+ const sharedKey = shared ?? await deriveAesKey(senderPrivateKey, recipientPublicKey);
84
86
  const rawMessageKey = await crypto.subtle.exportKey('raw', messageKey);
85
87
  const keyIv = crypto.getRandomValues(new Uint8Array(12));
86
88
  const encryptedKey = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: keyIv }, sharedKey, rawMessageKey);
@@ -91,8 +93,10 @@ const encrypt = async (plaintext, senderPrivateKey, recipientPublicKey) => {
91
93
  keyIv: toBase64(keyIv.buffer),
92
94
  };
93
95
  };
94
- const decrypt = async (payload, recipientPrivateKey, senderPublicKey) => {
95
- const sharedKey = await deriveAesKey(recipientPrivateKey, senderPublicKey);
96
+ const decrypt = async (payload, recipientPrivateKey, senderPublicKey,
97
+ /** Pre-derived ECDH secret for this pair, when the caller keeps one. */
98
+ shared) => {
99
+ const sharedKey = shared ?? await deriveAesKey(recipientPrivateKey, senderPublicKey);
96
100
  const rawMessageKey = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: new Uint8Array(fromBase64(payload.keyIv)) }, sharedKey, fromBase64(payload.encryptedKey));
97
101
  const messageKey = await crypto.subtle.importKey('raw', rawMessageKey, { name: 'AES-GCM' }, false, ['decrypt']);
98
102
  const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: new Uint8Array(fromBase64(payload.iv)) }, messageKey, fromBase64(payload.ciphertext));
@@ -341,11 +345,26 @@ exports.getDevicePublicKey = getDevicePublicKey;
341
345
  * Encrypt an ephemeral payload (e.g. a stream frame) for one recipient
342
346
  * device. Requires initDeviceCrypto() to have completed.
343
347
  */
348
+ /**
349
+ * One imported peer key plus the ECDH secret derived with it. A stream talks to
350
+ * exactly one subscriber, and the input half binds to that same key, so both
351
+ * directions reuse one entry for the stream's life — deriving per frame AND per
352
+ * keystroke was two WebCrypto operations repeating the same answer.
353
+ */
354
+ let peerCache = null;
355
+ const peerFor = async (raw, privateKey) => {
356
+ if (peerCache?.b64 === raw)
357
+ return peerCache;
358
+ const key = await importPublicKey(raw);
359
+ const shared = await deriveAesKey(privateKey, key);
360
+ peerCache = { b64: raw, key, shared };
361
+ return peerCache;
362
+ };
344
363
  const encryptEphemeral = async (plaintext, recipientPublicKeyRaw) => {
345
364
  if (!deviceKeyPair || !deviceExportedPublicKey)
346
365
  throw new Error('Device crypto not initialized');
347
- const recipientKey = await importPublicKey(recipientPublicKeyRaw);
348
- const payload = await encrypt(plaintext, deviceKeyPair.privateKey, recipientKey);
366
+ const peer = await peerFor(recipientPublicKeyRaw, deviceKeyPair.privateKey);
367
+ const payload = await encrypt(plaintext, deviceKeyPair.privateKey, peer.key, peer.shared);
349
368
  return { ...payload, senderPublicKey: deviceExportedPublicKey };
350
369
  };
351
370
  exports.encryptEphemeral = encryptEphemeral;
@@ -367,7 +386,7 @@ exports.encryptEphemeral = encryptEphemeral;
367
386
  const decryptEphemeral = async (payload) => {
368
387
  if (!deviceKeyPair)
369
388
  throw new Error('Device crypto not initialized');
370
- const senderKey = await importPublicKey(payload.senderPublicKey);
371
- return decrypt(payload, deviceKeyPair.privateKey, senderKey);
389
+ const peer = await peerFor(payload.senderPublicKey, deviceKeyPair.privateKey);
390
+ return decrypt(payload, deviceKeyPair.privateKey, peer.key, peer.shared);
372
391
  };
373
392
  exports.decryptEphemeral = decryptEphemeral;
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * - in-order seq delivers immediately (typing latency is the whole point of
11
11
  * this path; nothing is buffered on the happy path)
12
- * - a gap holds later keys for `holdMs`, then delivers what arrived anyway.
12
+ * - a gap holds later keys for INPUT_HOLD_MS, then delivers what arrived anyway.
13
13
  * Approximate order beats dropped keys: the relay is at-most-once, so a
14
14
  * never-arriving seq must not wedge everything behind it forever.
15
15
  * - a new epoch (sender restart) resets the high-water mark, and the old
@@ -48,7 +48,6 @@ export type SequencedInput = {
48
48
  epoch: number;
49
49
  };
50
50
  export type InputSequencerOptions<T> = {
51
- holdMs?: number;
52
51
  schedule?: ScheduleFlush;
53
52
  /** Called for each HELD message dropped without delivery — an epoch change
54
53
  * or reset() sweeping the buffer. Accept-time drops are reported via the
@@ -1 +1 @@
1
- {"version":3,"file":"input-sequencer.d.ts","sourceRoot":"","sources":["../src/input-sequencer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;sEAGsE;AACtE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;2DAI2D;AAC3D,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC;AAEtE,+BAA+B;AAC/B,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAE9B,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC;AAExE,MAAM,MAAM,cAAc,GAAG;IACzB,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;8EAE0E;IAC1E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,cAAc,IAAI;IACnD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,YAAY,CAAC;IACjC;2BACuB;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AASF,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,cAAc,EACzD,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EACzB,OAAM,qBAAqB,CAAC,CAAC,CAAM,KACpC,cAAc,CAAC,CAAC,CAmFlB,CAAC"}
1
+ {"version":3,"file":"input-sequencer.d.ts","sourceRoot":"","sources":["../src/input-sequencer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;sEAGsE;AACtE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;2DAI2D;AAC3D,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC;AAEtE,+BAA+B;AAC/B,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAE9B,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC;AAExE,MAAM,MAAM,cAAc,GAAG;IACzB,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACnC,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;8EAE0E;IAC1E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,cAAc,IAAI;IACnD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,YAAY,CAAC;IACjC;2BACuB;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AASF,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,cAAc,EACzD,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EACzB,OAAM,qBAAqB,CAAC,CAAC,CAAM,KACpC,cAAc,CAAC,CAAC,CAkFlB,CAAC"}
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * - in-order seq delivers immediately (typing latency is the whole point of
12
12
  * this path; nothing is buffered on the happy path)
13
- * - a gap holds later keys for `holdMs`, then delivers what arrived anyway.
13
+ * - a gap holds later keys for INPUT_HOLD_MS, then delivers what arrived anyway.
14
14
  * Approximate order beats dropped keys: the relay is at-most-once, so a
15
15
  * never-arriving seq must not wedge everything behind it forever.
16
16
  * - a new epoch (sender restart) resets the high-water mark, and the old
@@ -41,7 +41,6 @@ const defaultSchedule = (fn, ms) => {
41
41
  return () => clearTimeout(timer);
42
42
  };
43
43
  const createInputSequencer = (deliver, opts = {}) => {
44
- const holdMs = opts.holdMs ?? exports.INPUT_HOLD_MS;
45
44
  const schedule = opts.schedule ?? defaultSchedule;
46
45
  let epoch = null;
47
46
  let highWater = 0;
@@ -112,7 +111,7 @@ const createInputSequencer = (deliver, opts = {}) => {
112
111
  // One deadline per gap: it dates from when the hole opened, so a
113
112
  // steady stream of later keys can't extend the wait indefinitely.
114
113
  if (!cancelFlush)
115
- cancelFlush = schedule(flush, holdMs);
114
+ cancelFlush = schedule(flush, exports.INPUT_HOLD_MS);
116
115
  return 'ok';
117
116
  };
118
117
  return {
@@ -56,7 +56,7 @@ export declare const sessionsFingerprint: (sessions: AgentSession[]) => string;
56
56
  * idle heartbeat is due. */
57
57
  export declare const sessionsReportDue: (fingerprint: string, lastSentFingerprint: string | null, lastSentAtMs: number, nowMs: number) => boolean;
58
58
  export declare const AUTH_FAILURE_CODES: ReadonlySet<number>;
59
- export declare const checkRateLimit: (session: string, now?: number) => boolean;
59
+ export declare const checkRateLimit: (session: string, now?: number, cost?: number) => boolean;
60
60
  /** Read the foreground command in the named tmux session's active pane. */
61
61
  export declare const paneCurrentCommand: (session: string) => string | null;
62
62
  /**
@@ -159,6 +159,22 @@ export interface ScreenSnapshot {
159
159
  * token bucket as command injection.
160
160
  */
161
161
  export declare const handleScreenRequest: (req: ScreenRequest) => ScreenSnapshot | null;
162
+ /**
163
+ * Translate the cursor into an index into the lines the frame carries.
164
+ *
165
+ * The visible pane is the tail of the capture, so the cursor's row counts back
166
+ * from the end — which also makes this correct after the byte cap has cut lines
167
+ * off the top. A cursor that falls outside what the frame carries returns null
168
+ * and simply is not drawn.
169
+ */
170
+ export declare const cursorLineFor: (content: string, cursor: {
171
+ x: number;
172
+ y: number;
173
+ height: number;
174
+ }) => {
175
+ line: number;
176
+ col: number;
177
+ } | null;
162
178
  /**
163
179
  * After the phone injects keys, push fresh frames instead of waiting for its
164
180
  * next screen request — the phone's post-tap re-pull raced the key landing
@@ -204,6 +220,10 @@ export interface FrameBudget {
204
220
  * passed. False means this tick must skip WITHOUT marking the frame as sent —
205
221
  * the diff-gate would otherwise swallow that content for good.
206
222
  */
223
+ /** Is there budget left this second? A peek, so a tick can decline to pay for a
224
+ * blocking capture it could not send anyway — spending the token here instead
225
+ * would burn budget on ticks the diff-gate goes on to skip. */
226
+ export declare const hasFrameBudget: (budget: FrameBudget, now: number) => boolean;
207
227
  export declare const claimFrameSend: (budget: FrameBudget, now: number) => boolean;
208
228
  export declare const STREAM_LEASE_MS = 15000;
209
229
  export declare const MAX_CONCURRENT_STREAMS = 3;
@@ -220,6 +240,15 @@ export type StreamFramePayload = {
220
240
  /** Stream incarnation (stats.startedAt) — lets the receiver reset its
221
241
  * seq high-water mark when the daemon restarts the stream. */
222
242
  epoch?: number;
243
+ /** Where to draw the cursor: a 0-based index into the lines this frame
244
+ * carries, and a 0-based CELL column (not a string index — a wide glyph
245
+ * spans two cells, which is what the viewer positions by). Absent when the
246
+ * cursor is outside the captured region. Deliberately outside the E2EE
247
+ * envelope, like sessionName and seq: a coordinate without the text it
248
+ * points into says nothing, and keeping it in the clear means the viewer
249
+ * can place the cursor before the pane content finishes decrypting. */
250
+ cursorLine?: number;
251
+ cursorCol?: number;
223
252
  /** Plaintext pane content — only on unencrypted streams. */
224
253
  content?: string;
225
254
  /** E2EE envelope — replaces `content` on encrypted streams. */
@@ -257,7 +286,10 @@ export declare const isStreamCapReached: (activeCount: number) => boolean;
257
286
  export declare const buildStreamFrame: (captured: {
258
287
  content: string;
259
288
  truncated: boolean;
260
- }, sessionName: string, subscriberPublicKey?: string) => Promise<StreamFramePayload | null>;
289
+ }, sessionName: string, subscriberPublicKey?: string, cursor?: {
290
+ line: number;
291
+ col: number;
292
+ } | null) => Promise<StreamFramePayload | null>;
261
293
  /**
262
294
  * Handle agent.stream.start / agent.stream.stop. Returns true when the message
263
295
  * was a stream-control message (so the caller skips the one-shot screen-peek
@@ -403,7 +435,7 @@ interface HandlePushDeps {
403
435
  paneCommand?: (session: string) => string | null;
404
436
  inject?: (session: string, text: string) => boolean;
405
437
  sendKeys?: (session: string, tokens: string[]) => boolean;
406
- rateLimit?: (session: string) => boolean;
438
+ rateLimit?: (session: string, now?: number, cost?: number) => boolean;
407
439
  now?: () => number;
408
440
  /** Injectable for tests; defaults to the REST-backed downloader. */
409
441
  downloadAttachments?: (pushId: string, files: PushFileAttachment[]) => Promise<string[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
package/dist/listener.js CHANGED
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
26
  };
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.handleListener = exports.resolveWsUrl = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.handleCommandInput = exports.pendingInputDecrypts = exports.MAX_PENDING_DECRYPTS = exports.MAX_INPUT_LANES = exports.validateInputMessage = exports.MAX_INPUT_BODY_CHARS = exports.MAX_INPUT_KEYS = exports.handleStreamControl = exports.buildStreamFrame = exports.isStreamCapReached = exports.stopAllStreams = exports.stopStream = exports.MAX_CONCURRENT_STREAMS = exports.STREAM_LEASE_MS = exports.claimFrameSend = exports.streamCadence = exports.MAX_FRAMES_PER_SEC = exports.BURST_WINDOW_MS = exports.BURST_INTERVAL_MS = exports.STREAM_INTERVAL_MS = exports.scheduleInjectSnapshots = exports.handleScreenRequest = exports.collectWatchHits = exports.resetPatternWatches = exports.setPatternWatches = exports.deriveSessionState = exports.resetSessionStates = exports.detectRemoteAgent = exports.parseSessionName = exports.invalidateTmuxSocketCache = exports.resolveKeys = exports.paneCurrentCommand = exports.checkRateLimit = exports.AUTH_FAILURE_CODES = exports.sessionsReportDue = exports.sessionsFingerprint = exports.SESSION_REPORT_HEARTBEAT_MS = void 0;
28
+ exports.handleListener = exports.resolveWsUrl = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.handleCommandInput = exports.pendingInputDecrypts = exports.MAX_PENDING_DECRYPTS = exports.MAX_INPUT_LANES = exports.validateInputMessage = exports.MAX_INPUT_BODY_CHARS = exports.MAX_INPUT_KEYS = exports.handleStreamControl = exports.buildStreamFrame = exports.isStreamCapReached = exports.stopAllStreams = exports.stopStream = exports.MAX_CONCURRENT_STREAMS = exports.STREAM_LEASE_MS = exports.claimFrameSend = exports.hasFrameBudget = exports.streamCadence = exports.MAX_FRAMES_PER_SEC = exports.BURST_WINDOW_MS = exports.BURST_INTERVAL_MS = exports.STREAM_INTERVAL_MS = exports.scheduleInjectSnapshots = exports.cursorLineFor = exports.handleScreenRequest = exports.collectWatchHits = exports.resetPatternWatches = exports.setPatternWatches = exports.deriveSessionState = exports.resetSessionStates = exports.detectRemoteAgent = exports.parseSessionName = exports.invalidateTmuxSocketCache = exports.resolveKeys = exports.paneCurrentCommand = exports.checkRateLimit = exports.AUTH_FAILURE_CODES = exports.sessionsReportDue = exports.sessionsFingerprint = exports.SESSION_REPORT_HEARTBEAT_MS = void 0;
29
29
  const child_process_1 = require("child_process");
30
30
  const crypto_1 = require("crypto");
31
31
  const fs_1 = require("fs");
@@ -92,10 +92,19 @@ exports.sessionsFingerprint = sessionsFingerprint;
92
92
  const sessionsReportDue = (fingerprint, lastSentFingerprint, lastSentAtMs, nowMs) => fingerprint !== lastSentFingerprint ||
93
93
  nowMs - lastSentAtMs >= exports.SESSION_REPORT_HEARTBEAT_MS;
94
94
  exports.sessionsReportDue = sessionsReportDue;
95
- // Per-session token bucket — caps a runaway/compromised sender. 30/min
96
- // is generous for human-driven phone use, tight enough to block flooding.
97
- const RATE_LIMIT_TOKENS = 30;
95
+ // Per-session token bucket — caps a runaway/compromised sender.
96
+ //
97
+ // Weighted rather than flat, because the two things that pass through it are
98
+ // not the same size. A command submit is one whole instruction to the agent;
99
+ // 30 a minute has always been the ceiling for that, and it stays exactly that
100
+ // (120 / SUBMIT_COST). A named key is a keystroke — arrowing through a menu or
101
+ // holding Backspace is ordinary human speed, and charging it as if it were an
102
+ // instruction made the phone's key row refuse a normal burst of taps after a
103
+ // couple of seconds.
104
+ const RATE_LIMIT_TOKENS = 120;
98
105
  const RATE_LIMIT_WINDOW_MS = 60_000;
106
+ /** What one command submit costs, keeping its ceiling at the old 30/min. */
107
+ const SUBMIT_COST = 4;
99
108
  // Shells are refused: a shell prompt + send-keys = arbitrary command exec.
100
109
  const SHELL_COMMANDS = new Set(['bash', 'zsh', 'fish', 'sh', 'dash', 'ksh', 'tcsh', 'csh', 'pwsh']);
101
110
  // Auth-failure close codes: retrying with the same bad credentials hammers
@@ -112,18 +121,18 @@ const pruneStaleBuckets = (now) => {
112
121
  buckets.delete(key);
113
122
  }
114
123
  };
115
- const checkRateLimit = (session, now = Date.now()) => {
124
+ const checkRateLimit = (session, now = Date.now(), cost = 1) => {
116
125
  pruneStaleBuckets(now);
117
126
  const b = buckets.get(session) ?? { tokens: RATE_LIMIT_TOKENS, lastRefillAt: now };
118
127
  const elapsed = Math.max(0, now - b.lastRefillAt);
119
128
  // Fractional refill is intentional: smooths the boundary so a session
120
129
  // hitting the cap doesn't have to wait a full window for the next slot.
121
130
  const refilled = Math.min(RATE_LIMIT_TOKENS, b.tokens + (elapsed / RATE_LIMIT_WINDOW_MS) * RATE_LIMIT_TOKENS);
122
- if (refilled < 1) {
131
+ if (refilled < cost) {
123
132
  buckets.set(session, { tokens: refilled, lastRefillAt: now });
124
133
  return false;
125
134
  }
126
- buckets.set(session, { tokens: refilled - 1, lastRefillAt: now });
135
+ buckets.set(session, { tokens: refilled - cost, lastRefillAt: now });
127
136
  return true;
128
137
  };
129
138
  exports.checkRateLimit = checkRateLimit;
@@ -143,14 +152,53 @@ const isShellPane = (command) => {
143
152
  return false;
144
153
  return SHELL_COMMANDS.has(command);
145
154
  };
155
+ /** Where a message is parked between `set-buffer` and `paste-buffer`. Named,
156
+ * so the user's own paste stack is never pushed onto, and `-d` drops it
157
+ * again the moment it has been delivered. */
158
+ const INJECT_BUFFER = 'zeph-inject';
159
+ const sendLiteral = (session, text) => (0, child_process_1.spawnSync)('tmux', tmuxArgs(['send-keys', '-l', '-t', session, text]), { stdio: ['ignore', 'ignore', 'pipe'] })
160
+ .status === 0;
161
+ /**
162
+ * Put the message in the pane as a PASTE rather than as typing.
163
+ *
164
+ * `send-keys -l` hands the whole string to the application as if it had been
165
+ * typed at once, and a TUI that re-renders per character has to keep up with a
166
+ * burst it never sees from a human. With Hangul — three bytes and two columns
167
+ * per character — it does not: characters go missing and the wrapped line is
168
+ * painted twice. A phone message is a paste, so send it as one.
169
+ *
170
+ * `paste-buffer -p` brackets it only when the application has actually asked
171
+ * for bracketed paste (DECSET 2004); against one that has not, tmux sends the
172
+ * plain text and nothing changes. That gating is why this is safe to do
173
+ * unconditionally — the markers can never leak into an app that would show
174
+ * them as literal `[200~`.
175
+ *
176
+ * Falls back to the old path if either tmux call fails, because a message
177
+ * delivered imperfectly still beats one not delivered.
178
+ */
179
+ const pasteText = (session, text) => {
180
+ // `--` so a message that begins with a dash is data, not an option. The
181
+ // buffer contents are data to paste-buffer as well, which preserves the
182
+ // property `send-keys -l` had: no escape sequence inside a message can
183
+ // drive another tmux command.
184
+ const set = (0, child_process_1.spawnSync)('tmux', tmuxArgs(['set-buffer', '-b', INJECT_BUFFER, '--', text]), { stdio: ['ignore', 'ignore', 'pipe'] });
185
+ if (set.status !== 0)
186
+ return sendLiteral(session, text);
187
+ const paste = (0, child_process_1.spawnSync)('tmux', tmuxArgs(['paste-buffer', '-d', '-p', '-b', INJECT_BUFFER, '-t', session]), { stdio: ['ignore', 'ignore', 'pipe'] });
188
+ if (paste.status === 0)
189
+ return true;
190
+ // `-d` never ran, so the buffer is still there holding the message.
191
+ (0, child_process_1.spawnSync)('tmux', tmuxArgs(['delete-buffer', '-b', INJECT_BUFFER]), { stdio: ['ignore', 'ignore', 'pipe'] });
192
+ return sendLiteral(session, text);
193
+ };
146
194
  /**
147
- * Inject text into a tmux session: literal text via `-l`, then a
148
- * separate `Enter`. `-l` takes the text as data, so tmux escape
149
- * sequences inside the message can't drive other tmux commands.
195
+ * Inject text into a tmux session: the message as a paste, then a separate
196
+ * `Enter` to submit it. The two stay separate because a bracketed paste is
197
+ * text and only text — newlines inside it must not submit early, and the
198
+ * submit has to be a real key press.
150
199
  */
151
200
  const injectKeys = (session, text) => {
152
- const a = (0, child_process_1.spawnSync)('tmux', tmuxArgs(['send-keys', '-l', '-t', session, text]), { stdio: ['ignore', 'ignore', 'pipe'] });
153
- if (a.status !== 0)
201
+ if (!pasteText(session, text))
154
202
  return false;
155
203
  const b = (0, child_process_1.spawnSync)('tmux', tmuxArgs(['send-keys', '-t', session, 'Enter']), { stdio: ['ignore', 'ignore', 'pipe'] });
156
204
  return b.status === 0;
@@ -728,6 +776,47 @@ const capturePane = (sessionName, escapes = false, lines = SCREEN_PEEK_LINES) =>
728
776
  }
729
777
  return { content, truncated };
730
778
  };
779
+ /**
780
+ * Where the cursor sits, in the coordinates of a captured frame.
781
+ *
782
+ * `capture-pane` gives cells and colors but no cursor, so the mirror has never
783
+ * drawn one — which makes an arrow key indistinguishable from a dropped one.
784
+ * tmux reports the position separately; `pane_height` is what turns a
785
+ * pane-relative row into a row of the captured text, since the capture reaches
786
+ * back into scrollback and the visible pane is only its last `pane_height`
787
+ * lines.
788
+ *
789
+ * Read only for frames that are actually about to be sent — one extra tmux
790
+ * spawn per SENT frame rather than per tick.
791
+ */
792
+ const capturePaneCursor = (sessionName) => {
793
+ const r = (0, child_process_1.spawnSync)('tmux', tmuxArgs(['display-message', '-p', '-t', sessionName, '#{cursor_x},#{cursor_y},#{pane_height}']), { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
794
+ if (r.status !== 0)
795
+ return null;
796
+ const [x, y, height] = (r.stdout ?? '').trim().split(',').map(Number);
797
+ if (![x, y, height].every((n) => Number.isSafeInteger(n) && n >= 0))
798
+ return null;
799
+ return { x, y, height };
800
+ };
801
+ /**
802
+ * Translate the cursor into an index into the lines the frame carries.
803
+ *
804
+ * The visible pane is the tail of the capture, so the cursor's row counts back
805
+ * from the end — which also makes this correct after the byte cap has cut lines
806
+ * off the top. A cursor that falls outside what the frame carries returns null
807
+ * and simply is not drawn.
808
+ */
809
+ const cursorLineFor = (content, cursor) => {
810
+ // capture-pane ends the last row with a newline, which split() turns into a
811
+ // trailing empty element that is not a pane row.
812
+ const lines = content.split('\n');
813
+ const rows = lines.length > 0 && lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
814
+ const line = rows - cursor.height + cursor.y;
815
+ if (line < 0 || line >= rows)
816
+ return null;
817
+ return { line, col: cursor.x };
818
+ };
819
+ exports.cursorLineFor = cursorLineFor;
731
820
  /** Delays after a key injection at which the pane is re-captured and pushed
732
821
  * to the phone unsolicited. The first frame lands right after most TUI
733
822
  * redraws; the second catches slow animations and is skipped when nothing
@@ -744,6 +833,13 @@ const pendingInjectSnapshots = new Map();
744
833
  * captures so a burst of arrow presses yields frames after the LAST press.
745
834
  */
746
835
  const scheduleInjectSnapshots = (sessionName, send, delays = INJECT_SNAPSHOT_DELAYS_MS) => {
836
+ // These snapshots exist to show the phone what a keystroke did when nobody
837
+ // is mirroring the pane. A live stream already repaints it at the burst
838
+ // cadence, so scheduling them there is two extra blocking captures and two
839
+ // extra sends per keystroke — outside the stream's own send budget, which
840
+ // is exactly the cost the budget exists to bound.
841
+ if (activeStreams.has(sessionName))
842
+ return;
747
843
  for (const t of pendingInjectSnapshots.get(sessionName) ?? [])
748
844
  clearTimeout(t);
749
845
  let lastContent = null;
@@ -797,6 +893,13 @@ exports.streamCadence = streamCadence;
797
893
  * passed. False means this tick must skip WITHOUT marking the frame as sent —
798
894
  * the diff-gate would otherwise swallow that content for good.
799
895
  */
896
+ /** Is there budget left this second? A peek, so a tick can decline to pay for a
897
+ * blocking capture it could not send anyway — spending the token here instead
898
+ * would burn budget on ticks the diff-gate goes on to skip. */
899
+ const hasFrameBudget = (budget, now) => now - budget.windowStartedAt >= 1_000 || now < budget.windowStartedAt
900
+ ? true
901
+ : budget.sent < exports.MAX_FRAMES_PER_SEC;
902
+ exports.hasFrameBudget = hasFrameBudget;
800
903
  const claimFrameSend = (budget, now) => {
801
904
  // `now < windowStartedAt` = the clock ran backwards (NTP step, resume).
802
905
  // Without the guard the window never rolls again and a spent budget
@@ -970,12 +1073,13 @@ echo) => {
970
1073
  * (bad key, crypto not ready on the first tick) returns null so the caller
971
1074
  * drops the frame — an encrypted stream never leaks a plaintext frame.
972
1075
  */
973
- const buildStreamFrame = async (captured, sessionName, subscriberPublicKey) => {
1076
+ const buildStreamFrame = async (captured, sessionName, subscriberPublicKey, cursor) => {
974
1077
  const base = {
975
1078
  subtype: 'agent.stream.frame',
976
1079
  sessionName,
977
1080
  capturedAt: new Date().toISOString(),
978
1081
  truncated: captured.truncated,
1082
+ ...(cursor ? { cursorLine: cursor.line, cursorCol: cursor.col } : {}),
979
1083
  };
980
1084
  if (!subscriberPublicKey)
981
1085
  return { ...base, content: captured.content };
@@ -1054,6 +1158,12 @@ const handleStreamControl = (req, send) => {
1054
1158
  return true;
1055
1159
  }
1056
1160
  let lastContent = null;
1161
+ // The cursor's last reported place, as `line,col`. Part of the diff-gate
1162
+ // because moving the cursor is a change the CAPTURE cannot show: an arrow
1163
+ // key inside a prompt leaves every character where it was, so gating on the
1164
+ // text alone means the mirror's cursor never moves — which is precisely the
1165
+ // feedback the cursor exists to give.
1166
+ let lastCursor = null;
1057
1167
  const stats = {
1058
1168
  startedAt: Date.now(),
1059
1169
  frames: 0,
@@ -1104,8 +1214,28 @@ const handleStreamControl = (req, send) => {
1104
1214
  (0, exports.stopStream)(sessionName);
1105
1215
  return false;
1106
1216
  }
1217
+ // capturePane is a blocking tmux spawn. Asking the budget first means a
1218
+ // second that is already full costs nothing rather than paying for a
1219
+ // capture whose frame could not go out — a peek, not a claim, so a tick
1220
+ // the diff-gate goes on to skip does not spend the token.
1221
+ if (!(0, exports.hasFrameBudget)(budget, Date.now())) {
1222
+ stats.skipped++;
1223
+ stats.rateCapped++;
1224
+ return true;
1225
+ }
1107
1226
  const captured = capturePane(sessionName, true, STREAM_CAPTURE_LINES);
1108
- if (!captured || captured.content === lastContent) {
1227
+ if (!captured) {
1228
+ stats.skipped++;
1229
+ return true;
1230
+ }
1231
+ // Read per tick, not per sent frame: the gate below has to know whether
1232
+ // the cursor moved, and by the time a frame is being built the tick
1233
+ // that would have carried the move has already been skipped. One extra
1234
+ // tmux spawn on ticks that go on to send nothing is what that costs.
1235
+ const cursorAt = capturePaneCursor(sessionName);
1236
+ const cursor = cursorAt ? (0, exports.cursorLineFor)(captured.content, cursorAt) : null;
1237
+ const cursorKey = cursor ? `${cursor.line},${cursor.col}` : '';
1238
+ if (captured.content === lastContent && cursorKey === lastCursor) {
1109
1239
  stats.skipped++;
1110
1240
  return true; // diff-gate
1111
1241
  }
@@ -1124,12 +1254,13 @@ const handleStreamControl = (req, send) => {
1124
1254
  // fps by one frame per episode in the R2 numbers.
1125
1255
  const inBurst = armedDelay === exports.BURST_INTERVAL_MS;
1126
1256
  lastContent = captured.content;
1257
+ lastCursor = cursorKey;
1127
1258
  // Stamp the sequence in CAPTURE order, synchronously — frame assembly
1128
1259
  // is async (encryption) and fire-and-forget, so resolve order is not
1129
1260
  // guaranteed under load; the receiver drops any seq it has already
1130
1261
  // painted past.
1131
1262
  const seq = ++wireSeq;
1132
- void (0, exports.buildStreamFrame)(captured, sessionName, subscriberPublicKey).then((frame) => {
1263
+ void (0, exports.buildStreamFrame)(captured, sessionName, subscriberPublicKey, cursor).then((frame) => {
1133
1264
  // Stream stopped or restarted while this frame was in flight —
1134
1265
  // `stats` is unique per start, so it doubles as the identity
1135
1266
  // token. Don't send into a dead/replaced stream or skew its stats.
@@ -1141,6 +1272,7 @@ const handleStreamControl = (req, send) => {
1141
1272
  // subscriber key) never recovers: fail closed after a few
1142
1273
  // strikes instead of retrying every tick for 5 minutes.
1143
1274
  lastContent = null;
1275
+ lastCursor = null;
1144
1276
  // Init still in flight (or failed — its own path fail-closes):
1145
1277
  // a not-yet-ready key is not a malformed key, don't strike.
1146
1278
  if ((0, crypto_js_1.getDevicePublicKey)() === null)
@@ -1356,18 +1488,14 @@ const enqueueInput = (input, send) => {
1356
1488
  // would let the higher epoch supersede the other and silence it.
1357
1489
  const senderKey = input.deviceId ? `${input.sessionName}#${input.deviceId}` : input.sessionName;
1358
1490
  const sequencer = inputSequencerFor(senderKey);
1359
- if (!sequencer) {
1360
- log(`! input ${input.sessionName}: sender lane cap reached — drop`);
1361
- send(streamErrorFrame(input.sessionName, 'input_rejected', input));
1362
- return;
1363
- }
1491
+ if (!sequencer)
1492
+ return refuseInput(input, send, 'sender lane cap reached');
1364
1493
  const accepted = sequencer.accept({ ...input, send });
1365
1494
  if (accepted !== 'ok') {
1366
1495
  // overflow / superseded / stale — the message was dropped, and the
1367
1496
  // sender must hear so instead of waiting on a keystroke that never
1368
1497
  // lands (the sequencer reports swept HELD messages via onDiscard).
1369
- log(`! input ${input.sessionName}: ${accepted} — drop`);
1370
- send(streamErrorFrame(input.sessionName, 'input_rejected', input));
1498
+ refuseInput(input, send, accepted);
1371
1499
  }
1372
1500
  };
1373
1501
  /**
@@ -1399,17 +1527,21 @@ exports.pendingInputDecrypts = pendingInputDecrypts;
1399
1527
  * plaintext path runs — the envelope hides the payload from the relay, never
1400
1528
  * from the whitelist or the caps.
1401
1529
  */
1530
+ /**
1531
+ * Refuse one input message: log why here, tell the sender only THAT it was
1532
+ * refused. The reason is derived from plaintext the relay must not learn, so it
1533
+ * never rides the frame; a message with no sessionName gets no frame at all,
1534
+ * since the sender would have nothing to match it against.
1535
+ */
1536
+ const refuseInput = (msg, send, reason) => {
1537
+ log(`! input ${msg.sessionName ?? '(no session)'}: ${reason} — drop`);
1538
+ if (typeof msg.sessionName === 'string' && msg.sessionName) {
1539
+ send(streamErrorFrame(msg.sessionName, 'input_rejected', msg));
1540
+ }
1541
+ };
1402
1542
  const handleEncryptedInput = (msg, send) => {
1403
1543
  const { sessionName } = msg;
1404
- const refuse = (reason) => {
1405
- log(`! input ${sessionName ?? '(no session)'}: ${reason} — drop`);
1406
- // No sessionName means the sender can't match the error to anything.
1407
- // The reason never rides along: it is derived from plaintext the relay
1408
- // must not learn, so it stays in this host's log.
1409
- if (typeof sessionName === 'string' && sessionName) {
1410
- send(streamErrorFrame(sessionName, 'input_rejected', msg));
1411
- }
1412
- };
1544
+ const refuse = (reason) => refuseInput(msg, send, reason);
1413
1545
  if (typeof sessionName !== 'string' || !sessionName)
1414
1546
  return refuse('no sessionName');
1415
1547
  const stream = activeStreams.get(sessionName);
@@ -1467,11 +1599,10 @@ const handleEncryptedInput = (msg, send) => {
1467
1599
  // A real envelope clears the strikes: the cap is there to stop a flood,
1468
1600
  // not to retire a stream that saw one corrupted message.
1469
1601
  const live = activeStreams.get(sessionName);
1470
- if (live?.stats === incarnation)
1471
- live.inputDecryptFailures = 0;
1472
- if (activeStreams.get(sessionName)?.stats !== incarnation) {
1602
+ if (live?.stats !== incarnation) {
1473
1603
  return refuse('stream replaced while decrypting');
1474
1604
  }
1605
+ live.inputDecryptFailures = 0;
1475
1606
  const payload = parseSealedInput(opened);
1476
1607
  if (!payload)
1477
1608
  return refuse('sealed payload is not an input object');
@@ -1522,11 +1653,7 @@ const handleCommandInput = (msg, send) => {
1522
1653
  }
1523
1654
  const checked = (0, exports.validateInputMessage)(msg);
1524
1655
  if (!checked.ok) {
1525
- log(`! input ${msg.sessionName ?? '(no session)'}: ${checked.reason} — drop`);
1526
- // No sessionName means the sender can't match the error to anything.
1527
- if (typeof msg.sessionName === 'string' && msg.sessionName) {
1528
- send(streamErrorFrame(msg.sessionName, 'input_rejected', msg));
1529
- }
1656
+ refuseInput(msg, send, checked.reason);
1530
1657
  return true;
1531
1658
  }
1532
1659
  const { input } = checked;
@@ -1535,8 +1662,7 @@ const handleCommandInput = (msg, send) => {
1535
1662
  // against. The sender learns in one hop and falls back to a REST push.
1536
1663
  const stream = activeStreams.get(input.sessionName);
1537
1664
  if (!stream) {
1538
- log(`! input ${input.sessionName}: no live stream — drop`);
1539
- send(streamErrorFrame(input.sessionName, 'input_rejected', input));
1665
+ refuseInput(input, send, 'no live stream');
1540
1666
  return true;
1541
1667
  }
1542
1668
  // This stream's outbound half is E2EE for that subscriber, so accepting a
@@ -1544,8 +1670,7 @@ const handleCommandInput = (msg, send) => {
1544
1670
  // and nothing about it proves the subscriber sent it. An encrypted stream
1545
1671
  // takes sealed input (above) or none.
1546
1672
  if (stream.subscriberPublicKey) {
1547
- log(`! input ${input.sessionName}: stream is E2EE, plaintext input refused — drop`);
1548
- send(streamErrorFrame(input.sessionName, 'input_rejected', input));
1673
+ refuseInput(input, send, 'stream is E2EE, plaintext input refused');
1549
1674
  return true;
1550
1675
  }
1551
1676
  enqueueInput(input, send);
@@ -1639,11 +1764,11 @@ exports.collectSessions = collectSessions;
1639
1764
  * the structured `agent.command` push type and the legacy `@<session>`
1640
1765
  * prefix path route through here so the defense layers can't diverge.
1641
1766
  */
1642
- const passesInjectGuards = (session, deps) => {
1767
+ const passesInjectGuards = (session, deps, cost = 1) => {
1643
1768
  // Rate bucket first: the pane probe below is a blocking tmux spawnSync,
1644
1769
  // and the sequencer can flush several held messages back-to-back — an
1645
1770
  // empty bucket must refuse before paying that probe N times, not after.
1646
- if (!(deps.rateLimit ?? exports.checkRateLimit)(session)) {
1771
+ if (!(deps.rateLimit ?? exports.checkRateLimit)(session, Date.now(), cost)) {
1647
1772
  log(`! ${session}: rate-limited — drop`);
1648
1773
  return false;
1649
1774
  }
@@ -1686,7 +1811,7 @@ const tryInject = (session, text, deps) => {
1686
1811
  log(`! ${session}: empty text — drop`);
1687
1812
  return false;
1688
1813
  }
1689
- if (!passesInjectGuards(session, deps))
1814
+ if (!passesInjectGuards(session, deps, SUBMIT_COST))
1690
1815
  return false;
1691
1816
  const ok = (deps.inject ?? injectKeys)(session, text);
1692
1817
  const preview = text.length > 60 ? text.slice(0, 60) + '…' : text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/cli",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Zeph CLI + push notification SDK for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",