polygram 0.10.0-rc.36 → 0.10.0-rc.38

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.36",
4
+ "version": "0.10.0-rc.38",
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",
@@ -344,13 +344,27 @@ function createSdkCallbacks({
344
344
  }
345
345
  },
346
346
 
347
- // 0.10.0 H1 (observer-only): tmux backend hook-based turn
348
- // observability. TmuxProcess emits `hook-event` with normalized
349
- // HookEvent records for every claude-CLI hook firing (PreToolUse,
347
+ // 0.10.0 H1 (observer-only) + H2 (reactor wiring): tmux backend
348
+ // hook-based turn observability + status.
349
+ //
350
+ // H1: TmuxProcess emits `hook-event` with normalized HookEvent
351
+ // records for every claude-CLI hook firing (PreToolUse,
350
352
  // PostToolUse, UserPromptSubmit, Stop, SubagentStop, Notification,
351
- // plus `unknown` for any schema drift). Persisted compact so the
352
- // soak can characterize the stream's reliability against real
353
- // Music traffic before H2/H3/H4 consume it.
353
+ // plus `unknown` for any schema drift). Persisted compact for
354
+ // forensic soak analysis.
355
+ //
356
+ // H2: routes hook events to the head pending's reactor so the
357
+ // Telegram emoji reflects what claude is actually doing — incl.
358
+ // subagent-inner tool fires (PreToolUse with `agent_id`) that
359
+ // JSONL `tool-use` never surfaces. The win: long subagent turns
360
+ // stop tripping the 🥱→😨→🤯 escalation because each inner
361
+ // PostToolUse / SubagentStop / Notification heartbeats the
362
+ // reactor, proving the agent is alive.
363
+ //
364
+ // Augments — does NOT replace — the existing JSONL-driven
365
+ // `onToolUse` setState and stream-chunk heartbeats. Duplicate
366
+ // setState for the same state is a no-op in the reactor; the
367
+ // throttle/cascade timers are unchanged.
354
368
  //
355
369
  // Fields persisted are intentionally narrow: identity + tool/
356
370
  // subagent scoping + `duration_ms` (free per-tool latency from
@@ -359,8 +373,9 @@ function createSdkCallbacks({
359
373
  // (`tool_input`, full `tool_response`, `last_assistant_message`)
360
374
  // are NOT persisted to the events DB — they'd inflate row size
361
375
  // without informing the soak.
362
- onHookEvent: (sessionKey, payload /* , entry */) => {
376
+ onHookEvent: (sessionKey, payload, entry) => {
363
377
  try {
378
+ // ── H1: DB persist ────────────────────────────────────────
364
379
  const detail = {
365
380
  chat_id: getChatIdFromKey(sessionKey),
366
381
  session_key: sessionKey,
@@ -387,6 +402,58 @@ function createSdkCallbacks({
387
402
  detail.parse_error = payload?.error ?? null;
388
403
  }
389
404
  logEvent('hook-event', detail);
405
+
406
+ // ── H2: route to reactor ──────────────────────────────────
407
+ //
408
+ // The reactor lives on the HEAD pending's per-turn context
409
+ // (same shape as `onToolUse` and `onStreamChunk`). Hook
410
+ // events from claude can land in three windows relative to
411
+ // a polygram turn:
412
+ // 1. Mid-turn (the normal case) — head exists, reactor
413
+ // lives, route the event.
414
+ // 2. Between turns / before head is set — head is null,
415
+ // skip silently. The next setState from polygram-side
416
+ // turn lifecycle will recover.
417
+ // 3. UserPromptSubmit fires BEFORE polygram's
418
+ // reactor.setState('THINKING') in some races; that's
419
+ // fine because UserPromptSubmit is intentionally a
420
+ // no-op here (the existing turn-start path owns it).
421
+ const head = entry?.pendingQueue?.[0];
422
+ const reactor = head?.context?.reactor;
423
+ if (!reactor) return;
424
+
425
+ switch (payload?.type) {
426
+ case 'PreToolUse':
427
+ // PreToolUse fires for main-agent AND subagent-inner
428
+ // tools (the latter scoped by `agent_id`). The reactor
429
+ // doesn't care WHO ran the tool, only WHAT — so
430
+ // classifyToolName drives the state regardless of
431
+ // agent context.
432
+ if (payload.toolName) {
433
+ reactor.setState(classifyToolName(payload.toolName));
434
+ }
435
+ break;
436
+
437
+ case 'PostToolUse':
438
+ case 'SubagentStop':
439
+ case 'Notification':
440
+ // Liveness signals — each one proves the agent is still
441
+ // making progress. Heartbeat resets the STALL (🥱) and
442
+ // TIMEOUT (😨) timers, killing the fear escalation on
443
+ // long healthy turns that was the motivating msg-884
444
+ // incident.
445
+ if (typeof reactor.heartbeat === 'function') {
446
+ reactor.heartbeat();
447
+ }
448
+ break;
449
+
450
+ // UserPromptSubmit, Stop, unknown, parse-error: no
451
+ // reactor routing. Turn lifecycle owns start/clear; the
452
+ // observer-only H1 DB persist above still records them
453
+ // for forensics.
454
+ default:
455
+ break;
456
+ }
390
457
  } catch (err) {
391
458
  logger.error?.(`[${botName}] hook-event handler: ${err.message}`);
392
459
  }
@@ -57,6 +57,17 @@ const POLYGRAM_DISPLAY_HINT = [
57
57
  '- Headers `#`, `##`, `###` render as plain text — use **bold** for emphasis.',
58
58
  '- Horizontal rules render as a thin divider line.',
59
59
  '- Long replies stream in chunks; prefer concise structure over walls of text.',
60
+ '',
61
+ '### NEVER emit shell-context canned strings — HARD RULE',
62
+ '',
63
+ 'You are running as a Telegram chat bot, NOT as a script being piped into a shell. Certain phrases are CLI-context boilerplate from the underlying environment and MUST NEVER appear in a reply, because the user sees them as a literal message from you and they look like a system error:',
64
+ '',
65
+ '- `No response requested.`',
66
+ '- `No response needed.`',
67
+ '- `Continuing...` as a standalone reply',
68
+ '- Any other shell-prompt-style filler that acknowledges silence',
69
+ '',
70
+ 'If a user message is short, ambiguous, or feels like a no-op acknowledgement (e.g. `okay`, `ok`, `yes`, `got it`, `thanks`), reply with a brief substantive line — acknowledge what you understood and what (if anything) you will do next. If you genuinely have nothing useful to say, ask ONE specific clarifying question. NEVER emit a placeholder or a shell-style canned string — the chat surface has no silent-no-op state. Every reply must be intentional content.',
60
71
  ].join('\n');
61
72
 
62
73
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.10.0-rc.36",
3
+ "version": "0.10.0-rc.38",
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": {