polygram 0.17.10 → 0.17.12

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.
Files changed (39) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/lib/config.js +21 -0
  3. package/lib/error/classify.js +14 -0
  4. package/lib/handlers/dispatcher.js +54 -1
  5. package/lib/handlers/drop-redeliver.js +19 -0
  6. package/lib/handlers/should-handle.js +52 -0
  7. package/lib/media-group-buffer.js +29 -0
  8. package/lib/ops/auth-disabled-gate.js +43 -0
  9. package/lib/ops/heartbeat.js +56 -0
  10. package/lib/process/channels-tool-dispatcher.js +12 -1
  11. package/lib/prompt.js +22 -7
  12. package/lib/sdk/callbacks.js +2 -1
  13. package/lib/secret-detect.js +13 -1
  14. package/lib/telegram/chunk.js +20 -6
  15. package/lib/telegram/process-agent-reply.js +5 -3
  16. package/lib/telegram/streamer.js +6 -1
  17. package/package.json +2 -1
  18. package/polygram.js +202 -45
  19. package/lib/async-lock.js +0 -49
  20. package/lib/claude-bin.js +0 -246
  21. package/lib/compaction-warn.js +0 -59
  22. package/lib/context-usage.js +0 -93
  23. package/lib/process/channels-bridge-protocol.js +0 -199
  24. package/lib/process/channels-bridge-server.js +0 -274
  25. package/lib/process/channels-bridge.mjs +0 -477
  26. package/lib/process/cli-process.js +0 -4029
  27. package/lib/process/factory.js +0 -215
  28. package/lib/process/hook-event-tail.js +0 -162
  29. package/lib/process/hook-settings.js +0 -181
  30. package/lib/process/polygram-hook-append.js +0 -71
  31. package/lib/process/process.js +0 -215
  32. package/lib/process/sdk-process.js +0 -880
  33. package/lib/process-guard.js +0 -296
  34. package/lib/process-manager.js +0 -628
  35. package/lib/tmux/log-tail.js +0 -334
  36. package/lib/tmux/orphan-sweep.js +0 -79
  37. package/lib/tmux/poll-scheduler.js +0 -110
  38. package/lib/tmux/startup-gate.js +0 -250
  39. package/lib/tmux/tmux-runner.js +0 -412
@@ -1,215 +0,0 @@
1
- /**
2
- * Abstract Process — one running Claude session, regardless of backend.
3
- *
4
- * Subclasses ship per backend:
5
- * - SdkProcess (lib/process/sdk-process.js) — long-lived
6
- * @anthropic-ai/claude-agent-sdk Query
7
- * - TmuxProcess (lib/process/tmux-process.js) — claude TUI hosted
8
- * inside a tmux session (Phase 2)
9
- *
10
- * State machine: spawned → ready → (turn-in-flight | idle) ↔ closed.
11
- *
12
- * Public surface mirrors what polygram's handleMessage, slash-commands,
13
- * autosteer, edit-correction etc. already call on the current SDK pm.
14
- * Callers don't branch on subclass.
15
- *
16
- * Optional methods come in two flavors per the v3 audit:
17
- * - Async ones MAY throw UnsupportedOperationError. Callers `await` +
18
- * try/catch around them.
19
- * - Sync HOT-PATH ones (drainQueue, injectUserMessage) return a
20
- * sentinel value, NEVER throw. Per R1-F1: autosteer's call site
21
- * has no try/catch — a throw would crash the message handler.
22
- *
23
- * Weighted LRU: each Process advertises its `cost`. The pm evicts
24
- * to keep Σ cost ≤ budget rather than count ≤ cap. SDK Process cost=1,
25
- * TmuxProcess cost=3 (per Phase 0 F-spike-2: tmux ~545MB vs SDK ~50MB
26
- * per session).
27
- *
28
- * Phase 0 spike findings — `docs/0.10.0-phase0-spike-findings.md`.
29
- * Spec — `docs/0.10.0-process-manager-abstraction-plan.md`.
30
- */
31
-
32
- 'use strict';
33
-
34
- const EventEmitter = require('events');
35
-
36
- class UnsupportedOperationError extends Error {
37
- constructor(method, backend) {
38
- super(`Operation ${method} not supported by ${backend} backend`);
39
- this.name = 'UnsupportedOperationError';
40
- this.code = 'UNSUPPORTED_OPERATION';
41
- this.method = method;
42
- this.backend = backend;
43
- }
44
- }
45
-
46
- class Process extends EventEmitter {
47
- /**
48
- * @param {object} opts
49
- * @param {string} opts.sessionKey polygram session key
50
- * @param {string|null} opts.chatId
51
- * @param {string|null} opts.threadId
52
- * @param {string} opts.label human-readable for logs
53
- */
54
- constructor({ sessionKey, chatId, threadId, label } = {}) {
55
- super();
56
- if (typeof sessionKey !== 'string' || sessionKey.length === 0) {
57
- throw new TypeError('Process: sessionKey (string) required');
58
- }
59
- // Identity — immutable after construction
60
- this.sessionKey = sessionKey;
61
- this.chatId = chatId == null ? null : String(chatId);
62
- this.threadId = threadId == null ? null : String(threadId);
63
- this.label = label || `${this.chatId || ''}${this.threadId ? '/' + this.threadId : ''}` || sessionKey;
64
- // backend identifier — subclass overrides
65
- this.backend = 'abstract';
66
-
67
- // Mutable state
68
- this.closed = false;
69
- this.inFlight = false;
70
- this.pendingQueue = [];
71
- this.claudeSessionId = null;
72
- }
73
-
74
- /**
75
- * Cost weight for LRU eviction (per Phase 0 F-spike-2).
76
- * Subclass overrides. Defaults to 1 (SDK-equivalent).
77
- */
78
- get cost() {
79
- return 1;
80
- }
81
-
82
- // ─── REQUIRED methods — subclass MUST override ─────────────────────
83
-
84
- /**
85
- * Cold-spawn this process. Wire up internals; mark ready when
86
- * the underlying claude session is responsive.
87
- *
88
- * @param {object} opts — backend-specific. Typically includes:
89
- * existingSessionId — for --resume continuity
90
- * model, effort, cwd, chatConfig, botName — spawn params
91
- */
92
- async start(_opts) {
93
- throw new Error(`${this.constructor.name}.start() must be overridden`);
94
- }
95
-
96
- /**
97
- * Send a user turn. Resolves with a PmSendResult on completion.
98
- *
99
- * @param {string} prompt
100
- * @param {object} [opts]
101
- * @returns {Promise<PmSendResult>}
102
- */
103
- async send(_prompt, _opts) {
104
- throw new Error(`${this.constructor.name}.send() must be overridden`);
105
- }
106
-
107
- /**
108
- * Close cleanly. Returns when fully torn down.
109
- * Idempotent.
110
- *
111
- * @param {string} [reason]
112
- */
113
- async kill(_reason) {
114
- throw new Error(`${this.constructor.name}.kill() must be overridden`);
115
- }
116
-
117
- // ─── OPTIONAL async methods — caller awaits + try/catch ────────────
118
-
119
- async interrupt() {
120
- throw new UnsupportedOperationError('interrupt', this.backend);
121
- }
122
- async setModel(_model) {
123
- throw new UnsupportedOperationError('setModel', this.backend);
124
- }
125
- async applyFlagSettings(_settings) {
126
- throw new UnsupportedOperationError('applyFlagSettings', this.backend);
127
- }
128
- async setPermissionMode(_mode) {
129
- throw new UnsupportedOperationError('setPermissionMode', this.backend);
130
- }
131
- async resetSession(_opts) {
132
- throw new UnsupportedOperationError('resetSession', this.backend);
133
- }
134
- async getContextUsage() {
135
- throw new UnsupportedOperationError('getContextUsage', this.backend);
136
- }
137
-
138
- // ─── OPTIONAL sync HOT-PATH methods — never throw (R1-F1) ──────────
139
-
140
- /**
141
- * Reject all pending turns with the supplied error code.
142
- * Used by /stop, daemon shutdown, /new.
143
- *
144
- * @param {string} [_code='INTERRUPTED']
145
- * @returns {number} count of pendings drained
146
- */
147
- drainQueue(_code = 'INTERRUPTED') {
148
- return 0;
149
- }
150
-
151
- /**
152
- * Inject a user message into the in-flight turn (autosteer +
153
- * edit-correction). Returns false if the backend can't inject
154
- * right now (e.g. no live turn) — caller falls through to normal
155
- * pm.send queue path.
156
- *
157
- * @returns {boolean}
158
- */
159
- injectUserMessage(_opts) {
160
- return false;
161
- }
162
-
163
- /**
164
- * Does this session have a DETACHED background job running (a `run_in_background`
165
- * shell that outlives the dispatch turn)? Used by ProcessManager._evictLRU to PIN
166
- * the session — skip it for eviction the same way `inFlight` is skipped — so a live
167
- * job isn't silently killed under budget pressure. Default: no signal → false.
168
- * Backends that can detect detached shells (cli) override this. Must be cheap + sync.
169
- *
170
- * @returns {boolean}
171
- */
172
- hasActiveBackgroundWork() {
173
- return false;
174
- }
175
-
176
- /**
177
- * 0.13 D1 (S9): does this process have an open interactive question?
178
- * Backends without the ask feature return false; CliProcess overrides.
179
- * ProcessManager._evictLRU treats true like inFlight (eviction pin).
180
- */
181
- hasOpenQuestions() {
182
- return false;
183
- }
184
-
185
- /**
186
- * Push priority='now' style steer (rare; legacy of OpenClaw shape).
187
- * Hot-path-safe.
188
- *
189
- * @returns {boolean}
190
- */
191
- steer(_text, _opts) {
192
- return false;
193
- }
194
-
195
- /**
196
- * Fire-and-forget user-message injection regardless of inFlight
197
- * state. Used by polygram's slash-command paths (/compact, /reload
198
- * etc) where we want to send a user-shaped message into the
199
- * underlying claude session BUT NOT wait for the turn to complete.
200
- *
201
- * Differs from `injectUserMessage` (which is for mid-stream fold and
202
- * requires inFlight on tmux) and `send` (which blocks until turn
203
- * completion). Default returns false; subclasses override.
204
- *
205
- * @returns {boolean} true if message was queued/pasted
206
- */
207
- fireUserMessage(_text) {
208
- return false;
209
- }
210
- }
211
-
212
- module.exports = {
213
- Process,
214
- UnsupportedOperationError,
215
- };