polygram 0.10.0-rc.13 → 0.10.0-rc.14
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.10.0-rc.
|
|
4
|
+
"version": "0.10.0-rc.14",
|
|
5
5
|
"description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
|
@@ -387,14 +387,18 @@ class TmuxProcess extends Process {
|
|
|
387
387
|
try {
|
|
388
388
|
// R2-F1: sanitization happens inside runner.pasteText; we also
|
|
389
389
|
// log when chars get stripped.
|
|
390
|
-
|
|
390
|
+
// rc.13.1: pasteAndEnter holds a per-session async lock around
|
|
391
|
+
// paste + Enter so a concurrent injectUserMessage paste can't
|
|
392
|
+
// interleave keystrokes with this primary turn's prompt
|
|
393
|
+
// (shumorobot 2026-05-15 saw msg 696's prompt truncated at
|
|
394
|
+
// `chat_id="-1003` by msg 698's autosteer paste cutting in).
|
|
395
|
+
const result = await this._pasteAndEnter(prompt);
|
|
391
396
|
if (result.stripped > 0) {
|
|
392
397
|
this.logger.warn?.(
|
|
393
398
|
`[${this.label}] stripped ${result.stripped} control chars from prompt`,
|
|
394
399
|
);
|
|
395
400
|
this.emit('prompt-sanitized', { stripped: result.stripped, source: 'send' });
|
|
396
401
|
}
|
|
397
|
-
await this.runner.sendControl(this.tmuxName, 'Enter');
|
|
398
402
|
|
|
399
403
|
// Race: JSONL result event vs capture-pane quiescence fallback
|
|
400
404
|
// vs hard timeout. JSONL is the primary signal (carries structured
|
|
@@ -814,6 +818,31 @@ class TmuxProcess extends Process {
|
|
|
814
818
|
}
|
|
815
819
|
}
|
|
816
820
|
|
|
821
|
+
/**
|
|
822
|
+
* rc.13.1: paste a prompt body AND press Enter as an atomic
|
|
823
|
+
* per-session operation. The production runner provides
|
|
824
|
+
* `pasteAndEnter(name, text)` which holds a per-session async lock
|
|
825
|
+
* across the paste+Enter pair so concurrent pasteText calls from
|
|
826
|
+
* a primary pm.send and a parallel injectUserMessage cannot
|
|
827
|
+
* interleave keystrokes in the TUI input box (root cause of the
|
|
828
|
+
* shumorobot 2026-05-15 reply-mis-attribution bug: msg 696's
|
|
829
|
+
* paste was truncated at `chat_id="-1003` when msg 698's autosteer
|
|
830
|
+
* paste cut in).
|
|
831
|
+
*
|
|
832
|
+
* Test runners without pasteAndEnter fall back to the sequential
|
|
833
|
+
* pasteText + sendControl(Enter) pair. Behaviour-equivalent for
|
|
834
|
+
* non-concurrent test scenarios; only production with a real tmux
|
|
835
|
+
* + concurrent injects exposes the race.
|
|
836
|
+
*/
|
|
837
|
+
async _pasteAndEnter(text) {
|
|
838
|
+
if (typeof this.runner.pasteAndEnter === 'function') {
|
|
839
|
+
return this.runner.pasteAndEnter(this.tmuxName, text);
|
|
840
|
+
}
|
|
841
|
+
const result = await this.runner.pasteText(this.tmuxName, text);
|
|
842
|
+
await this.runner.sendControl(this.tmuxName, 'Enter');
|
|
843
|
+
return result;
|
|
844
|
+
}
|
|
845
|
+
|
|
817
846
|
// ─── completion detection (§4.A capture-pane diff path — fallback) ──
|
|
818
847
|
|
|
819
848
|
/**
|
|
@@ -1207,9 +1236,13 @@ class TmuxProcess extends Process {
|
|
|
1207
1236
|
// intended extra-turn-reply path with reply_to=msg 686).
|
|
1208
1237
|
const oneLine = safe.replace(/\r?\n/g, ' / ');
|
|
1209
1238
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1239
|
+
// rc.13.1: use pasteAndEnter so the autosteer's paste+Enter pair
|
|
1240
|
+
// serialises behind any in-flight pm.send paste+Enter. Without
|
|
1241
|
+
// the lock, the autosteer paste could interleave keystrokes into
|
|
1242
|
+
// the middle of a primary turn's prompt (caught on shumorobot
|
|
1243
|
+
// 2026-05-15: msg 696's prompt got truncated mid-attribute when
|
|
1244
|
+
// msg 698's autosteer paste cut in).
|
|
1245
|
+
this._pasteAndEnter(safe)
|
|
1213
1246
|
.catch((err) => this.emit('inject-fail', { err: err.message }));
|
|
1214
1247
|
|
|
1215
1248
|
// Tell the next assistant-chunk to open a fresh Telegram bubble
|
package/lib/tmux/tmux-runner.js
CHANGED
|
@@ -32,6 +32,7 @@ const childProcess = require('child_process');
|
|
|
32
32
|
const crypto = require('crypto');
|
|
33
33
|
const fs = require('fs');
|
|
34
34
|
const path = require('path');
|
|
35
|
+
const { createAsyncLock } = require('../async-lock');
|
|
35
36
|
|
|
36
37
|
// ─── Constants ───────────────────────────────────────────────────────
|
|
37
38
|
|
|
@@ -196,6 +197,32 @@ function createTmuxRunner({ logger = console, runFn = run } = {}) {
|
|
|
196
197
|
await runFn('tmux', ['send-keys', '-t', name, key]);
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
// rc.13.1: paste+Enter must be ATOMIC per session. Pre-rc.13.1 two
|
|
201
|
+
// concurrent pasteText+sendControl pairs could interleave in the
|
|
202
|
+
// TUI's bracketed-paste buffer — Ivan caught this on shumorobot
|
|
203
|
+
// 2026-05-15 (the 2233-char user JSONL entry contained one
|
|
204
|
+
// truncated polygram channel + a full nested polygram prompt for
|
|
205
|
+
// a different msg_id). Symptom: msg 696's paste was at byte
|
|
206
|
+
// `chat_id="-1003` when msg 698's autosteer paste cut in,
|
|
207
|
+
// concatenating two pastes into one TUI user message → the agent
|
|
208
|
+
// saw a malformed input → the reply attribution went sideways
|
|
209
|
+
// (msg 697 got msg 698's answer, msg 696 got served last).
|
|
210
|
+
//
|
|
211
|
+
// The async-lock is keyed by tmux session name, so different
|
|
212
|
+
// sessions don't block each other. Within one session, pasteText
|
|
213
|
+
// + sendControl(Enter) hold the lock atomically.
|
|
214
|
+
const inputLock = createAsyncLock();
|
|
215
|
+
async function pasteAndEnter(name, text) {
|
|
216
|
+
const release = await inputLock.acquire(name);
|
|
217
|
+
try {
|
|
218
|
+
const res = await pasteText(name, text);
|
|
219
|
+
await sendControl(name, 'Enter');
|
|
220
|
+
return res;
|
|
221
|
+
} finally {
|
|
222
|
+
release();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
199
226
|
/**
|
|
200
227
|
* Push a multi-line text prompt into the pane.
|
|
201
228
|
*
|
|
@@ -293,6 +320,7 @@ function createTmuxRunner({ logger = console, runFn = run } = {}) {
|
|
|
293
320
|
spawn,
|
|
294
321
|
sendControl,
|
|
295
322
|
pasteText,
|
|
323
|
+
pasteAndEnter,
|
|
296
324
|
capturePane,
|
|
297
325
|
captureWide,
|
|
298
326
|
sessionExists,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.10.0-rc.
|
|
3
|
+
"version": "0.10.0-rc.14",
|
|
4
4
|
"description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
|
|
5
5
|
"main": "lib/ipc/client.js",
|
|
6
6
|
"bin": {
|