polygram 0.10.0-rc.40 → 0.10.0-rc.41
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.41",
|
|
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",
|
|
@@ -202,6 +202,14 @@ const DEFAULT_QUIESCE_MS = 500; // require READY for this long before decl
|
|
|
202
202
|
// wedge-detection delay is bounded.
|
|
203
203
|
const DEFAULT_HARD_BACKSTOP_MS = 4 * 60 * 60_000; // 4 hours
|
|
204
204
|
const IDLE_POLL_INTERVAL_MS = 30_000; // 30 s
|
|
205
|
+
// 0.10.0 H4 — `Stop` hook as authoritative turn-done.
|
|
206
|
+
// The Stop hook fires when claude finishes responding — same
|
|
207
|
+
// semantic as the JSONL `result` event. Both should land within
|
|
208
|
+
// ms of each other; this grace gives JSONL a chance to win (full
|
|
209
|
+
// result data: subtype, stopReason, all the metadata) before the
|
|
210
|
+
// Stop hook synthesizes a fallback settle. If the JSONL stream is
|
|
211
|
+
// broken or stuck, Stop carries the turn to completion alone.
|
|
212
|
+
const DEFAULT_STOP_GRACE_MS = 2_000; // 2 s
|
|
205
213
|
|
|
206
214
|
// B8 (slow-MCP readiness): how long the claude `--debug-file` log must
|
|
207
215
|
// have had NO new bytes appended before the startup is considered
|
|
@@ -251,6 +259,7 @@ class TmuxProcess extends Process {
|
|
|
251
259
|
readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS,
|
|
252
260
|
turnTimeoutMs = DEFAULT_TURN_TIMEOUT_MS,
|
|
253
261
|
hardBackstopMs = DEFAULT_HARD_BACKSTOP_MS,
|
|
262
|
+
stopGraceMs = DEFAULT_STOP_GRACE_MS,
|
|
254
263
|
pollMs = DEFAULT_POLL_MS,
|
|
255
264
|
quiesceMs = DEFAULT_QUIESCE_MS,
|
|
256
265
|
lateGraceMs = 1500,
|
|
@@ -295,6 +304,7 @@ class TmuxProcess extends Process {
|
|
|
295
304
|
this.readyTimeoutMs = readyTimeoutMs;
|
|
296
305
|
this.turnTimeoutMs = turnTimeoutMs;
|
|
297
306
|
this.hardBackstopMs = hardBackstopMs;
|
|
307
|
+
this.stopGraceMs = stopGraceMs;
|
|
298
308
|
this.pollMs = pollMs;
|
|
299
309
|
this.quiesceMs = quiesceMs;
|
|
300
310
|
this.readyDebugQuietMs = readyDebugQuietMs;
|
|
@@ -1601,7 +1611,7 @@ class TmuxProcess extends Process {
|
|
|
1601
1611
|
}
|
|
1602
1612
|
|
|
1603
1613
|
/**
|
|
1604
|
-
* Hook-event handler.
|
|
1614
|
+
* Hook-event handler. Four roles, layered over time:
|
|
1605
1615
|
*
|
|
1606
1616
|
* H1 (rc.36) — emit `hook-event` so polygram persists each event
|
|
1607
1617
|
* to the events DB; observer-only.
|
|
@@ -1615,24 +1625,63 @@ class TmuxProcess extends Process {
|
|
|
1615
1625
|
* the structural fix for the msg-884 incident (49-min
|
|
1616
1626
|
* SoundCloud subagent killed at the 30-min wall-clock while
|
|
1617
1627
|
* demonstrably alive).
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1628
|
+
* H4 (rc.41) — `Stop` hook is an authoritative turn-done signal.
|
|
1629
|
+
* If JSONL `result` doesn't fire within `stopGraceMs`,
|
|
1630
|
+
* synthesize a settle from the Stop payload so a broken or
|
|
1631
|
+
* stuck JSONL stream can't strand a finished turn. Promise-
|
|
1632
|
+
* resolve idempotency means JSONL still wins when both fire.
|
|
1620
1633
|
*
|
|
1621
1634
|
* Parse errors and unknown event shapes are intentionally still
|
|
1622
1635
|
* forwarded — observer-only metrics for stream-reliability soak.
|
|
1623
1636
|
*/
|
|
1624
1637
|
_handleHookEvent(ev) {
|
|
1625
1638
|
// H3: every hook event (except the diagnostic types) is liveness
|
|
1626
|
-
// evidence. Heartbeat every turn
|
|
1627
|
-
// idle-ceiling poller resets. We don't differentiate by event
|
|
1639
|
+
// evidence. Heartbeat every turn we can identify as in-flight so
|
|
1640
|
+
// the idle-ceiling poller resets. We don't differentiate by event
|
|
1628
1641
|
// type — even Notification or UserPromptSubmit prove claude is
|
|
1629
1642
|
// active in this session.
|
|
1643
|
+
//
|
|
1644
|
+
// Two scopes are searched (deduped via Set): active group turns
|
|
1645
|
+
// (the steady state once `user-message` has landed) AND the
|
|
1646
|
+
// pendingQueue head (the PRE-active window between turn start
|
|
1647
|
+
// and the first `user-message`). Hook events can fire in either
|
|
1648
|
+
// window — e.g. `UserPromptSubmit` arrives just after claude
|
|
1649
|
+
// receives the paste but BEFORE the `user-message` is echoed
|
|
1650
|
+
// back into the JSONL. Without the pendingQueue fallback, that
|
|
1651
|
+
// window leaves the turn un-heartbeated and the idle poller
|
|
1652
|
+
// could fire on a turn that's actively starting up.
|
|
1630
1653
|
if (ev?.type && ev.type !== 'parse-error' && ev.type !== 'unknown') {
|
|
1631
|
-
const turns = this._activeGroup?.turns || [];
|
|
1654
|
+
const turns = new Set(this._activeGroup?.turns || []);
|
|
1655
|
+
const head = this.pendingQueue[0];
|
|
1656
|
+
if (head) turns.add(head);
|
|
1632
1657
|
for (const t of turns) {
|
|
1633
1658
|
this._heartbeat(t, `hook:${ev.type}`);
|
|
1634
1659
|
}
|
|
1635
1660
|
}
|
|
1661
|
+
// H4: Stop hook → synthesize a settle for the primary turn after
|
|
1662
|
+
// a grace, so JSONL `result` (which carries richer metadata)
|
|
1663
|
+
// wins when both fire. If JSONL never arrives — broken stream,
|
|
1664
|
+
// stuck parser — the Stop synth settles the turn instead of
|
|
1665
|
+
// stranding it. Idempotent: a later JSONL settleResult call is
|
|
1666
|
+
// a no-op once the promise has resolved.
|
|
1667
|
+
if (ev?.type === 'Stop') {
|
|
1668
|
+
const primary = (this._activeGroup?.turns || [])
|
|
1669
|
+
.find((t) => t.kind === 'primary');
|
|
1670
|
+
if (primary && typeof primary.settleResult === 'function') {
|
|
1671
|
+
const synth = {
|
|
1672
|
+
text: primary.text || ev.lastAssistantMessage || '',
|
|
1673
|
+
subtype: 'success',
|
|
1674
|
+
stopReason: 'stop_hook',
|
|
1675
|
+
sessionId: this.claudeSessionId,
|
|
1676
|
+
via: 'stop-hook',
|
|
1677
|
+
};
|
|
1678
|
+
const timer = setTimeout(
|
|
1679
|
+
() => primary.settleResult(synth),
|
|
1680
|
+
this.stopGraceMs,
|
|
1681
|
+
);
|
|
1682
|
+
timer.unref?.();
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1636
1685
|
this.emit('hook-event', ev);
|
|
1637
1686
|
}
|
|
1638
1687
|
|
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.41",
|
|
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": {
|