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.
- package/README.md +46 -110
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +0 -2
- package/dist/cli/command-registry.js +0 -2
- package/dist/commands/codex.js +3 -3
- package/dist/commands/doctor.js +5 -157
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/codex/codex-cli-syntax-builder.js +0 -8
- package/dist/core/codex-app/codex-models-cache.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +0 -7
- package/dist/core/codex-compat/codex-capability-matrix.js +7 -7
- package/dist/core/codex-compat/codex-compat-report.js +8 -12
- package/dist/core/codex-compat/codex-runtime-contract.js +62 -0
- package/dist/core/codex-compat/codex-version-policy.js +7 -7
- package/dist/core/codex-control/codex-current-app-capability.js +3 -3
- package/dist/core/codex-control/codex-current-capability.js +67 -94
- package/dist/core/codex-control/codex-current-core-capability.js +3 -3
- package/dist/core/codex-control/codex-current-core-probe-runner.js +4 -4
- package/dist/core/codex-control/codex-current-core-real-probes.js +2 -2
- package/dist/core/codex-control/codex-current-feature-capability.js +4 -4
- package/dist/core/codex-control/codex-current-image-path-real-probe.js +1 -1
- package/dist/core/codex-control/codex-current-web-search-probe.js +1 -1
- package/dist/core/codex-control/codex-sdk-capability.js +2 -2
- package/dist/core/commands/remote-command.js +2 -2
- package/dist/core/context7-client.js +5 -2
- package/dist/core/doctor/codex-current-core-probe-doctor.js +2 -2
- package/dist/core/doctor/doctor-dirty-planner.js +1 -1
- package/dist/core/doctor/doctor-readiness-matrix.js +0 -7
- package/dist/core/feature-fixtures.js +0 -4
- package/dist/core/feature-registry.js +0 -1
- package/dist/core/image-ux-review.js +3 -3
- package/dist/core/install/installed-package-smoke.js +2 -3
- package/dist/core/mad-sks/sql-plane/mcp-executor.js +4 -3
- package/dist/core/mcp/modern-protocol.js +36 -0
- package/dist/core/mcp-config/health-check.js +45 -30
- package/dist/core/perf/release-latency-slo.js +1 -2
- package/dist/core/proof/runtime-truth-matrix.js +1 -1
- package/dist/core/release/canonical-test-proof.js +1 -2
- package/dist/core/release/gate-manifest.js +5 -2
- package/dist/core/release/physical-release-gates.js +0 -26
- package/dist/core/release/release-gate-affected-selector.js +2 -1
- package/dist/core/release/release-gate-contract.js +3 -1
- package/dist/core/release/release-pack-receipt.js +0 -1
- package/dist/core/release/stage-publish.js +1 -1
- package/dist/core/version-manager.js +4 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -12
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -6
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +28 -471
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +1 -43
- package/dist/scripts/codex-current-app-server-v2-check.js +9 -8
- package/dist/scripts/codex-current-binary-identity-check.js +2 -2
- package/dist/scripts/codex-current-capability-check.js +9 -7
- package/dist/scripts/codex-current-dependency-check.js +21 -0
- package/dist/scripts/docs-truthfulness-check.js +4 -19
- package/dist/scripts/fast-codex-service-tier-proof-check.js +0 -1
- package/dist/scripts/install-surface-ssot-check.js +2 -2
- package/dist/scripts/latest-version-guidance-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +0 -1
- package/dist/scripts/release-metadata-check.js +4 -1
- package/dist/scripts/release-version-truth-check.js +149 -0
- package/dist/vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/post-compact.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/pre-compact.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/session-start.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/snapshot-metadata.json +7 -5
- package/dist/vendor/openai-codex/latest/hooks/subagent-start.command.output.schema.json +4 -18
- package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.output.schema.json +2 -16
- package/package.json +4 -7
- package/release-gates.v2.json +49 -19
- package/config/codex-releases/rust-v0.146.0.json +0 -45
- package/dist/core/agents/agent-runner-codex-exec.js +0 -177
- package/dist/core/codex-compat/codex-release-manifest.js +0 -72
- package/dist/core/commands/telegram-command.js +0 -447
- package/dist/core/telegram/access.js +0 -58
- package/dist/core/telegram/client.js +0 -157
- package/dist/core/telegram/confirmation.js +0 -37
- package/dist/core/telegram/doctor.js +0 -154
- package/dist/core/telegram/index.js +0 -9
- package/dist/core/telegram/keychain.js +0 -317
- package/dist/core/telegram/liveness.js +0 -124
- package/dist/core/telegram/poller.js +0 -90
- package/dist/core/telegram/redaction.js +0 -15
- package/dist/core/telegram/state-lock.js +0 -416
- package/dist/core/telegram/types.js +0 -1
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileStore.swift +0 -498
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileSupport.swift +0 -236
- package/dist/native/sks-menubar/Sources/TelegramProcessGateway.swift +0 -454
- package/dist/native/sks-menubar/Sources/TelegramRuntimeSupport.swift +0 -56
- package/dist/native/sks-menubar/Sources/TelegramStateLock.swift +0 -448
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +0 -243
- package/dist/native/sks-menubar/Sources/TelegramTransport.swift +0 -499
- package/dist/scripts/codex-current-manifest-check.js +0 -29
- 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
|
-
}
|