sneakoscope 8.2.0 → 8.3.0

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 (101) hide show
  1. package/README.md +46 -110
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/command-manifest-lite.js +0 -2
  5. package/dist/cli/command-registry.js +0 -2
  6. package/dist/commands/codex.js +3 -3
  7. package/dist/commands/doctor.js +5 -157
  8. package/dist/config/skills-manifest.json +1 -1
  9. package/dist/core/codex/codex-cli-syntax-builder.js +0 -8
  10. package/dist/core/codex-app/codex-models-cache.js +2 -1
  11. package/dist/core/codex-app/menubar/constants.js +0 -7
  12. package/dist/core/codex-compat/codex-capability-matrix.js +7 -7
  13. package/dist/core/codex-compat/codex-compat-report.js +8 -12
  14. package/dist/core/codex-compat/codex-runtime-contract.js +62 -0
  15. package/dist/core/codex-compat/codex-version-policy.js +7 -7
  16. package/dist/core/codex-control/codex-current-app-capability.js +3 -3
  17. package/dist/core/codex-control/codex-current-capability.js +67 -94
  18. package/dist/core/codex-control/codex-current-core-capability.js +3 -3
  19. package/dist/core/codex-control/codex-current-core-probe-runner.js +4 -4
  20. package/dist/core/codex-control/codex-current-core-real-probes.js +2 -2
  21. package/dist/core/codex-control/codex-current-feature-capability.js +4 -4
  22. package/dist/core/codex-control/codex-current-image-path-real-probe.js +1 -1
  23. package/dist/core/codex-control/codex-current-web-search-probe.js +1 -1
  24. package/dist/core/codex-control/codex-sdk-capability.js +2 -2
  25. package/dist/core/commands/remote-command.js +2 -2
  26. package/dist/core/context7-client.js +5 -2
  27. package/dist/core/doctor/codex-current-core-probe-doctor.js +2 -2
  28. package/dist/core/doctor/doctor-dirty-planner.js +1 -1
  29. package/dist/core/doctor/doctor-readiness-matrix.js +0 -7
  30. package/dist/core/feature-fixtures.js +0 -4
  31. package/dist/core/feature-registry.js +0 -1
  32. package/dist/core/image-ux-review.js +3 -3
  33. package/dist/core/install/installed-package-smoke.js +2 -3
  34. package/dist/core/mad-sks/sql-plane/mcp-executor.js +4 -3
  35. package/dist/core/mcp/modern-protocol.js +36 -0
  36. package/dist/core/mcp-config/health-check.js +45 -30
  37. package/dist/core/perf/release-latency-slo.js +1 -2
  38. package/dist/core/proof/runtime-truth-matrix.js +1 -1
  39. package/dist/core/release/canonical-test-proof.js +1 -2
  40. package/dist/core/release/gate-manifest.js +5 -2
  41. package/dist/core/release/physical-release-gates.js +0 -26
  42. package/dist/core/release/release-gate-affected-selector.js +2 -1
  43. package/dist/core/release/release-gate-contract.js +3 -1
  44. package/dist/core/release/release-pack-receipt.js +0 -1
  45. package/dist/core/release/stage-publish.js +1 -1
  46. package/dist/core/version-manager.js +4 -0
  47. package/dist/core/version.js +1 -1
  48. package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -12
  49. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -6
  50. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +28 -471
  51. package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +1 -43
  52. package/dist/scripts/codex-current-app-server-v2-check.js +9 -8
  53. package/dist/scripts/codex-current-binary-identity-check.js +2 -2
  54. package/dist/scripts/codex-current-capability-check.js +9 -7
  55. package/dist/scripts/codex-current-dependency-check.js +21 -0
  56. package/dist/scripts/docs-truthfulness-check.js +4 -19
  57. package/dist/scripts/fast-codex-service-tier-proof-check.js +0 -1
  58. package/dist/scripts/install-surface-ssot-check.js +2 -2
  59. package/dist/scripts/latest-version-guidance-check.js +1 -1
  60. package/dist/scripts/packlist-performance-check.js +0 -1
  61. package/dist/scripts/release-metadata-check.js +4 -1
  62. package/dist/scripts/release-version-truth-check.js +149 -0
  63. package/dist/vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json +6 -0
  64. package/dist/vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json +2 -16
  65. package/dist/vendor/openai-codex/latest/hooks/post-compact.command.input.schema.json +6 -0
  66. package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.input.schema.json +6 -0
  67. package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.output.schema.json +2 -16
  68. package/dist/vendor/openai-codex/latest/hooks/pre-compact.command.input.schema.json +6 -0
  69. package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.input.schema.json +6 -0
  70. package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.output.schema.json +2 -16
  71. package/dist/vendor/openai-codex/latest/hooks/session-start.command.output.schema.json +2 -16
  72. package/dist/vendor/openai-codex/latest/hooks/snapshot-metadata.json +7 -5
  73. package/dist/vendor/openai-codex/latest/hooks/subagent-start.command.output.schema.json +4 -18
  74. package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.input.schema.json +6 -0
  75. package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.output.schema.json +2 -16
  76. package/package.json +4 -7
  77. package/release-gates.v2.json +49 -19
  78. package/config/codex-releases/rust-v0.146.0.json +0 -45
  79. package/dist/core/agents/agent-runner-codex-exec.js +0 -177
  80. package/dist/core/codex-compat/codex-release-manifest.js +0 -72
  81. package/dist/core/commands/telegram-command.js +0 -447
  82. package/dist/core/telegram/access.js +0 -58
  83. package/dist/core/telegram/client.js +0 -157
  84. package/dist/core/telegram/confirmation.js +0 -37
  85. package/dist/core/telegram/doctor.js +0 -154
  86. package/dist/core/telegram/index.js +0 -9
  87. package/dist/core/telegram/keychain.js +0 -317
  88. package/dist/core/telegram/liveness.js +0 -124
  89. package/dist/core/telegram/poller.js +0 -90
  90. package/dist/core/telegram/redaction.js +0 -15
  91. package/dist/core/telegram/state-lock.js +0 -416
  92. package/dist/core/telegram/types.js +0 -1
  93. package/dist/native/sks-menubar/Sources/TelegramPrivateFileStore.swift +0 -498
  94. package/dist/native/sks-menubar/Sources/TelegramPrivateFileSupport.swift +0 -236
  95. package/dist/native/sks-menubar/Sources/TelegramProcessGateway.swift +0 -454
  96. package/dist/native/sks-menubar/Sources/TelegramRuntimeSupport.swift +0 -56
  97. package/dist/native/sks-menubar/Sources/TelegramStateLock.swift +0 -448
  98. package/dist/native/sks-menubar/Sources/TelegramSupport.swift +0 -243
  99. package/dist/native/sks-menubar/Sources/TelegramTransport.swift +0 -499
  100. package/dist/scripts/codex-current-manifest-check.js +0 -29
  101. package/schemas/codex/app-server-0.146/codex_app_server_protocol.v2.schemas.json +0 -19858
@@ -1,447 +0,0 @@
1
- import { commandContract, outputCapFor, timeoutFor } from '../safety/command-contract/contracts.js';
2
- import { validateJsonSchema } from '../safety/command-contract/json-schema.js';
3
- import { probeTelegram, telegramSelfHealAction } from '../telegram/doctor.js';
4
- import { readTelegramLivenessReceipt, telegramLivenessPath } from '../telegram/liveness.js';
5
- import { TELEGRAM_TOKEN_ENV_NAMES, bindTelegramBotIdentity, issueTelegramPairingCode, preflightTelegramTokenStorage, storeTelegramToken } from '../telegram/keychain.js';
6
- import { spawn } from 'node:child_process';
7
- import fs from 'node:fs/promises';
8
- import path from 'node:path';
9
- import { TelegramApiError, TelegramClient } from '../telegram/client.js';
10
- import { redactTelegramError } from '../telegram/redaction.js';
11
- import { projectRoot } from '../fsx.js';
12
- import { projectRootRealpath } from '../session/project-namespace.js';
13
- export const TELEGRAM_COMMAND_WHITELIST = new Set([
14
- 'gates', 'paths', 'pipeline', 'proof', 'search', 'stats', 'status',
15
- 'stop-gate', 'trust', 'update-check', 'validate-artifacts'
16
- ]);
17
- export const TELEGRAM_CONFIRMATION_REQUIRED = new Set([
18
- 'gates', 'stop-gate', 'validate-artifacts'
19
- ]);
20
- export function createTelegramCommandDispatcher(adapter, lookup = commandContract) {
21
- const resolve = (request) => {
22
- if (!TELEGRAM_COMMAND_WHITELIST.has(request.name))
23
- return { error: 'telegram_command_not_whitelisted' };
24
- const contract = lookup(request.name);
25
- if (!contract || !contract.remote_allowed || contract.risk === 'R3')
26
- return { error: 'telegram_command_not_remote_allowed' };
27
- const validation = validateJsonSchema(request.input, contract.input_schema);
28
- if (!validation.ok)
29
- return { error: `telegram_command_input_invalid:${validation.issues[0]?.code ?? 'schema'}` };
30
- return { contract, argv: contract.argv_builder(validation.value) };
31
- };
32
- return {
33
- prepare(request) {
34
- const resolved = resolve(request);
35
- if ('error' in resolved)
36
- return { ok: false, error: resolved.error };
37
- if (requiresConfirmation(resolved.contract))
38
- return { ok: true, confirmation_required: true };
39
- return {
40
- ok: true,
41
- confirmation_required: false,
42
- execute: () => adapter.executeArgv(resolved.argv, resolved.contract)
43
- };
44
- },
45
- async executeConfirmed(request) {
46
- const resolved = resolve(request);
47
- if ('error' in resolved)
48
- throw new Error(resolved.error);
49
- if (!requiresConfirmation(resolved.contract))
50
- throw new Error('telegram_confirmation_not_required');
51
- return adapter.executeArgv(resolved.argv, resolved.contract);
52
- }
53
- };
54
- }
55
- function requiresConfirmation(contract) {
56
- return TELEGRAM_CONFIRMATION_REQUIRED.has(contract.name) || !contract.read_only || contract.risk !== 'R0';
57
- }
58
- export function usage() {
59
- return [
60
- 'Usage: sks telegram status|doctor|pair|setup [--json]',
61
- '',
62
- ' status Read the secret-free resident poller liveness receipt.',
63
- ' doctor Evaluate getMe, pairing, poller, and liveness evidence.',
64
- ' pair Issue a short-lived code for the intended private Telegram chat.',
65
- ' setup --token-stdin [--remove-webhook]',
66
- ' Verify and store the token for any @BotFather bot you own; an existing webhook requires explicit removal consent.',
67
- ' prepare --stdin-json Validate an authorized native request through the typed contract.',
68
- ' execute --stdin-json Execute a prepared request; destructive requests also need --confirmed.',
69
- '',
70
- 'After pairing, send /sks status {}. Confirm prompted actions with /confirm <nonce>.',
71
- 'The bot token is never accepted in argv, printed, or written to a config file.'
72
- ].join('\n');
73
- }
74
- export async function telegramCommand(args = [], dependencies = {}) {
75
- if (args.some((value) => ['help', '--help', '-h'].includes(value.toLowerCase()))) {
76
- console.log(usage());
77
- return { schema: 'sks.telegram-command.v1', ok: true, action: 'help' };
78
- }
79
- const action = args[0] && !args[0].startsWith('-') ? args[0].toLowerCase() : 'status';
80
- const json = args.includes('--json');
81
- const receiptPath = dependencies.receiptPath ?? telegramLivenessPath();
82
- try {
83
- if (action === 'status') {
84
- const state = await readTelegramLivenessReceipt(receiptPath);
85
- const result = state.ok
86
- ? { schema: 'sks.telegram-status.v1', ok: !state.stale && state.receipt.running, stale: state.stale, age_ms: state.age_ms, receipt: state.receipt }
87
- : { schema: 'sks.telegram-status.v1', ok: false, blocker: state.blocker };
88
- if (!result.ok)
89
- process.exitCode = 1;
90
- return emit(result, json);
91
- }
92
- if (action === 'doctor') {
93
- const probe = await probeTelegram({ receiptPath });
94
- const result = { ...probe, self_heal_action: telegramSelfHealAction(probe) };
95
- if (!result.ok)
96
- process.exitCode = 1;
97
- return emit(result, json);
98
- }
99
- if (action === 'pair') {
100
- const issued = await (dependencies.issuePairingCode ?? issueTelegramPairingCode)();
101
- return emit({
102
- schema: 'sks.telegram-pair-command.v1', ok: true,
103
- code: issued.code, expires_at: issued.expires_at,
104
- instruction: `Send /start ${issued.code} to the configured bot from the intended private chat. Then send /sks status {}.`,
105
- post_pair_command: '/sks status {}',
106
- confirmation_grammar: '/confirm <nonce>'
107
- }, json);
108
- }
109
- if (action === 'setup') {
110
- if (!args.includes('--token-stdin'))
111
- return telegramSetupFailure('telegram_token_stdin_required', json);
112
- if (args.includes('--drop-pending-updates'))
113
- return telegramSetupFailure('telegram_drop_pending_updates_unsupported', json);
114
- const environment = dependencies.environment ?? process.env;
115
- const envOverrideActive = args.includes('--operator-env-override-active')
116
- || TELEGRAM_TOKEN_ENV_NAMES.some((name) => String(environment[name] ?? '').trim().length > 0);
117
- if (envOverrideActive)
118
- return telegramSetupEnvOverrideBlocked(json);
119
- const token = await (dependencies.readTokenStdin ?? readBoundedTokenStdin)();
120
- let identity;
121
- try {
122
- identity = await (dependencies.verifyToken ?? verifyTelegramToken)(token);
123
- }
124
- catch (error) {
125
- return telegramSetupFailure(telegramIdentityFailure(error), json, 'getme');
126
- }
127
- const removeWebhook = args.includes('--remove-webhook');
128
- let webhook;
129
- try {
130
- webhook = await (dependencies.inspectWebhook ?? inspectTelegramWebhook)(token);
131
- }
132
- catch (error) {
133
- throw new Error(`telegram_webhook_inspection_failed:${redactTelegramError(error, token)}`);
134
- }
135
- const webhookConfigured = webhook.url.trim().length > 0;
136
- if (webhookConfigured && !removeWebhook) {
137
- throw new Error('telegram_webhook_configured_remove_consent_required');
138
- }
139
- await (dependencies.preflightTokenStorage ?? preflightTelegramTokenStorage)(token);
140
- let webhookRemoved = false;
141
- if (webhookConfigured) {
142
- try {
143
- await (dependencies.removeWebhook ?? removeTelegramWebhook)(token);
144
- webhookRemoved = true;
145
- }
146
- catch (error) {
147
- throw new Error(`telegram_webhook_remove_failed:${redactTelegramError(error, token)}`);
148
- }
149
- }
150
- let binding = null;
151
- if (identity) {
152
- try {
153
- binding = await (dependencies.bindBotIdentity ?? bindTelegramBotIdentity)(identity.id);
154
- }
155
- catch (error) {
156
- if (webhookRemoved) {
157
- return telegramSetupPartialFailure({
158
- error: 'telegram_bot_state_bind_failed_after_webhook_removed',
159
- detail: redactTelegramError(error, token),
160
- webhookRemoved,
161
- botId: identity.id
162
- }, json);
163
- }
164
- throw error;
165
- }
166
- }
167
- try {
168
- await (dependencies.storeToken ?? storeTelegramToken)(token);
169
- }
170
- catch (error) {
171
- if (webhookRemoved || binding) {
172
- return telegramSetupPartialFailure({
173
- error: webhookRemoved
174
- ? 'telegram_token_store_failed_after_webhook_removed'
175
- : 'telegram_token_store_failed_after_bot_state_binding',
176
- detail: redactTelegramError(error, token),
177
- webhookRemoved,
178
- botId: identity?.id ?? null,
179
- binding
180
- }, json);
181
- }
182
- throw error;
183
- }
184
- return emit({
185
- schema: 'sks.telegram-setup-command.v1', ok: true,
186
- getme_verified: true, token_stored: true, storage: 'user_secret_file',
187
- token_source: 'user_secret_file',
188
- webhook_configured_before: webhookConfigured,
189
- webhook_removed: webhookRemoved, pending_updates_dropped: false,
190
- bot_id: identity?.id ?? null,
191
- bot_username: publicBotUsername(identity?.username),
192
- bot_rotated: binding?.rotated ?? false,
193
- bot_state_reset: binding?.state_reset ?? false,
194
- restart_required: true
195
- }, json);
196
- }
197
- if (action === 'prepare' || action === 'execute') {
198
- if (!args.includes('--stdin-json'))
199
- return fail('telegram_command_stdin_json_required', json);
200
- const commandRoot = await resolveTelegramCommandRoot(args, dependencies.resolveProjectRoot);
201
- const request = await (dependencies.readCommandStdin ?? readBoundedCommandStdin)();
202
- const executionAdapter = dependencies.executionAdapter ?? { executeArgv: executeTypedArgv };
203
- const dispatcher = createTelegramCommandDispatcher({
204
- executeArgv: (argv, contract) => executionAdapter.executeArgv(argv, contract, commandRoot)
205
- });
206
- const prepared = dispatcher.prepare(request);
207
- if (action === 'prepare') {
208
- return emit(prepared.ok
209
- ? { schema: 'sks.telegram-command-preparation.v1', ok: true, allowed: true, confirmation_required: prepared.confirmation_required, public_error: null }
210
- : { schema: 'sks.telegram-command-preparation.v1', ok: false, allowed: false, confirmation_required: false, public_error: prepared.error }, json);
211
- }
212
- if (!prepared.ok)
213
- return fail(prepared.error, json);
214
- if (prepared.confirmation_required && !args.includes('--confirmed'))
215
- return fail('telegram_confirmation_required', json);
216
- const output = prepared.confirmation_required
217
- ? await dispatcher.executeConfirmed(request)
218
- : await prepared.execute();
219
- return emit({ schema: 'sks.telegram-command-execution.v1', ok: true, output }, json);
220
- }
221
- return fail('telegram_unknown_action', json, ['status', 'doctor', 'pair', 'setup', 'prepare', 'execute']);
222
- }
223
- catch (error) {
224
- const message = error instanceof Error ? error.message : String(error);
225
- return action === 'setup'
226
- ? telegramSetupFailure(redactTelegramError(error), json)
227
- : fail(message, json);
228
- }
229
- }
230
- async function verifyTelegramToken(token) {
231
- const client = new TelegramClient({ tokenProvider: { loadToken: async () => token } });
232
- const identity = await client.getMe();
233
- if (!identity.is_bot)
234
- throw new Error('telegram_getme_not_bot');
235
- if (!Number.isSafeInteger(identity.id) || identity.id <= 0)
236
- throw new Error('telegram_getme_bot_id_invalid');
237
- return identity;
238
- }
239
- async function inspectTelegramWebhook(token) {
240
- const client = new TelegramClient({ tokenProvider: { loadToken: async () => token } });
241
- return client.getWebhookInfo();
242
- }
243
- async function removeTelegramWebhook(token) {
244
- const client = new TelegramClient({ tokenProvider: { loadToken: async () => token } });
245
- await client.deleteWebhook();
246
- }
247
- async function readBoundedCommandStdin() {
248
- if (process.stdin.isTTY)
249
- throw new Error('telegram_command_stdin_json_required');
250
- process.stdin.setEncoding('utf8');
251
- let value = '';
252
- for await (const chunk of process.stdin) {
253
- value += String(chunk);
254
- if (Buffer.byteLength(value, 'utf8') > 32 * 1024)
255
- throw new Error('telegram_command_stdin_json_too_large');
256
- }
257
- let parsed;
258
- try {
259
- parsed = JSON.parse(value);
260
- }
261
- catch {
262
- throw new Error('telegram_command_stdin_json_invalid');
263
- }
264
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
265
- throw new Error('telegram_command_stdin_json_invalid');
266
- const row = parsed;
267
- if (typeof row.name !== 'string' || !row.input || typeof row.input !== 'object' || Array.isArray(row.input)) {
268
- throw new Error('telegram_command_stdin_json_invalid');
269
- }
270
- return { name: row.name, input: row.input };
271
- }
272
- async function executeTypedArgv(argv, contract, cwd = process.cwd()) {
273
- const entrypoint = process.argv[1];
274
- if (!entrypoint)
275
- throw new Error('telegram_cli_entrypoint_unavailable');
276
- const timeoutMs = timeoutFor(contract.latency);
277
- const outputLimit = outputCapFor(contract.latency);
278
- const childEnv = { ...process.env };
279
- for (const name of TELEGRAM_TOKEN_ENV_NAMES)
280
- delete childEnv[name];
281
- return new Promise((resolve, reject) => {
282
- const child = spawn(process.execPath, [entrypoint, ...argv], {
283
- cwd, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'], shell: false
284
- });
285
- let output = Buffer.alloc(0);
286
- let settled = false;
287
- const timer = setTimeout(() => finish(new Error('telegram_command_timeout')), timeoutMs);
288
- const append = (chunk) => {
289
- if (settled)
290
- return;
291
- output = Buffer.concat([output, chunk]);
292
- if (output.length > outputLimit)
293
- finish(new Error('telegram_command_output_limit'));
294
- };
295
- const finish = (error, code) => {
296
- if (settled)
297
- return;
298
- settled = true;
299
- clearTimeout(timer);
300
- if (child.exitCode === null)
301
- child.kill('SIGKILL');
302
- if (error)
303
- return reject(error);
304
- const text = output.toString('utf8').trim();
305
- if (code !== 0)
306
- return reject(new Error(`telegram_command_failed:exit_${code ?? 'signal'}`));
307
- try {
308
- resolve(JSON.parse(text));
309
- }
310
- catch {
311
- resolve(text);
312
- }
313
- };
314
- child.stdout.on('data', append);
315
- child.stderr.on('data', append);
316
- child.once('error', (error) => finish(new Error(`telegram_command_spawn_failed:${error.message}`)));
317
- child.once('close', (code) => finish(undefined, code));
318
- });
319
- }
320
- async function resolveTelegramCommandRoot(args, resolveRoot) {
321
- const indexes = args.flatMap((value, index) => value === '--project-root' ? [index] : []);
322
- if (indexes.length === 0)
323
- return process.cwd();
324
- if (indexes.length !== 1)
325
- throw new Error('telegram_project_root_repeated');
326
- const value = args[indexes[0] + 1];
327
- if (!value || value.startsWith('-'))
328
- throw new Error('telegram_project_root_required');
329
- if (!path.isAbsolute(value))
330
- throw new Error('telegram_project_root_absolute_required');
331
- try {
332
- const canonical = resolveRoot ? await resolveRoot(value) : await projectRootRealpath(value);
333
- if (!path.isAbsolute(canonical) || path.normalize(canonical) !== canonical) {
334
- throw new Error('invalid_canonical_project_root');
335
- }
336
- if (resolveRoot)
337
- return canonical;
338
- if (canonical === path.parse(canonical).root || !(await fs.stat(canonical)).isDirectory()) {
339
- throw new Error('invalid_project_root_directory');
340
- }
341
- const detected = await projectRoot(canonical);
342
- const detectedCanonical = await projectRootRealpath(detected);
343
- if (detectedCanonical !== canonical)
344
- throw new Error('not_exact_project_root');
345
- return canonical;
346
- }
347
- catch (error) {
348
- if (error instanceof Error && error.message.startsWith('telegram_project_root_'))
349
- throw error;
350
- throw new Error('telegram_project_root_invalid');
351
- }
352
- }
353
- async function readBoundedTokenStdin() {
354
- if (process.stdin.isTTY)
355
- throw new Error('telegram_token_stdin_required');
356
- process.stdin.setEncoding('utf8');
357
- let value = '';
358
- for await (const chunk of process.stdin) {
359
- value += String(chunk);
360
- if (Buffer.byteLength(value, 'utf8') > 1024)
361
- throw new Error('telegram_token_stdin_too_large');
362
- }
363
- const token = value.trim();
364
- if (!token)
365
- throw new Error('telegram_token_stdin_empty');
366
- return token;
367
- }
368
- function fail(error, json, supported) {
369
- process.exitCode = 1;
370
- return emit({
371
- schema: 'sks.telegram-command.v1', ok: false, error,
372
- ...(supported ? { supported } : {})
373
- }, json);
374
- }
375
- function telegramSetupFailure(error, json, failureStage) {
376
- process.exitCode = 1;
377
- return emit({
378
- schema: 'sks.telegram-setup-command.v1',
379
- ok: false,
380
- error,
381
- token_stored: false,
382
- ...(failureStage ? { failure_stage: failureStage } : {})
383
- }, json);
384
- }
385
- function telegramIdentityFailure(error) {
386
- const message = redactTelegramError(error);
387
- const code = error instanceof TelegramApiError ? error.code : null;
388
- if (code === 401 || code === 404 || /\b(?:401|404|Unauthorized|Not Found)\b/i.test(message)) {
389
- return 'telegram_token_rejected';
390
- }
391
- if (/telegram_(?:request_)?timeout|timed?\s*out/i.test(message)) {
392
- return 'telegram_identity_verification_timeout';
393
- }
394
- if (/fetch failed|network|ENOTFOUND|ECONN|EAI_AGAIN|offline/i.test(message)) {
395
- return 'telegram_identity_verification_network_failed';
396
- }
397
- if (message === 'telegram_token_invalid')
398
- return message;
399
- return 'telegram_identity_verification_failed';
400
- }
401
- function publicBotUsername(value) {
402
- const normalized = String(value ?? '').trim();
403
- return /^[A-Za-z0-9_]{5,64}$/.test(normalized) ? normalized : null;
404
- }
405
- function telegramSetupPartialFailure(input, json) {
406
- process.exitCode = 1;
407
- return emit({
408
- schema: 'sks.telegram-setup-command.v1',
409
- ok: false,
410
- partial_success: true,
411
- error: input.error,
412
- detail: input.detail,
413
- getme_verified: true,
414
- token_stored: false,
415
- token_source: 'unchanged',
416
- webhook_removed: input.webhookRemoved,
417
- pending_updates_dropped: false,
418
- bot_id: input.botId,
419
- bot_rotated: input.binding?.rotated ?? false,
420
- bot_state_reset: input.binding?.state_reset ?? false,
421
- restart_required: false,
422
- recovery: {
423
- action: 'rerun_secure_setup',
424
- command: 'sks telegram setup --token-stdin',
425
- note: 'The token was not printed or stored. Supply it again through non-TTY standard input.'
426
- }
427
- }, json);
428
- }
429
- function telegramSetupEnvOverrideBlocked(json) {
430
- process.exitCode = 1;
431
- return emit({
432
- schema: 'sks.telegram-setup-command.v1',
433
- ok: false,
434
- partial_success: false,
435
- error: 'telegram_operator_env_override_active',
436
- token_source: 'env',
437
- token_stored: false,
438
- storage_attempted: false,
439
- webhook_removed: false,
440
- pending_updates_dropped: false,
441
- operator_action: 'Remove the operator-managed Telegram token environment override before storing a file token, or keep managing the bot through that environment source.'
442
- }, json);
443
- }
444
- function emit(value, _json) {
445
- console.log(JSON.stringify(value, null, 2));
446
- return value;
447
- }
@@ -1,58 +0,0 @@
1
- import { randomInt, randomUUID } from 'node:crypto';
2
- import { SYSTEM_TELEGRAM_CLOCK } from './types.js';
3
- export class InMemoryTelegramAccessStore {
4
- clock;
5
- pairings = new Map();
6
- chats = new Map();
7
- transactionTail = Promise.resolve();
8
- constructor(clock = SYSTEM_TELEGRAM_CLOCK) {
9
- this.clock = clock;
10
- }
11
- async issuePairingCode(ttlMs = 5 * 60_000) {
12
- return this.transaction(async () => {
13
- this.expireCodes();
14
- const code = `${randomInt(100_000, 1_000_000)}-${randomUUID().slice(0, 4).toUpperCase()}`;
15
- const expiresAt = this.clock.now() + Math.max(30_000, Math.min(ttlMs, 15 * 60_000));
16
- this.pairings.set(code, { code, expiresAt, used: false });
17
- return { code, expires_at: new Date(expiresAt).toISOString() };
18
- });
19
- }
20
- async pairAtomically(input) {
21
- return this.transaction(async () => {
22
- const record = this.pairings.get(input.code);
23
- if (input.chatType !== 'private'
24
- || !Number.isSafeInteger(input.chatId) || input.chatId <= 0
25
- || !Number.isSafeInteger(input.senderId) || input.senderId <= 0
26
- || !record || record.used || record.expiresAt <= this.clock.now())
27
- return false;
28
- record.used = true;
29
- this.chats.clear();
30
- this.chats.set(input.chatId, {
31
- chatId: input.chatId,
32
- senderId: input.senderId,
33
- pairedAt: new Date(this.clock.now()).toISOString()
34
- });
35
- return true;
36
- });
37
- }
38
- async authorized(chatId, senderId) {
39
- return this.transaction(async () => {
40
- const chat = this.chats.get(chatId);
41
- return chat?.senderId === senderId;
42
- });
43
- }
44
- async authorizedCount() {
45
- return this.transaction(async () => this.chats.size);
46
- }
47
- expireCodes() {
48
- for (const [code, record] of this.pairings) {
49
- if (record.used || record.expiresAt <= this.clock.now())
50
- this.pairings.delete(code);
51
- }
52
- }
53
- transaction(operation) {
54
- const next = this.transactionTail.then(operation, operation);
55
- this.transactionTail = next.then(() => undefined, () => undefined);
56
- return next;
57
- }
58
- }
@@ -1,157 +0,0 @@
1
- import { redactTelegramError } from './redaction.js';
2
- export class TelegramApiError extends Error {
3
- code;
4
- retryAfterSeconds;
5
- constructor(message, code = null, retryAfterSeconds = null) {
6
- super(message);
7
- this.name = 'TelegramApiError';
8
- this.code = code;
9
- this.retryAfterSeconds = retryAfterSeconds;
10
- }
11
- }
12
- export class TelegramClient {
13
- tokenProvider;
14
- fetchImpl;
15
- baseUrl;
16
- requestTimeoutMs;
17
- constructor(options) {
18
- this.tokenProvider = options.tokenProvider;
19
- this.fetchImpl = options.fetch ?? globalThis.fetch;
20
- this.baseUrl = validatedBaseUrl(options.baseUrl ?? 'https://api.telegram.org', options.allowInsecureLocalhost === true);
21
- this.requestTimeoutMs = Math.max(1_000, Math.min(options.requestTimeoutMs ?? 20_000, 120_000));
22
- }
23
- getMe(signal) {
24
- return this.call('getMe', {}, signal);
25
- }
26
- async getWebhookInfo(signal) {
27
- const info = await this.call('getWebhookInfo', {}, signal);
28
- if (!info || typeof info.url !== 'string')
29
- throw new TelegramApiError('telegram_webhook_info_invalid');
30
- return info;
31
- }
32
- async deleteWebhook(signal) {
33
- const removed = await this.call('deleteWebhook', {
34
- drop_pending_updates: false
35
- }, signal);
36
- if (removed !== true)
37
- throw new TelegramApiError('telegram_delete_webhook_failed');
38
- }
39
- getUpdates(input) {
40
- if (!Number.isSafeInteger(input.offset) || input.offset < 0) {
41
- return Promise.reject(new TelegramApiError('telegram_offset_invalid'));
42
- }
43
- const timeoutSeconds = Number.isFinite(input.timeoutSeconds)
44
- ? Math.max(0, Math.min(50, Math.floor(input.timeoutSeconds)))
45
- : 0;
46
- return this.call('getUpdates', {
47
- offset: input.offset,
48
- timeout: timeoutSeconds,
49
- allowed_updates: [...(input.allowedUpdates ?? ['message'])]
50
- }, input.signal, Math.max(this.requestTimeoutMs, Math.min(70_000, (timeoutSeconds * 1_000) + 10_000)));
51
- }
52
- sendMessage(chatId, text, signal) {
53
- if (!Number.isSafeInteger(chatId))
54
- return Promise.reject(new TelegramApiError('telegram_chat_id_invalid'));
55
- const bounded = text.slice(0, 4_000);
56
- if (!bounded)
57
- return Promise.reject(new TelegramApiError('telegram_message_empty'));
58
- return this.call('sendMessage', { chat_id: chatId, text: bounded }, signal);
59
- }
60
- async call(method, body, signal, timeoutMs = this.requestTimeoutMs) {
61
- const token = String(await this.tokenProvider.loadToken() ?? '').trim();
62
- if (!token)
63
- throw new TelegramApiError('telegram_token_unavailable');
64
- if (!/^\d+:[A-Za-z0-9_-]+$/.test(token))
65
- throw new TelegramApiError('telegram_token_invalid');
66
- const endpoint = new URL(`/bot${token}/${method}`, this.baseUrl);
67
- const requestController = new AbortController();
68
- let timedOut = false;
69
- const timer = setTimeout(() => {
70
- timedOut = true;
71
- requestController.abort(new Error('telegram_request_timeout'));
72
- }, timeoutMs);
73
- const abort = () => requestController.abort(signal?.reason);
74
- if (signal?.aborted)
75
- requestController.abort(signal.reason);
76
- else
77
- signal?.addEventListener('abort', abort, { once: true });
78
- try {
79
- const response = await this.fetchImpl(endpoint, {
80
- method: 'POST',
81
- headers: { 'content-type': 'application/json; charset=utf-8' },
82
- body: JSON.stringify(body),
83
- redirect: 'error',
84
- signal: requestController.signal
85
- });
86
- const decoded = await decodeBoundedJson(response);
87
- if (!response.ok || !decoded.ok) {
88
- const failure = decoded.ok ? null : decoded;
89
- throw new TelegramApiError(redactTelegramError(failure?.description || `telegram_http_${response.status}`, token), failure?.error_code ?? response.status, failure?.parameters?.retry_after ?? null);
90
- }
91
- return decoded.result;
92
- }
93
- catch (error) {
94
- if (error instanceof TelegramApiError)
95
- throw error;
96
- if (timedOut)
97
- throw new TelegramApiError('telegram_request_timeout');
98
- if (signal?.aborted)
99
- throw error;
100
- throw new TelegramApiError(redactTelegramError(error, token));
101
- }
102
- finally {
103
- clearTimeout(timer);
104
- signal?.removeEventListener('abort', abort);
105
- }
106
- }
107
- }
108
- async function decodeBoundedJson(response) {
109
- const maximum = 2 * 1024 * 1024;
110
- const declared = Number(response.headers.get('content-length') ?? '0');
111
- if (Number.isFinite(declared) && declared > maximum)
112
- throw new TelegramApiError('telegram_response_too_large');
113
- const reader = response.body?.getReader();
114
- let text = '';
115
- if (reader) {
116
- const decoder = new TextDecoder();
117
- let bytes = 0;
118
- while (true) {
119
- const chunk = await reader.read();
120
- if (chunk.done)
121
- break;
122
- bytes += chunk.value.byteLength;
123
- if (bytes > maximum) {
124
- await reader.cancel().catch(() => undefined);
125
- throw new TelegramApiError('telegram_response_too_large');
126
- }
127
- text += decoder.decode(chunk.value, { stream: true });
128
- }
129
- text += decoder.decode();
130
- }
131
- else {
132
- text = await response.text();
133
- if (Buffer.byteLength(text, 'utf8') > maximum)
134
- throw new TelegramApiError('telegram_response_too_large');
135
- }
136
- try {
137
- const value = JSON.parse(text);
138
- if (!value || typeof value !== 'object' || typeof value.ok !== 'boolean'
139
- || (value.ok && !('result' in value))) {
140
- throw new Error('shape');
141
- }
142
- return value;
143
- }
144
- catch {
145
- throw new TelegramApiError('telegram_response_invalid');
146
- }
147
- }
148
- function validatedBaseUrl(raw, allowInsecureLocalhost) {
149
- const url = new URL(raw);
150
- const local = url.hostname === '127.0.0.1' || url.hostname === 'localhost' || url.hostname === '::1';
151
- if (url.username || url.password || url.search || url.hash)
152
- throw new Error('telegram_base_url_invalid');
153
- if (url.protocol !== 'https:' && !(allowInsecureLocalhost && local && url.protocol === 'http:')) {
154
- throw new Error('telegram_base_url_https_required');
155
- }
156
- return url;
157
- }