polygram 0.11.0-rc.7 → 0.11.0-rc.9

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.11.0-rc.7",
4
+ "version": "0.11.0-rc.9",
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",
@@ -365,53 +365,96 @@ class ChannelsProcess extends Process {
365
365
  '--dangerously-load-development-channels', 'server:polygram-bridge',
366
366
  ];
367
367
 
368
- // Parity audit P8: branch on existingSessionId. `--session-id <id>`
369
- // creates a NEW claude session with that id; `--resume <id>` resumes the
370
- // EXISTING conversation. Lazy-respawn after bridge-disconnect must use
371
- // --resume so conversation history is preserved. Mirrors
372
- // tmux-process.js:514-518.
368
+ // Resolve config FIRST so the --resume file-check below has the correct
369
+ // cwd (it picks up topic precedence). Other flags get pushed in order
370
+ // after this.
371
+ const topicConfig = opts.threadId && opts.chatConfig?.topics?.[opts.threadId];
372
+ const agent = topicConfig?.agent || opts.chatConfig?.agent || opts.agent;
373
+ const model = topicConfig?.model || opts.chatConfig?.model || opts.model;
374
+ const effort = topicConfig?.effort || opts.chatConfig?.effort || opts.effort;
375
+ const resolvedCwd = topicConfig?.cwd || opts.chatConfig?.cwd || opts.cwd;
376
+
377
+ // Parity audit P8 + rc.8 fs-guard (2026-05-26 shumorobot Music topic):
378
+ // `--session-id <id>` creates a NEW claude session with that id;
379
+ // `--resume <id>` resumes the EXISTING conversation. Lazy-respawn after
380
+ // bridge-disconnect must use --resume so conversation history is
381
+ // preserved. Mirrors tmux-process.js:514-518.
373
382
  //
374
- // rc.6 note: pre-channels session ids (created by sdk/tmux backends and
375
- // persisted in polygram's DB) are NOT cross-backend compatible claude
376
- // indexes session files under a cwd-derived projects dir that may not
377
- // match what channels-mode passes, leading to silent "No conversation
378
- // found" exit. The fix lives at the data layer, not here: operator
379
- // clears pre-channels session ids from polygram's sessions table before
380
- // flipping a chat to channels (see ops/migrate-to-channels.sql).
381
- if (opts.existingSessionId) {
383
+ // rc.8 ghost-session guard: polygram persists claude_session_id to its
384
+ // DB as soon as the bridge handshakes (onInit), but claude only writes
385
+ // the JSONL after a successful turn. If an early channels attempt fails
386
+ // before claude completes any turn, polygram's DB ends up with a
387
+ // claude_session_id that has NO corresponding file under claude's
388
+ // projects dir. Subsequent `--resume <ghost-id>` makes claude exit
389
+ // clean with "No conversation found" exactly the Music topic stall
390
+ // observed at 04:04:29 (session_id=567c72db never persisted; rc.4
391
+ // pane snapshot proved it).
392
+ //
393
+ // Fix: before passing --resume, verify the session JSONL actually
394
+ // exists under the launch cwd. If not, drop the ghost id and use
395
+ // --session-id with the freshly-generated uuid — claude creates a
396
+ // fresh session and onInit re-upserts the DB row.
397
+ //
398
+ // Resume cases preserved:
399
+ // - in-daemon lazy respawn (file written after first successful turn)
400
+ // - daemon restart on a chat that completed at least one turn
401
+ // Resume cases correctly dropped:
402
+ // - cross-backend stale ids (different cwd → different projects dir)
403
+ // - ghost ids from failed-before-first-turn attempts
404
+ let canResume = false;
405
+ let resumePath = null;
406
+ if (opts.existingSessionId && resolvedCwd) {
407
+ // claude's projects dir naming: cwd with '/' → '-'.
408
+ // Verified live at ~/.claude/projects/-Users-ivanshumkov-Music-rekordbox/
409
+ const cwdMangled = resolvedCwd.replace(/\//g, '-');
410
+ resumePath = path.join(os.homedir(), '.claude', 'projects', cwdMangled, `${opts.existingSessionId}.jsonl`);
411
+ try { canResume = fs.statSync(resumePath).isFile(); } catch { canResume = false; }
412
+ }
413
+ if (canResume) {
382
414
  claudeArgs.push('--resume', opts.existingSessionId);
383
415
  } else {
384
416
  claudeArgs.push('--session-id', this.claudeSessionId);
417
+ if (opts.existingSessionId) {
418
+ this.logger.warn?.(
419
+ `[${this.label}] channels: dropping DB session ${opts.existingSessionId} — ` +
420
+ `no local file at ${resumePath || '<unknown cwd>'}. Starting fresh with ${this.claudeSessionId}.`,
421
+ );
422
+ }
385
423
  }
386
-
387
- // Parity audit P4: per-chat / per-topic agent flag. Without this, chats
388
- // configured with agent='music-curation' / 'shumabit' silently fall back
389
- // to claude's default. Mirrors tmux-process.js:527.
390
- // Topic precedence over chat — same as tmux pattern (parity audit P7).
391
- const topicConfig = opts.threadId && opts.chatConfig?.topics?.[opts.threadId];
392
- const agent = topicConfig?.agent || opts.chatConfig?.agent || opts.agent;
393
- const model = topicConfig?.model || opts.chatConfig?.model || opts.model;
394
- const effort = topicConfig?.effort || opts.chatConfig?.effort || opts.effort;
395
424
  if (agent) claudeArgs.push('--agent', agent);
396
425
  if (model) claudeArgs.unshift('--model', model);
397
426
  if (effort) claudeArgs.push('--effort', effort);
398
427
 
399
- // Mirror TmuxProcess pattern (lib/process/tmux-process.js:522-525):
400
- // honor opts.permissionMode === 'bypassPermissions' by passing
401
- // --dangerously-skip-permissions. In that mode Claude auto-approves all
402
- // tool uses, so permission_request notifications stop firing (the bridge
403
- // relay becomes inactive — that's the chat owner's choice).
404
- // Default (no bypass): permission_request flows through the relay → TG
405
- // approve/deny buttons via polygram's onApprovalRequired handler.
428
+ // rc.9 (2026-05-26 shumorobot first-turn-dead-zone diagnosis): channels
429
+ // backend defaults to permissionMode='bypassPermissions'. Without it,
430
+ // claude TUI shows the canonical interactive permission prompt for
431
+ // every `mcp__polygram-bridge__reply` call:
432
+ //
433
+ // polygram-bridge - reply(...) (MCP)
434
+ // Do you want to proceed?
435
+ // ❯ 1. Yes 2. Yes, and don't ask again 3. No
436
+ //
437
+ // Channels mode has no interactive surface — there's no human at the
438
+ // tmux pane to press a number — so every first-turn hangs until the
439
+ // 30-min turn timeout fires. Reproduced + fixed live via
440
+ // `scripts/spikes/channels-first-turn.mjs`: without bypassPermissions
441
+ // the spike times out at 60s with claude "Marinating" forever; with
442
+ // bypassPermissions it replies in ~5s.
443
+ //
444
+ // The bridge DOES relay `notifications/claude/channel/permission_request`
445
+ // (channels-bridge.mjs:258) for the EXPERIMENTAL channel-permission
446
+ // API, but claude TUI doesn't route ordinary MCP tool calls through
447
+ // that channel — it shows the regular TUI prompt. So the relay path
448
+ // isn't reachable from a fresh-spawn channels turn.
406
449
  //
407
- // Trust + dev-channel confirmation dialogs are SEPARATE concerns and
408
- // still need _handleStartupDialogs polling either way skip-permissions
409
- // only affects per-tool prompts, not workspace-trust or dev-channels
410
- // confirmations.
450
+ // Config can still override (e.g., chats that genuinely want a
451
+ // different mode set `permissionMode` in chat/topic config); the
452
+ // default ensures bots actually reply out of the box.
411
453
  const permissionMode = topicConfig?.permissionMode
412
454
  || opts.chatConfig?.permissionMode
413
- || opts.permissionMode;
414
- if (permissionMode) claudeArgs.push('--permission-mode', permissionMode);
455
+ || opts.permissionMode
456
+ || 'bypassPermissions';
457
+ claudeArgs.push('--permission-mode', permissionMode);
415
458
  if (permissionMode === 'bypassPermissions') {
416
459
  claudeArgs.push('--dangerously-skip-permissions');
417
460
  }
@@ -466,8 +509,8 @@ class ChannelsProcess extends Process {
466
509
  // --mcp-config MUST be last (variadic flag)
467
510
  claudeArgs.push('--mcp-config', this.mcpConfigPath); // P0 #1: file path, not inline JSON
468
511
 
469
- const cwd = topicConfig?.cwd || opts.chatConfig?.cwd || opts.cwd;
470
- if (cwd) claudeArgs.unshift('--add-dir', cwd);
512
+ // resolvedCwd was computed above (line ~375) for the --resume file-check.
513
+ if (resolvedCwd) claudeArgs.unshift('--add-dir', resolvedCwd);
471
514
 
472
515
  // rc.5 (2026-05-25 shumorobot diagnosis): the launch cwd MUST be the
473
516
  // resolved topic/chat cwd, not just opts.cwd. claude's TUI indexes
@@ -486,7 +529,7 @@ class ChannelsProcess extends Process {
486
529
  // Real tmuxRunner.spawn signature: {name, cwd, command, args, envExtras, paneWidth}
487
530
  await this.runner.spawn({
488
531
  name: tmuxName,
489
- cwd: cwd || opts.cwd || process.cwd(),
532
+ cwd: resolvedCwd || opts.cwd || process.cwd(),
490
533
  command: this.claudeBin,
491
534
  args: claudeArgs,
492
535
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.11.0-rc.7",
3
+ "version": "0.11.0-rc.9",
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": {