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.
- package/.claude-plugin/plugin.json +1 -1
- package/lib/config.js +21 -0
- package/lib/error/classify.js +14 -0
- package/lib/handlers/dispatcher.js +54 -1
- package/lib/handlers/drop-redeliver.js +19 -0
- package/lib/handlers/should-handle.js +52 -0
- package/lib/media-group-buffer.js +29 -0
- package/lib/ops/auth-disabled-gate.js +43 -0
- package/lib/ops/heartbeat.js +56 -0
- package/lib/process/channels-tool-dispatcher.js +12 -1
- package/lib/prompt.js +22 -7
- package/lib/sdk/callbacks.js +2 -1
- package/lib/secret-detect.js +13 -1
- package/lib/telegram/chunk.js +20 -6
- package/lib/telegram/process-agent-reply.js +5 -3
- package/lib/telegram/streamer.js +6 -1
- package/package.json +2 -1
- package/polygram.js +202 -45
- package/lib/async-lock.js +0 -49
- package/lib/claude-bin.js +0 -246
- package/lib/compaction-warn.js +0 -59
- package/lib/context-usage.js +0 -93
- package/lib/process/channels-bridge-protocol.js +0 -199
- package/lib/process/channels-bridge-server.js +0 -274
- package/lib/process/channels-bridge.mjs +0 -477
- package/lib/process/cli-process.js +0 -4029
- package/lib/process/factory.js +0 -215
- package/lib/process/hook-event-tail.js +0 -162
- package/lib/process/hook-settings.js +0 -181
- package/lib/process/polygram-hook-append.js +0 -71
- package/lib/process/process.js +0 -215
- package/lib/process/sdk-process.js +0 -880
- package/lib/process-guard.js +0 -296
- package/lib/process-manager.js +0 -628
- package/lib/tmux/log-tail.js +0 -334
- package/lib/tmux/orphan-sweep.js +0 -79
- package/lib/tmux/poll-scheduler.js +0 -110
- package/lib/tmux/startup-gate.js +0 -250
- package/lib/tmux/tmux-runner.js +0 -412
package/lib/process/process.js
DELETED
|
@@ -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
|
-
};
|