polygram 0.12.0-rc.27 → 0.12.0-rc.28

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.
@@ -1207,6 +1207,42 @@ class CliProcess extends Process {
1207
1207
  }
1208
1208
  }
1209
1209
 
1210
+ /**
1211
+ * Keep a turn alive while claude is still TOOL-working (2026-06-08 Shumabit@UMI
1212
+ * WA-topic incident). The per-turn reply-quiet window (`quietTimer`, armed in
1213
+ * _recordReplyForPendingTurn) is reset ONLY by reply tool calls. So a turn that
1214
+ * replied and then kept working with NON-reply tools — claude read a follow-up
1215
+ * screenshot, ran a Bash API check, then replied again 90s later, all inside ONE
1216
+ * claude turn (no Stop hook until the very end) — resolved on the reply-quiet
1217
+ * window WHILE claude was still active. Its reactor + typing tore down at the
1218
+ * premature resolve, and the late reply fell through to the orphan
1219
+ * autonomous-assistant-message path (no pending turn).
1220
+ *
1221
+ * claude's Stop hook is the AUTHORITATIVE turn-end; until it lands, a PreToolUse/
1222
+ * PostToolUse hook means claude is still working — so push the reply-quiet window
1223
+ * (and the idle ceiling) out. The absoluteTimer (30-min runaway cap) is left
1224
+ * untouched as the hard backstop, and Stop still resolves normally. Turns that
1225
+ * reply then truly stop (no intervening tool hooks) are unaffected — there's no
1226
+ * tool activity to extend on.
1227
+ */
1228
+ _extendQuietOnToolActivity() {
1229
+ for (const [turnId, pending] of this.pendingTurns) {
1230
+ if (pending._stopGracePending) continue; // already finalizing — don't revive
1231
+ // Idle ceiling: tool activity IS activity (mirrors the per-reply reset at
1232
+ // _recordReplyForPendingTurn so a long tool-heavy turn isn't idle-killed).
1233
+ if (pending.hardTimer) {
1234
+ clearTimeout(pending.hardTimer);
1235
+ pending.hardTimer = setTimeout(() => pending._fireTimeout?.('idle'), this.turnTimeoutMs);
1236
+ }
1237
+ // Reply-quiet window: only armed AFTER the first reply — that's exactly the
1238
+ // post-reply-still-tooling case this fixes.
1239
+ if (pending.quietTimer) {
1240
+ clearTimeout(pending.quietTimer);
1241
+ pending.quietTimer = setTimeout(() => this._resolveTurn(turnId), this.turnQuietMs);
1242
+ }
1243
+ }
1244
+ }
1245
+
1210
1246
  // 0.12 Phase 1.7 (Finding 0.1.A): two-step turn resolution.
1211
1247
  // _resolveTurn — entry point called by channel-result OR quiet-window
1212
1248
  // expiry. Schedules a stopGraceMs window during which
@@ -1799,6 +1835,15 @@ class CliProcess extends Process {
1799
1835
  });
1800
1836
  }
1801
1837
 
1838
+ // 2026-06-08 (WA-topic incident): a tool hook means claude is STILL working,
1839
+ // so keep its turn(s) alive — the reply-quiet window must not resolve a turn
1840
+ // mid-work just because the last *reply* was a few seconds ago. See
1841
+ // _extendQuietOnToolActivity. Reply tool calls are handled separately (their
1842
+ // own reset in _recordReplyForPendingTurn); covering them here too is a no-op.
1843
+ if (ev.type === 'PreToolUse' || ev.type === 'PostToolUse') {
1844
+ this._extendQuietOnToolActivity();
1845
+ }
1846
+
1802
1847
  switch (ev.type) {
1803
1848
  case 'UserPromptSubmit':
1804
1849
  this.emit('turn-start', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.12.0-rc.27",
3
+ "version": "0.12.0-rc.28",
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": {