polygram 0.38.0 → 0.38.2
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/codex/fault-provenance.js +75 -0
- package/lib/codex/runtime-controller.js +4 -0
- package/lib/db/codex-failed-generation.js +8 -1
- package/lib/db/events-retention.js +1 -0
- package/lib/db.js +49 -0
- package/lib/handlers/dispatcher.js +13 -0
- package/lib/handlers/poll.js +138 -59
- package/lib/handlers/questions.js +26 -1
- package/lib/ipc/handlers.js +29 -0
- package/lib/ops/clean-restart-qualification.js +261 -0
- package/lib/ops/clean-resume.js +12 -1
- package/lib/ops/clean-shutdown.js +105 -6
- package/lib/ops/daemon-identity.js +42 -0
- package/lib/ops/deploy-restart-handler.js +111 -0
- package/lib/ops/foreground-canary-target.js +454 -0
- package/lib/ops/systemd-invocation.js +10 -3
- package/package.json +2 -2
- package/polygram.js +117 -47
- package/scripts/ipc-smoke.js +38 -3
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createHash,
|
|
5
|
+
createHmac,
|
|
6
|
+
timingSafeEqual,
|
|
7
|
+
} = require('node:crypto');
|
|
8
|
+
const {
|
|
9
|
+
getSessionKey,
|
|
10
|
+
getTopicName,
|
|
11
|
+
} = require('../session-key');
|
|
12
|
+
|
|
13
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
14
|
+
const TOKEN_RE = /^[a-f0-9]{64}$/;
|
|
15
|
+
const UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
16
|
+
const PROVIDERS = new Set(['claude', 'codex']);
|
|
17
|
+
const LIVE_HANDLER_STATUSES = new Set(['dispatched', 'processing']);
|
|
18
|
+
const DEPLOY_REQUEST_KEYS = new Set([
|
|
19
|
+
'op',
|
|
20
|
+
'id',
|
|
21
|
+
'secret',
|
|
22
|
+
'foreground_expectation',
|
|
23
|
+
'qualification_expectation',
|
|
24
|
+
]);
|
|
25
|
+
const EXPECTATION_KEYS = [
|
|
26
|
+
'schema_version',
|
|
27
|
+
'daemon_instance_id',
|
|
28
|
+
'pid',
|
|
29
|
+
'provider',
|
|
30
|
+
'configured_scope_sha256',
|
|
31
|
+
'target_token',
|
|
32
|
+
];
|
|
33
|
+
const PROBE_KEYS = [
|
|
34
|
+
'op',
|
|
35
|
+
'id',
|
|
36
|
+
'secret',
|
|
37
|
+
'provider',
|
|
38
|
+
'configured_scope_sha256',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function isPlainObject(value) {
|
|
42
|
+
return value != null
|
|
43
|
+
&& typeof value === 'object'
|
|
44
|
+
&& !Array.isArray(value)
|
|
45
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasExactKeys(value, keys) {
|
|
49
|
+
if (!isPlainObject(value)) return false;
|
|
50
|
+
const actual = Object.keys(value);
|
|
51
|
+
return actual.length === keys.length
|
|
52
|
+
&& keys.every((key) => Object.hasOwn(value, key));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isBoundedString(value, maxBytes = 256) {
|
|
56
|
+
return typeof value === 'string'
|
|
57
|
+
&& value.length > 0
|
|
58
|
+
&& Buffer.byteLength(value, 'utf8') <= maxBytes
|
|
59
|
+
&& !/[\x00-\x1f\x7f]/.test(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function validRequestId(value) {
|
|
63
|
+
return UUID_RE.test(value ?? '');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function configuredScopeSha256({
|
|
67
|
+
botName,
|
|
68
|
+
chatId,
|
|
69
|
+
threadId,
|
|
70
|
+
chatName,
|
|
71
|
+
topicName,
|
|
72
|
+
isolateTopics,
|
|
73
|
+
} = {}) {
|
|
74
|
+
return createHash('sha256').update(JSON.stringify([
|
|
75
|
+
String(botName),
|
|
76
|
+
String(chatId),
|
|
77
|
+
threadId == null ? null : String(threadId),
|
|
78
|
+
String(chatName),
|
|
79
|
+
topicName == null ? null : String(topicName),
|
|
80
|
+
isolateTopics === true,
|
|
81
|
+
])).digest('hex');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function configuredScopes({ config, botName } = {}) {
|
|
85
|
+
const scopes = [];
|
|
86
|
+
for (const [chatId, chatConfig] of Object.entries(config?.chats ?? {})) {
|
|
87
|
+
if (!chatConfig || typeof chatConfig !== 'object') continue;
|
|
88
|
+
const base = {
|
|
89
|
+
botName,
|
|
90
|
+
chatId: String(chatId),
|
|
91
|
+
threadId: null,
|
|
92
|
+
chatName: String(chatConfig.name ?? ''),
|
|
93
|
+
topicName: null,
|
|
94
|
+
isolateTopics: chatConfig.isolateTopics === true,
|
|
95
|
+
sessionKey: getSessionKey(String(chatId), null, chatConfig),
|
|
96
|
+
};
|
|
97
|
+
scopes.push(Object.freeze({
|
|
98
|
+
...base,
|
|
99
|
+
digest: configuredScopeSha256(base),
|
|
100
|
+
}));
|
|
101
|
+
for (const threadId of Object.keys(chatConfig.topics ?? {})) {
|
|
102
|
+
const topic = {
|
|
103
|
+
...base,
|
|
104
|
+
threadId: String(threadId),
|
|
105
|
+
topicName: getTopicName(chatConfig, String(threadId)),
|
|
106
|
+
sessionKey: getSessionKey(String(chatId), String(threadId), chatConfig),
|
|
107
|
+
};
|
|
108
|
+
scopes.push(Object.freeze({
|
|
109
|
+
...topic,
|
|
110
|
+
digest: configuredScopeSha256(topic),
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return scopes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveConfiguredScope({ config, botName, digest } = {}) {
|
|
118
|
+
if (!SHA256_RE.test(digest ?? '')) return null;
|
|
119
|
+
const matches = configuredScopes({ config, botName })
|
|
120
|
+
.filter((scope) => scope.digest === digest);
|
|
121
|
+
return matches.length === 1 ? matches[0] : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeDeployForegroundExpectation(request) {
|
|
125
|
+
if (!isPlainObject(request)) return null;
|
|
126
|
+
const keys = Object.keys(request);
|
|
127
|
+
const unknown = keys.some((key) => !DEPLOY_REQUEST_KEYS.has(key));
|
|
128
|
+
const present = Object.hasOwn(request, 'foreground_expectation');
|
|
129
|
+
if (!present && !unknown) return undefined;
|
|
130
|
+
if (
|
|
131
|
+
unknown
|
|
132
|
+
|| request.op !== 'deploy_restart'
|
|
133
|
+
|| !validRequestId(request.id)
|
|
134
|
+
|| !isBoundedString(request.secret, 1024)
|
|
135
|
+
) return null;
|
|
136
|
+
|
|
137
|
+
const expectation = request.foreground_expectation;
|
|
138
|
+
if (
|
|
139
|
+
!hasExactKeys(expectation, EXPECTATION_KEYS)
|
|
140
|
+
|| expectation.schema_version !== 1
|
|
141
|
+
|| !UUID_RE.test(expectation.daemon_instance_id ?? '')
|
|
142
|
+
|| !Number.isSafeInteger(expectation.pid)
|
|
143
|
+
|| expectation.pid <= 0
|
|
144
|
+
|| !PROVIDERS.has(expectation.provider)
|
|
145
|
+
|| !SHA256_RE.test(expectation.configured_scope_sha256 ?? '')
|
|
146
|
+
|| !TOKEN_RE.test(expectation.target_token ?? '')
|
|
147
|
+
) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
daemonInstanceId: expectation.daemon_instance_id,
|
|
152
|
+
pid: expectation.pid,
|
|
153
|
+
provider: expectation.provider,
|
|
154
|
+
configuredScopeSha256: expectation.configured_scope_sha256,
|
|
155
|
+
targetToken: expectation.target_token,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizedActiveTarget(value) {
|
|
160
|
+
if (!isPlainObject(value)) return null;
|
|
161
|
+
const sessionKey = value.sessionKey;
|
|
162
|
+
const chatId = value.chatId;
|
|
163
|
+
const threadId = value.threadId;
|
|
164
|
+
const telegramMessageId = value.telegramMessageId;
|
|
165
|
+
if (
|
|
166
|
+
!isBoundedString(sessionKey, 512)
|
|
167
|
+
|| !isBoundedString(chatId, 128)
|
|
168
|
+
|| !(threadId === null || isBoundedString(threadId, 128))
|
|
169
|
+
|| !isBoundedString(telegramMessageId, 128)
|
|
170
|
+
) return null;
|
|
171
|
+
return {
|
|
172
|
+
sessionKey,
|
|
173
|
+
chatId,
|
|
174
|
+
threadId,
|
|
175
|
+
telegramMessageId,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizedSelectedTarget(value) {
|
|
180
|
+
if (!isPlainObject(value)) return null;
|
|
181
|
+
if (
|
|
182
|
+
!Number.isSafeInteger(value.messageId)
|
|
183
|
+
|| value.messageId <= 0
|
|
184
|
+
|| !isBoundedString(value.chatId, 128)
|
|
185
|
+
|| !(value.threadId === null || isBoundedString(value.threadId, 128))
|
|
186
|
+
|| !isBoundedString(value.telegramMessageId, 128)
|
|
187
|
+
|| !isBoundedString(value.sessionKey, 512)
|
|
188
|
+
|| !PROVIDERS.has(value.provider)
|
|
189
|
+
|| !isBoundedString(value.handlerStatus, 64)
|
|
190
|
+
) return null;
|
|
191
|
+
return {
|
|
192
|
+
messageId: value.messageId,
|
|
193
|
+
chatId: value.chatId,
|
|
194
|
+
threadId: value.threadId,
|
|
195
|
+
telegramMessageId: value.telegramMessageId,
|
|
196
|
+
sessionKey: value.sessionKey,
|
|
197
|
+
provider: value.provider,
|
|
198
|
+
handlerStatus: value.handlerStatus,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function sameTarget(left, right) {
|
|
203
|
+
return left.sessionKey === right.sessionKey
|
|
204
|
+
&& left.chatId === right.chatId
|
|
205
|
+
&& left.threadId === right.threadId
|
|
206
|
+
&& left.telegramMessageId === right.telegramMessageId;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function sameBinding(left, right) {
|
|
210
|
+
return sameTarget(left, right)
|
|
211
|
+
&& left.messageId === right.messageId
|
|
212
|
+
&& left.provider === right.provider
|
|
213
|
+
&& left.scopeDigest === right.scopeDigest;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function safeTokenEqual(left, right) {
|
|
217
|
+
if (!TOKEN_RE.test(left ?? '') || !TOKEN_RE.test(right ?? '')) return false;
|
|
218
|
+
return timingSafeEqual(Buffer.from(left), Buffer.from(right));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function createForegroundCanaryAuthorizer({
|
|
222
|
+
botName,
|
|
223
|
+
daemonIdentity,
|
|
224
|
+
config,
|
|
225
|
+
tokenSecret,
|
|
226
|
+
getActiveHandlerCount,
|
|
227
|
+
getActiveHandlerTargets,
|
|
228
|
+
getForegroundCanaryTarget,
|
|
229
|
+
now = Date.now,
|
|
230
|
+
tokenTtlMs = 300_000,
|
|
231
|
+
tokenLimit = 64,
|
|
232
|
+
} = {}) {
|
|
233
|
+
if (!isBoundedString(botName, 128)) {
|
|
234
|
+
throw new TypeError('foreground canary bot name is required');
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
!isPlainObject(daemonIdentity)
|
|
238
|
+
|| !UUID_RE.test(daemonIdentity.daemon_instance_id ?? '')
|
|
239
|
+
|| !Number.isSafeInteger(daemonIdentity.pid)
|
|
240
|
+
|| daemonIdentity.pid <= 0
|
|
241
|
+
|| !isBoundedString(daemonIdentity.package_version, 128)
|
|
242
|
+
) {
|
|
243
|
+
throw new TypeError('foreground canary daemon identity is invalid');
|
|
244
|
+
}
|
|
245
|
+
if (!isBoundedString(tokenSecret, 1024)) {
|
|
246
|
+
throw new TypeError('foreground canary token secret is required');
|
|
247
|
+
}
|
|
248
|
+
if (typeof getActiveHandlerCount !== 'function') {
|
|
249
|
+
throw new TypeError('foreground canary active-handler count reader is required');
|
|
250
|
+
}
|
|
251
|
+
if (typeof getActiveHandlerTargets !== 'function') {
|
|
252
|
+
throw new TypeError('foreground canary active-handler reader is required');
|
|
253
|
+
}
|
|
254
|
+
if (typeof getForegroundCanaryTarget !== 'function') {
|
|
255
|
+
throw new TypeError('foreground canary database reader is required');
|
|
256
|
+
}
|
|
257
|
+
if (!Number.isSafeInteger(tokenTtlMs) || tokenTtlMs <= 0) {
|
|
258
|
+
throw new TypeError('foreground canary token TTL is invalid');
|
|
259
|
+
}
|
|
260
|
+
if (!Number.isSafeInteger(tokenLimit) || tokenLimit <= 0) {
|
|
261
|
+
throw new TypeError('foreground canary token limit is invalid');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const tokens = new Map();
|
|
265
|
+
|
|
266
|
+
function reject(rejectionCode) {
|
|
267
|
+
return { rejectionCode };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function inspectLiveTarget({ provider, scopeDigest }) {
|
|
271
|
+
const scope = resolveConfiguredScope({ config, botName, digest: scopeDigest });
|
|
272
|
+
if (!scope) return reject('configured-scope-mismatch');
|
|
273
|
+
|
|
274
|
+
let activeCount;
|
|
275
|
+
let active;
|
|
276
|
+
try {
|
|
277
|
+
activeCount = getActiveHandlerCount();
|
|
278
|
+
active = getActiveHandlerTargets();
|
|
279
|
+
} catch {
|
|
280
|
+
return reject('target-unavailable');
|
|
281
|
+
}
|
|
282
|
+
if (activeCount !== 1 || !Array.isArray(active) || active.length !== 1) {
|
|
283
|
+
return reject('daemon-busy');
|
|
284
|
+
}
|
|
285
|
+
const activeTarget = normalizedActiveTarget(active[0]);
|
|
286
|
+
if (!activeTarget) return reject('source-mismatch');
|
|
287
|
+
if (
|
|
288
|
+
activeTarget.chatId !== scope.chatId
|
|
289
|
+
|| activeTarget.threadId !== scope.threadId
|
|
290
|
+
|| activeTarget.sessionKey !== scope.sessionKey
|
|
291
|
+
) {
|
|
292
|
+
return reject('source-mismatch');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let selected;
|
|
296
|
+
try {
|
|
297
|
+
selected = normalizedSelectedTarget(getForegroundCanaryTarget({
|
|
298
|
+
botName,
|
|
299
|
+
chatId: activeTarget.chatId,
|
|
300
|
+
telegramMessageId: activeTarget.telegramMessageId,
|
|
301
|
+
}));
|
|
302
|
+
} catch {
|
|
303
|
+
return reject('target-unavailable');
|
|
304
|
+
}
|
|
305
|
+
if (!selected) return reject('target-not-live');
|
|
306
|
+
if (!sameTarget(activeTarget, selected)) return reject('source-mismatch');
|
|
307
|
+
if (selected.provider !== provider) return reject('provider-mismatch');
|
|
308
|
+
if (!LIVE_HANDLER_STATUSES.has(selected.handlerStatus)) {
|
|
309
|
+
return reject('target-not-live');
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
binding: Object.freeze({
|
|
313
|
+
...selected,
|
|
314
|
+
scopeDigest,
|
|
315
|
+
}),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function probe(request) {
|
|
320
|
+
if (
|
|
321
|
+
!hasExactKeys(request, PROBE_KEYS)
|
|
322
|
+
|| request.op !== 'foreground_canary_target'
|
|
323
|
+
|| !validRequestId(request.id)
|
|
324
|
+
|| !isBoundedString(request.secret, 1024)
|
|
325
|
+
|| !PROVIDERS.has(request.provider)
|
|
326
|
+
|| !SHA256_RE.test(request.configured_scope_sha256 ?? '')
|
|
327
|
+
) {
|
|
328
|
+
return {
|
|
329
|
+
outcome: 'rejected',
|
|
330
|
+
rejection_code: 'invalid-request',
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const issuedAt = now();
|
|
335
|
+
for (const [requestId, stored] of tokens) {
|
|
336
|
+
if (issuedAt > stored.expiresAt) tokens.delete(requestId);
|
|
337
|
+
}
|
|
338
|
+
if (!tokens.has(request.id) && tokens.size >= tokenLimit) {
|
|
339
|
+
return {
|
|
340
|
+
outcome: 'rejected',
|
|
341
|
+
rejection_code: 'target-unavailable',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const inspected = inspectLiveTarget({
|
|
346
|
+
provider: request.provider,
|
|
347
|
+
scopeDigest: request.configured_scope_sha256,
|
|
348
|
+
});
|
|
349
|
+
if (!inspected.binding) {
|
|
350
|
+
return {
|
|
351
|
+
outcome: 'rejected',
|
|
352
|
+
rejection_code: inspected.rejectionCode,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const token = createHmac('sha256', tokenSecret).update(JSON.stringify([
|
|
357
|
+
request.id,
|
|
358
|
+
daemonIdentity.daemon_instance_id,
|
|
359
|
+
daemonIdentity.pid,
|
|
360
|
+
inspected.binding.provider,
|
|
361
|
+
inspected.binding.scopeDigest,
|
|
362
|
+
inspected.binding.sessionKey,
|
|
363
|
+
inspected.binding.messageId,
|
|
364
|
+
inspected.binding.telegramMessageId,
|
|
365
|
+
issuedAt,
|
|
366
|
+
])).digest('hex');
|
|
367
|
+
tokens.set(request.id, Object.freeze({
|
|
368
|
+
token,
|
|
369
|
+
binding: inspected.binding,
|
|
370
|
+
expiresAt: issuedAt + tokenTtlMs,
|
|
371
|
+
}));
|
|
372
|
+
return {
|
|
373
|
+
outcome: 'live',
|
|
374
|
+
bot: botName,
|
|
375
|
+
daemon_instance_id: daemonIdentity.daemon_instance_id,
|
|
376
|
+
pid: daemonIdentity.pid,
|
|
377
|
+
package_version: daemonIdentity.package_version,
|
|
378
|
+
provider: request.provider,
|
|
379
|
+
configured_scope_sha256: request.configured_scope_sha256,
|
|
380
|
+
target_token: token,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function authorizeRestart({ requestId, expectation } = {}) {
|
|
385
|
+
if (!validRequestId(requestId) || !isPlainObject(expectation)) {
|
|
386
|
+
return { accepted: false, rejectionCode: 'invalid-request' };
|
|
387
|
+
}
|
|
388
|
+
if (
|
|
389
|
+
expectation.daemonInstanceId !== daemonIdentity.daemon_instance_id
|
|
390
|
+
|| expectation.pid !== daemonIdentity.pid
|
|
391
|
+
) {
|
|
392
|
+
return { accepted: false, rejectionCode: 'daemon-identity-mismatch' };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const stored = tokens.get(requestId);
|
|
396
|
+
if (!stored || !safeTokenEqual(stored.token, expectation.targetToken)) {
|
|
397
|
+
return { accepted: false, rejectionCode: 'target-token-invalid' };
|
|
398
|
+
}
|
|
399
|
+
if (now() > stored.expiresAt) {
|
|
400
|
+
tokens.delete(requestId);
|
|
401
|
+
return { accepted: false, rejectionCode: 'target-token-expired' };
|
|
402
|
+
}
|
|
403
|
+
if (
|
|
404
|
+
expectation.provider !== stored.binding.provider
|
|
405
|
+
|| expectation.configuredScopeSha256 !== stored.binding.scopeDigest
|
|
406
|
+
) {
|
|
407
|
+
tokens.delete(requestId);
|
|
408
|
+
return { accepted: false, rejectionCode: 'target-binding-mismatch' };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const inspected = inspectLiveTarget({
|
|
412
|
+
provider: expectation.provider,
|
|
413
|
+
scopeDigest: expectation.configuredScopeSha256,
|
|
414
|
+
});
|
|
415
|
+
if (!inspected.binding) {
|
|
416
|
+
tokens.delete(requestId);
|
|
417
|
+
return { accepted: false, rejectionCode: inspected.rejectionCode };
|
|
418
|
+
}
|
|
419
|
+
if (!sameBinding(stored.binding, inspected.binding)) {
|
|
420
|
+
tokens.delete(requestId);
|
|
421
|
+
return { accepted: false, rejectionCode: 'source-mismatch' };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
tokens.delete(requestId);
|
|
425
|
+
return {
|
|
426
|
+
accepted: true,
|
|
427
|
+
authorizationEvent: Object.freeze({
|
|
428
|
+
bot: botName,
|
|
429
|
+
daemon_instance_id: daemonIdentity.daemon_instance_id,
|
|
430
|
+
pid: daemonIdentity.pid,
|
|
431
|
+
package_version: daemonIdentity.package_version,
|
|
432
|
+
provider: stored.binding.provider,
|
|
433
|
+
configured_scope_sha256: stored.binding.scopeDigest,
|
|
434
|
+
session_key: stored.binding.sessionKey,
|
|
435
|
+
source_message_id: stored.binding.messageId,
|
|
436
|
+
restart_request_sha256: createHash('sha256')
|
|
437
|
+
.update(requestId)
|
|
438
|
+
.digest('hex'),
|
|
439
|
+
}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return Object.freeze({
|
|
444
|
+
probe,
|
|
445
|
+
authorizeRestart,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
module.exports = {
|
|
450
|
+
configuredScopeSha256,
|
|
451
|
+
createForegroundCanaryAuthorizer,
|
|
452
|
+
normalizeDeployForegroundExpectation,
|
|
453
|
+
resolveConfiguredScope,
|
|
454
|
+
};
|
|
@@ -9,11 +9,18 @@ function parseSystemdInvocationId(env = process.env) {
|
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function lifecycleDetail(detail, invocationId) {
|
|
12
|
+
function lifecycleDetail(detail, invocationId, daemonIdentity = null) {
|
|
13
13
|
const value = detail && typeof detail === 'object' ? detail : {};
|
|
14
|
-
|
|
14
|
+
const withDaemonIdentity = daemonIdentity == null
|
|
15
15
|
? value
|
|
16
|
-
: {
|
|
16
|
+
: {
|
|
17
|
+
...value,
|
|
18
|
+
daemon_instance_id: daemonIdentity.daemon_instance_id,
|
|
19
|
+
pid: daemonIdentity.pid,
|
|
20
|
+
};
|
|
21
|
+
return invocationId == null
|
|
22
|
+
? withDaemonIdentity
|
|
23
|
+
: { ...withDaemonIdentity, invocation_id: invocationId };
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.2",
|
|
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": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@anthropic-ai/claude-agent-sdk": "0.2.123",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
|
-
"@shumkov/orchestra": "0.10.
|
|
66
|
+
"@shumkov/orchestra": "0.10.16",
|
|
67
67
|
"better-sqlite3": "^12.9.0",
|
|
68
68
|
"grammy": "^1.42.0",
|
|
69
69
|
"markdown-it": "^14.1.1",
|