polygram 0.10.0-rc.30 → 0.10.0-rc.32
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.
- package/.claude-plugin/plugin.json +1 -1
- package/lib/db/sessions.js +28 -11
- package/lib/process/tmux-process.js +51 -0
- package/package.json +1 -1
- package/polygram.js +11 -8
|
@@ -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.32",
|
|
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",
|
package/lib/db/sessions.js
CHANGED
|
@@ -98,16 +98,33 @@ function getClaudeSessionId(db, sessionKey) {
|
|
|
98
98
|
// ─── S2: session-config drift ────────────────────────────────────────
|
|
99
99
|
//
|
|
100
100
|
// A stored `sessions` row records the config the claude session was
|
|
101
|
-
// SPAWNED under
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
101
|
+
// SPAWNED under. Two of the recorded fields are spawn-identity:
|
|
102
|
+
// - agent — `--agent <name>` is baked into the spawned process;
|
|
103
|
+
// resuming a session spawned under agent X under agent Y forces
|
|
104
|
+
// claude to use Y's system prompt + tool whitelist against
|
|
105
|
+
// conversation history built under X's. Incoherent.
|
|
106
|
+
// - cwd — `--cwd <path>` (SDK) / tmux session cwd; claude resolves
|
|
107
|
+
// project-local config (.claude/settings.json, agent files,
|
|
108
|
+
// plugins) relative to it. Mid-conversation cwd drift means
|
|
109
|
+
// half the messages are answered with one project's allowlist
|
|
110
|
+
// and the other half with another's.
|
|
111
|
+
//
|
|
112
|
+
// pm_backend was REMOVED from spawn-identity (rc.32, 2026-05-21).
|
|
113
|
+
// Both backends spawn the same pinned claude binary and write the
|
|
114
|
+
// same on-disk JSONL (~/.claude/projects/<cwd-enc>/<sid>.jsonl) —
|
|
115
|
+
// claude itself doesn't know or care which Node-side wrapper invoked
|
|
116
|
+
// it. Treating a backend flip as drift was destructively dropping
|
|
117
|
+
// context across the SDK→tmux migration window, costing every chat
|
|
118
|
+
// its conversation history on its first turn under the new backend.
|
|
119
|
+
// shumorobot 2026-05-20 18:51 incident: the Music topic flipped
|
|
120
|
+
// tmux→sdk→tmux during runtime and lost its agent's prior context
|
|
121
|
+
// at each flip. The orphan-tmux problem that the flip ALSO triggered
|
|
122
|
+
// is solved by rc.31's spawn-time reconcile (TmuxProcess.start) —
|
|
123
|
+
// independently, so a backend flip is now a no-op for session-state.
|
|
124
|
+
//
|
|
125
|
+
// shumorobot 2026-05-17 22:03, topic :3 (the original drift incident)
|
|
126
|
+
// remains correctly handled: that row had agent+cwd drift in
|
|
127
|
+
// addition to backend, so the agent+cwd drift alone still drops it.
|
|
111
128
|
//
|
|
112
129
|
// model + effort are deliberately EXCLUDED from the invalidating set.
|
|
113
130
|
// They are NOT spawn-identity: a live `/model` or `/effort` change is
|
|
@@ -118,7 +135,7 @@ function getClaudeSessionId(db, sessionKey) {
|
|
|
118
135
|
// every model switch, double-handling what the live-apply path
|
|
119
136
|
// already covers cleanly. The stored model/effort columns are
|
|
120
137
|
// informational, not identity.
|
|
121
|
-
const SPAWN_IDENTITY_FIELDS = ['agent', 'cwd'
|
|
138
|
+
const SPAWN_IDENTITY_FIELDS = ['agent', 'cwd'];
|
|
122
139
|
|
|
123
140
|
/**
|
|
124
141
|
* Decide whether a stored session can be resumed for the next spawn,
|
|
@@ -496,6 +496,57 @@ class TmuxProcess extends Process {
|
|
|
496
496
|
throw Object.assign(new Error(binCheck.reason), { code: 'CLAUDE_BIN_MISSING' });
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
// Spawn-time reconcile (shumorobot 2026-05-20 18:51 incident).
|
|
500
|
+
// A tmux session with our name may already exist on the host
|
|
501
|
+
// BEFORE we spawn — sources:
|
|
502
|
+
// - pm_backend flip: chat config flipped tmux → sdk → tmux;
|
|
503
|
+
// polygram dropped its in-memory handle on the flip away
|
|
504
|
+
// from tmux but the previous daemon's tmux session is still
|
|
505
|
+
// running headless. (rc.32 makes the flip context-preserving
|
|
506
|
+
// on the session-id side; this reconcile keeps the
|
|
507
|
+
// tmux-pane side safe.)
|
|
508
|
+
// - Boot-sweep raced a concurrent operator (already noted in
|
|
509
|
+
// lib/tmux/orphan-sweep.js header).
|
|
510
|
+
// - Crash mid-spawn: the spawn() call landed but the sessionCreated
|
|
511
|
+
// teardown below was bypassed (SIGKILL).
|
|
512
|
+
// The orphan TUI inside is headless — its parent daemon (or any
|
|
513
|
+
// previous attach) is dead, its --debug-file isn't being tailed,
|
|
514
|
+
// and any JSONL it writes is unrouted. The live claude session
|
|
515
|
+
// ID inside is NOT recoverable from outside without probing the
|
|
516
|
+
// pane (flaky) — and we don't need to: claude's per-session JSONL
|
|
517
|
+
// lives in ~/.claude/projects/.../<sid>.jsonl independent of
|
|
518
|
+
// tmux, so a fresh --resume into a clean pane recovers the
|
|
519
|
+
// conversation cleanly.
|
|
520
|
+
//
|
|
521
|
+
// Strategy: detect → kill → re-check → spawn. Always-kill is
|
|
522
|
+
// safer than try-to-reuse — we have no living claim to the orphan.
|
|
523
|
+
// If kill fails (server gone, races a concurrent kill), the
|
|
524
|
+
// subsequent spawn will surface a clear TMUX_SPAWN_FAILED with
|
|
525
|
+
// the underlying tmux error for the operator.
|
|
526
|
+
if (typeof this.runner.sessionExists === 'function'
|
|
527
|
+
&& await this.runner.sessionExists(this.tmuxName)) {
|
|
528
|
+
this.logger.warn?.(
|
|
529
|
+
`[${this.label}] orphan tmux session ${this.tmuxName} present at spawn — killing`,
|
|
530
|
+
);
|
|
531
|
+
this.emit('spawn-reconcile', {
|
|
532
|
+
tmux_name: this.tmuxName,
|
|
533
|
+
phase: 'kill-orphan',
|
|
534
|
+
backend: 'tmux',
|
|
535
|
+
});
|
|
536
|
+
try {
|
|
537
|
+
await this.runner.killSession(this.tmuxName);
|
|
538
|
+
} catch (killErr) {
|
|
539
|
+
this.logger.warn?.(
|
|
540
|
+
`[${this.label}] spawn-reconcile killSession failed (continuing): ${killErr.message}`,
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
// Brief settle window so the tmux server releases the name
|
|
544
|
+
// before we re-spawn. tmux's kill-session is synchronous in
|
|
545
|
+
// the client but the server's session-name release can race
|
|
546
|
+
// a tight kill→new-session pair (observed on busy hosts).
|
|
547
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
548
|
+
}
|
|
549
|
+
|
|
499
550
|
// R2-F8: spawn errors must fail loud, not silent-catch.
|
|
500
551
|
await this.runner.spawn({
|
|
501
552
|
name: this.tmuxName,
|
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.32",
|
|
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": {
|
package/polygram.js
CHANGED
|
@@ -400,18 +400,21 @@ function buildSpawnContext(sessionKey) {
|
|
|
400
400
|
const threadId = sessionKey.includes(':') ? sessionKey.split(':')[1] : null;
|
|
401
401
|
|
|
402
402
|
// S2: a stored session is valid ONLY for the config it was spawned
|
|
403
|
-
// under. agent / cwd
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
403
|
+
// under. agent / cwd are spawn-identity — baked into the process at
|
|
404
|
+
// spawn time, never mutable on a live session. Resolve them the
|
|
405
|
+
// same way the backends do (topic override merged over chat-level)
|
|
406
|
+
// and compare to the stored `sessions` row. On drift,
|
|
407
|
+
// resolveSessionForSpawn drops the stale row and returns
|
|
408
408
|
// existingSessionId:null → the spawn starts fresh under the correct
|
|
409
409
|
// config instead of `--resume`-ing a stale one. This self-heals the
|
|
410
410
|
// pre-per-topic-config rows (e.g. shumorobot's Music topic :3,
|
|
411
|
-
// stored agent=shumabit / cwd=$HOME
|
|
412
|
-
// music-curation:music-curator / .../Music/rekordbox
|
|
411
|
+
// stored agent=shumabit / cwd=$HOME vs the current
|
|
412
|
+
// music-curation:music-curator / .../Music/rekordbox).
|
|
413
413
|
// model/effort are NOT compared — they apply live via setModel /
|
|
414
|
-
// applyFlagSettings with no respawn.
|
|
414
|
+
// applyFlagSettings with no respawn. pm_backend is also NOT
|
|
415
|
+
// compared (rc.32): both backends spawn the same pinned claude
|
|
416
|
+
// binary against the same on-disk JSONL, so a backend flip
|
|
417
|
+
// preserves context. See lib/db/sessions.js for full reasoning.
|
|
415
418
|
//
|
|
416
419
|
// The drift check runs only at COLD spawn (no warm process). A warm
|
|
417
420
|
// process already runs under its spawn-time config; getOrSpawn
|