sneakoscope 8.1.3 → 8.2.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 +10 -8
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/commands/telegram-command.js +46 -6
- package/dist/core/hooks-runtime/stop-finalization.js +11 -1
- package/dist/core/telegram/doctor.js +2 -2
- package/dist/core/telegram/liveness.js +10 -4
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +12 -0
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,14 +22,15 @@ Proof-first orchestration for Codex CLI, ChatGPT Desktop, AI coding agents, mult
|
|
|
22
22
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
23
23
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
24
24
|
|
|
25
|
-
This README documents package **SKS 8.
|
|
25
|
+
This README documents package **SKS 8.2.0** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
|
|
26
26
|
|
|
27
27
|
Use the official latest stable SKS and Codex CLI releases. The Codex compatibility SSOT is always the **current latest stable** host; capability probes measure what that host can actually do. Product docs do not crown a fixed `0.x.y` string as SSOT (release pins and schema directories are measured artifacts for the current package, not a permanent product version claim). Menu Bar / Center induce updates to the latest stable build. Run `sks update-check` for what is installed and read the capability report for what is supported. Install SSOT is npm `sneakoscope@latest`; PATH `sks` and Menu Bar stamped generation must match that version or gates fail. It resolves managed SKS skills from the authoritative global install, preserves a runnable Naruto child slot when `max_threads=2`, and keeps Menu Bar repair transactional so stamped generations remain verifiable. Naruto uses stable opt-in multi-agent V2 when the host exposes it (Codex official multi-agent wrap-only; SKS does not reimplement a parallel runtime). Local code search is mode-separated (`sks search files|text|structure|symbol|context`); `context` is answered by the compiled TriWiki Context Graph (`context-graph.json` is exhaustive authority; `context-pack.json` and managed `AGENTS.md` are bounded projections) — see [docs/architecture/context-graph.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/architecture/context-graph.md) and [docs/PRODUCT-CONTRACT.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/PRODUCT-CONTRACT.md). See [CHANGELOG.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/CHANGELOG.md).
|
|
28
28
|
|
|
29
|
-
## What 8.
|
|
29
|
+
## What 8.2.0 Ships
|
|
30
30
|
|
|
31
|
-
| Problem | 8.
|
|
31
|
+
| Problem | 8.2.0 behavior |
|
|
32
32
|
| --- | --- |
|
|
33
|
+
| SKS Center could reject a valid token for a user-selected BotFather bot with only a generic verification message | Setup accepts any bot the user owns, binds the exact `getMe` identity, returns actionable verification failures, and keeps native/CLI liveness receipts schema-compatible. |
|
|
33
34
|
| Overview mixed Menu Bar, installed SKS, and cached registry versions | Each value is labeled by authority, stale or unavailable probes remain explicit, and Refresh forces a bounded update-status refresh. |
|
|
34
35
|
| Naruto stopped creating children after its first wave | The root parent records settled waves, recovers open-thread capacity, rescans the ready DAG, and can launch later direct-child waves under the same workflow run. |
|
|
35
36
|
| Most delegated work drifted to Sol Max | Read-heavy discovery uses Terra Max, ordinary implementation uses Sol High, and Sol Max is reserved for focused high-risk or final judgment slices. |
|
|
@@ -80,10 +81,11 @@ or public webhook.
|
|
|
80
81
|
|
|
81
82
|
#### BotFather setup
|
|
82
83
|
|
|
83
|
-
1. In Telegram, open **@BotFather
|
|
84
|
-
|
|
85
|
-
bot
|
|
86
|
-
|
|
84
|
+
1. In Telegram, open **@BotFather** and create a bot with `/newbot`, or select
|
|
85
|
+
any existing bot that you own. Copy that bot's token. SKS does not assume a
|
|
86
|
+
fixed bot name or username: it verifies the token with `getMe` and binds the
|
|
87
|
+
returned bot identity. Treat the token like a password. Do not place it in
|
|
88
|
+
a shell command, config file, commit, issue, or screenshot.
|
|
87
89
|
2. On the Mac that runs SKS, open **SKS Center → Remote Coding**, choose
|
|
88
90
|
**Enter Bot Token…**, and paste the token into the native secure-input
|
|
89
91
|
sheet. SKS verifies `getMe` and the bot's webhook state before it stores
|
|
@@ -190,7 +192,7 @@ remote change on the operator's behalf.
|
|
|
190
192
|
|
|
191
193
|
## Desktop Bridge
|
|
192
194
|
|
|
193
|
-
SKS 8.
|
|
195
|
+
SKS 8.2.0 uses a single local **Desktop Bridge** for managed Codex Desktop and
|
|
194
196
|
CLI routing. ChatGPT OAuth stays in the Codex identity plane. Codex-LB and
|
|
195
197
|
OpenRouter credentials are independent profiles that can be configured and
|
|
196
198
|
validated simultaneously; changing one profile does not remove the other.
|
|
@@ -6,7 +6,7 @@ import { TELEGRAM_TOKEN_ENV_NAMES, bindTelegramBotIdentity, issueTelegramPairing
|
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import fs from 'node:fs/promises';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import { TelegramClient } from '../telegram/client.js';
|
|
9
|
+
import { TelegramApiError, TelegramClient } from '../telegram/client.js';
|
|
10
10
|
import { redactTelegramError } from '../telegram/redaction.js';
|
|
11
11
|
import { projectRoot } from '../fsx.js';
|
|
12
12
|
import { projectRootRealpath } from '../session/project-namespace.js';
|
|
@@ -63,7 +63,7 @@ export function usage() {
|
|
|
63
63
|
' doctor Evaluate getMe, pairing, poller, and liveness evidence.',
|
|
64
64
|
' pair Issue a short-lived code for the intended private Telegram chat.',
|
|
65
65
|
' setup --token-stdin [--remove-webhook]',
|
|
66
|
-
' Verify and store
|
|
66
|
+
' Verify and store the token for any @BotFather bot you own; an existing webhook requires explicit removal consent.',
|
|
67
67
|
' prepare --stdin-json Validate an authorized native request through the typed contract.',
|
|
68
68
|
' execute --stdin-json Execute a prepared request; destructive requests also need --confirmed.',
|
|
69
69
|
'',
|
|
@@ -108,16 +108,22 @@ export async function telegramCommand(args = [], dependencies = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
if (action === 'setup') {
|
|
110
110
|
if (!args.includes('--token-stdin'))
|
|
111
|
-
return
|
|
111
|
+
return telegramSetupFailure('telegram_token_stdin_required', json);
|
|
112
112
|
if (args.includes('--drop-pending-updates'))
|
|
113
|
-
return
|
|
113
|
+
return telegramSetupFailure('telegram_drop_pending_updates_unsupported', json);
|
|
114
114
|
const environment = dependencies.environment ?? process.env;
|
|
115
115
|
const envOverrideActive = args.includes('--operator-env-override-active')
|
|
116
116
|
|| TELEGRAM_TOKEN_ENV_NAMES.some((name) => String(environment[name] ?? '').trim().length > 0);
|
|
117
117
|
if (envOverrideActive)
|
|
118
118
|
return telegramSetupEnvOverrideBlocked(json);
|
|
119
119
|
const token = await (dependencies.readTokenStdin ?? readBoundedTokenStdin)();
|
|
120
|
-
|
|
120
|
+
let identity;
|
|
121
|
+
try {
|
|
122
|
+
identity = await (dependencies.verifyToken ?? verifyTelegramToken)(token);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return telegramSetupFailure(telegramIdentityFailure(error), json, 'getme');
|
|
126
|
+
}
|
|
121
127
|
const removeWebhook = args.includes('--remove-webhook');
|
|
122
128
|
let webhook;
|
|
123
129
|
try {
|
|
@@ -182,6 +188,7 @@ export async function telegramCommand(args = [], dependencies = {}) {
|
|
|
182
188
|
webhook_configured_before: webhookConfigured,
|
|
183
189
|
webhook_removed: webhookRemoved, pending_updates_dropped: false,
|
|
184
190
|
bot_id: identity?.id ?? null,
|
|
191
|
+
bot_username: publicBotUsername(identity?.username),
|
|
185
192
|
bot_rotated: binding?.rotated ?? false,
|
|
186
193
|
bot_state_reset: binding?.state_reset ?? false,
|
|
187
194
|
restart_required: true
|
|
@@ -214,7 +221,10 @@ export async function telegramCommand(args = [], dependencies = {}) {
|
|
|
214
221
|
return fail('telegram_unknown_action', json, ['status', 'doctor', 'pair', 'setup', 'prepare', 'execute']);
|
|
215
222
|
}
|
|
216
223
|
catch (error) {
|
|
217
|
-
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
return action === 'setup'
|
|
226
|
+
? telegramSetupFailure(redactTelegramError(error), json)
|
|
227
|
+
: fail(message, json);
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
230
|
async function verifyTelegramToken(token) {
|
|
@@ -362,6 +372,36 @@ function fail(error, json, supported) {
|
|
|
362
372
|
...(supported ? { supported } : {})
|
|
363
373
|
}, json);
|
|
364
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
|
+
}
|
|
365
405
|
function telegramSetupPartialFailure(input, json) {
|
|
366
406
|
process.exitCode = 1;
|
|
367
407
|
return emit({
|
|
@@ -97,6 +97,8 @@ function honestGapHeadingResolved(line, nextLine) {
|
|
|
97
97
|
return /^(?:없음|없습니다|없다|none|no(?:ne)?|0|0개)[.!。]?$/i.test(String(nextLine || '').trim());
|
|
98
98
|
}
|
|
99
99
|
function honestGapLineResolved(line) {
|
|
100
|
+
if (honestIssueLineCompleted(line))
|
|
101
|
+
return true;
|
|
100
102
|
if (/(?:unverified|미검증)\s*:\s*\[\s*\]/i.test(line) && /blockers?\s*:\s*\[\s*\]/i.test(line))
|
|
101
103
|
return true;
|
|
102
104
|
if (/(?:^|[\s*-])(?:unverified|미검증|blockers?)\s*:\s*\[\s*\](?:\s*(?:[,.;]|$).*)?$/i.test(line))
|
|
@@ -111,7 +113,7 @@ function honestGapLineResolved(line) {
|
|
|
111
113
|
return true;
|
|
112
114
|
if (/no\s+active\s+blocking\s+route\s+gate\s+detected/i.test(line))
|
|
113
115
|
return true;
|
|
114
|
-
if (/(?:blockers?|차단(?:\s*(?:항목|요소|건))?)\s*(?:[:=]\s*)?0(?:건|개)?\b/i.test(line))
|
|
116
|
+
if (/(?:blockers?(?:은|는|이|가)?|차단(?:\s*(?:항목|요소|건))?)\s*(?:[:=]\s*)?0(?:건|개)?\b/i.test(line))
|
|
115
117
|
return true;
|
|
116
118
|
if (/(non[-\s]?blocker|non[-\s]?blocking|not\s+(?:a\s+)?blocker|no\s+blocker|does\s+not\s+block|not\s+blocking|비\s*차단|blocker\s*(?:는|가)?\s*(?:아님|아닙니다|없음)|차단(?:하지|하진|하지는)\s*않|막(?:지|지는)\s*않)/i.test(line))
|
|
117
119
|
return true;
|
|
@@ -123,6 +125,14 @@ function honestGapLineResolved(line) {
|
|
|
123
125
|
return false;
|
|
124
126
|
return /(없음|없습니다|없다|해당 없음|none|no unresolved|no remaining|no gaps|zero|0개|n\/a|not applicable)\.?\s*$/i.test(line);
|
|
125
127
|
}
|
|
128
|
+
function honestIssueLineCompleted(line) {
|
|
129
|
+
const text = String(line || '').trim();
|
|
130
|
+
const unresolved = /(?:미해결|미완료|해결|수정|보완)\s*(?:필요|예정|대기|중)|(?:not|isn't|wasn't)\s+(?:fixed|resolved|addressed|implemented|completed)|(?:still|remains?|pending)\b/i;
|
|
131
|
+
if (unresolved.test(text))
|
|
132
|
+
return false;
|
|
133
|
+
return /(?:해결|수정|보완|정상화|완료)(?:했(?:습니다|다|음)?|됐(?:습니다|다)?|됨|함)?[.!。`*_)]*$/i.test(text)
|
|
134
|
+
|| /(?:fixed|resolved|addressed|implemented|completed)[.!。`*_)]*$/i.test(text);
|
|
135
|
+
}
|
|
126
136
|
export function shouldLoopBackAfterHonestMode(state = {}) {
|
|
127
137
|
if (!state?.mission_id || state.implementation_allowed === false)
|
|
128
138
|
return false;
|
|
@@ -123,8 +123,8 @@ async function probeTelegramReceipt(receiptPath, now) {
|
|
|
123
123
|
token_configured: receipt.token_configured,
|
|
124
124
|
token_source: receipt.token_source ?? (receipt.token_configured ? 'unknown' : 'none'),
|
|
125
125
|
bot_identity_valid: receipt.bot_identity_valid,
|
|
126
|
-
getme_checked_at: receipt.getme_checked_at,
|
|
127
|
-
getme_latency_ms: receipt.getme_latency_ms,
|
|
126
|
+
getme_checked_at: receipt.getme_checked_at ?? null,
|
|
127
|
+
getme_latency_ms: receipt.getme_latency_ms ?? null,
|
|
128
128
|
getme_check_kind: 'receipt',
|
|
129
129
|
paired_chat_count: receipt.paired_chat_count,
|
|
130
130
|
audit_healthy: receipt.audit_healthy !== false,
|
|
@@ -89,8 +89,9 @@ function isTelegramLivenessReceipt(value) {
|
|
|
89
89
|
&& (row.token_source === undefined || ['env', 'user_secret_file', 'none', 'unknown'].includes(String(row.token_source)))
|
|
90
90
|
&& optionalPositiveSafeInteger(row.bot_id)
|
|
91
91
|
&& typeof row.bot_identity_valid === 'boolean'
|
|
92
|
-
&&
|
|
93
|
-
&& (row.getme_latency_ms ===
|
|
92
|
+
&& optionalNullableIso(row.getme_checked_at)
|
|
93
|
+
&& (row.getme_latency_ms === undefined || row.getme_latency_ms === null
|
|
94
|
+
|| (Number.isFinite(row.getme_latency_ms) && Number(row.getme_latency_ms) >= 0))
|
|
94
95
|
&& Number.isSafeInteger(row.paired_chat_count) && Number(row.paired_chat_count) >= 0
|
|
95
96
|
&& typeof row.started_at === 'string' && Number.isFinite(Date.parse(row.started_at))
|
|
96
97
|
&& typeof row.heartbeat_at === 'string' && Number.isFinite(Date.parse(row.heartbeat_at))
|
|
@@ -110,9 +111,14 @@ function isPollSnapshot(value) {
|
|
|
110
111
|
&& typeof row.running === 'boolean'
|
|
111
112
|
&& Number.isSafeInteger(row.offset) && Number(row.offset) >= 0
|
|
112
113
|
&& Number.isSafeInteger(row.consecutive_failures) && Number(row.consecutive_failures) >= 0
|
|
113
|
-
&&
|
|
114
|
-
&& (row.
|
|
114
|
+
&& optionalNullableIso(row.last_poll_at)
|
|
115
|
+
&& optionalNullableIso(row.last_success_at)
|
|
116
|
+
&& optionalNullableIso(row.last_update_at)
|
|
117
|
+
&& (row.last_error === undefined || row.last_error === null || typeof row.last_error === 'string');
|
|
115
118
|
}
|
|
116
119
|
function nullableIso(value) {
|
|
117
120
|
return value === null || (typeof value === 'string' && Number.isFinite(Date.parse(value)));
|
|
118
121
|
}
|
|
122
|
+
function optionalNullableIso(value) {
|
|
123
|
+
return value === undefined || nullableIso(value);
|
|
124
|
+
}
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '8.
|
|
1
|
+
export const PACKAGE_VERSION = '8.2.0';
|
|
@@ -46,8 +46,8 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
46
46
|
subtitle: "Create a Telegram bot, then let SKS validate and store its token privately.",
|
|
47
47
|
views: [
|
|
48
48
|
NativeView.detail(
|
|
49
|
-
"1.
|
|
50
|
-
"3.
|
|
49
|
+
"1. Create or select any bot you own in @BotFather. 2. Copy that bot's complete HTTP API token. " +
|
|
50
|
+
"3. Choose Enter Bot Token below. SKS binds the identity returned by Telegram getMe; it never assumes a bot name or username."
|
|
51
51
|
),
|
|
52
52
|
NativeView.detail(
|
|
53
53
|
"The token is sent only through process stdin to the canonical SKS setup command, verified with Telegram getMe, and stored in the private user secret file. It is never shown again or written to the SKS action log. Revoke it in BotFather if you suspect exposure."
|
|
@@ -113,8 +113,8 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
113
113
|
window: window,
|
|
114
114
|
title: "Enter Telegram Bot Token",
|
|
115
115
|
message: removeWebhook
|
|
116
|
-
? "Paste
|
|
117
|
-
: "Paste the HTTP API token
|
|
116
|
+
? "Paste this selected bot's BotFather token again. With your consent, SKS will remove that bot's existing webhook without dropping pending updates, verify its live identity, and store the token privately."
|
|
117
|
+
: "Paste the complete HTTP API token for any bot you own in @BotFather. SKS will accept the identity returned by Telegram getMe without assuming a bot name, then store the token privately.",
|
|
118
118
|
secure: true,
|
|
119
119
|
placeholder: "123456789:BotFatherToken",
|
|
120
120
|
actionTitle: removeWebhook ? "Remove Webhook & Save" : "Verify & Save"
|
|
@@ -269,7 +269,7 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
269
269
|
"Token not saved",
|
|
270
270
|
detail: self.publicFailure(
|
|
271
271
|
result.output,
|
|
272
|
-
fallback: "Telegram could not
|
|
272
|
+
fallback: "Telegram setup could not complete. Check the connection and SKS diagnostics, then try again."
|
|
273
273
|
)
|
|
274
274
|
)
|
|
275
275
|
return
|
|
@@ -280,11 +280,12 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
280
280
|
let receipt = try await restart.value
|
|
281
281
|
self?.endOperation()
|
|
282
282
|
self?.render(receipt)
|
|
283
|
+
let selectedBot = response.bot_username.map { "@\($0)" } ?? "The selected bot"
|
|
283
284
|
self?.showProgress(
|
|
284
285
|
"Bot verified and connected",
|
|
285
286
|
detail: response.bot_state_reset == true
|
|
286
|
-
? "
|
|
287
|
-
: "
|
|
287
|
+
? "\(selectedBot) is verified and stored privately. Bot-scoped pairing and poll state were reset; generate a new pairing code."
|
|
288
|
+
: "\(selectedBot) is verified and stored privately. Generate a pairing code for the intended private chat."
|
|
288
289
|
)
|
|
289
290
|
} catch {
|
|
290
291
|
self?.endOperation()
|
|
@@ -448,6 +449,14 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
448
449
|
switch error {
|
|
449
450
|
case "telegram_token_invalid", "telegram_token_stdin_empty":
|
|
450
451
|
return "That value is not a complete BotFather HTTP API token."
|
|
452
|
+
case "telegram_token_rejected":
|
|
453
|
+
return "Telegram rejected this token. In @BotFather, select the bot you want SKS to use and copy that bot's current complete token."
|
|
454
|
+
case "telegram_identity_verification_timeout":
|
|
455
|
+
return "Telegram identity verification timed out. Check the network connection and try the selected bot token again."
|
|
456
|
+
case "telegram_identity_verification_network_failed":
|
|
457
|
+
return "Telegram identity verification could not reach the Bot API. Check the network connection and try again."
|
|
458
|
+
case "telegram_identity_verification_failed":
|
|
459
|
+
return "Telegram returned an invalid bot identity response. Try the selected bot's current token again."
|
|
451
460
|
case let value where value.contains("401") || value.contains("Unauthorized"):
|
|
452
461
|
return "Telegram rejected this token. Copy the current token from @BotFather and try again."
|
|
453
462
|
case let value where value.contains("timeout") || value.contains("network"):
|
|
@@ -63,6 +63,18 @@ enum SecureProcessEnvelope {
|
|
|
63
63
|
["env", "user_secret_file", "none", "unchanged"].contains(source) {
|
|
64
64
|
envelope["token_source"] = source
|
|
65
65
|
}
|
|
66
|
+
if let stage = boundedString(object["failure_stage"], limit: 40),
|
|
67
|
+
stage == "getme" {
|
|
68
|
+
envelope["failure_stage"] = stage
|
|
69
|
+
}
|
|
70
|
+
if let botID = object["bot_id"] as? NSNumber,
|
|
71
|
+
botID.int64Value > 0 {
|
|
72
|
+
envelope["bot_id"] = botID
|
|
73
|
+
}
|
|
74
|
+
if let username = boundedString(object["bot_username"], limit: 64),
|
|
75
|
+
username.range(of: #"^[A-Za-z0-9_]{5,64}$"#, options: .regularExpression) != nil {
|
|
76
|
+
envelope["bot_username"] = username
|
|
77
|
+
}
|
|
66
78
|
if let action = boundedString(object["operator_action"], limit: 480) {
|
|
67
79
|
envelope["operator_action"] = action
|
|
68
80
|
}
|
|
@@ -60,6 +60,27 @@ struct TelegramPollerReceipt: Codable, Sendable, Equatable {
|
|
|
60
60
|
let last_success_at: String?
|
|
61
61
|
let last_update_at: String?
|
|
62
62
|
let last_error: String?
|
|
63
|
+
|
|
64
|
+
private enum CodingKeys: String, CodingKey {
|
|
65
|
+
case schema, running, offset, consecutive_failures
|
|
66
|
+
case last_poll_at, last_success_at, last_update_at, last_error
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func encode(to encoder: Encoder) throws {
|
|
70
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
71
|
+
try container.encode(schema, forKey: .schema)
|
|
72
|
+
try container.encode(running, forKey: .running)
|
|
73
|
+
try container.encode(offset, forKey: .offset)
|
|
74
|
+
try container.encode(consecutive_failures, forKey: .consecutive_failures)
|
|
75
|
+
if let last_poll_at { try container.encode(last_poll_at, forKey: .last_poll_at) }
|
|
76
|
+
else { try container.encodeNil(forKey: .last_poll_at) }
|
|
77
|
+
if let last_success_at { try container.encode(last_success_at, forKey: .last_success_at) }
|
|
78
|
+
else { try container.encodeNil(forKey: .last_success_at) }
|
|
79
|
+
if let last_update_at { try container.encode(last_update_at, forKey: .last_update_at) }
|
|
80
|
+
else { try container.encodeNil(forKey: .last_update_at) }
|
|
81
|
+
if let last_error { try container.encode(last_error, forKey: .last_error) }
|
|
82
|
+
else { try container.encodeNil(forKey: .last_error) }
|
|
83
|
+
}
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
struct TelegramLivenessReceipt: Codable, Sendable, Equatable {
|
|
@@ -80,6 +101,39 @@ struct TelegramLivenessReceipt: Codable, Sendable, Equatable {
|
|
|
80
101
|
let audit_healthy: Bool?
|
|
81
102
|
let audit_last_error: String?
|
|
82
103
|
let poller: TelegramPollerReceipt
|
|
104
|
+
|
|
105
|
+
private enum CodingKeys: String, CodingKey {
|
|
106
|
+
case schema, generation, pid, running, token_configured, token_source
|
|
107
|
+
case bot_id, bot_identity_valid, getme_checked_at, getme_latency_ms
|
|
108
|
+
case paired_chat_count, started_at, heartbeat_at, stale_after_seconds
|
|
109
|
+
case audit_healthy, audit_last_error, poller
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func encode(to encoder: Encoder) throws {
|
|
113
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
114
|
+
try container.encode(schema, forKey: .schema)
|
|
115
|
+
try container.encode(generation, forKey: .generation)
|
|
116
|
+
try container.encode(pid, forKey: .pid)
|
|
117
|
+
try container.encode(running, forKey: .running)
|
|
118
|
+
try container.encode(token_configured, forKey: .token_configured)
|
|
119
|
+
try container.encode(token_source, forKey: .token_source)
|
|
120
|
+
if let bot_id { try container.encode(bot_id, forKey: .bot_id) }
|
|
121
|
+
else { try container.encodeNil(forKey: .bot_id) }
|
|
122
|
+
try container.encode(bot_identity_valid, forKey: .bot_identity_valid)
|
|
123
|
+
if let getme_checked_at { try container.encode(getme_checked_at, forKey: .getme_checked_at) }
|
|
124
|
+
else { try container.encodeNil(forKey: .getme_checked_at) }
|
|
125
|
+
if let getme_latency_ms { try container.encode(getme_latency_ms, forKey: .getme_latency_ms) }
|
|
126
|
+
else { try container.encodeNil(forKey: .getme_latency_ms) }
|
|
127
|
+
try container.encode(paired_chat_count, forKey: .paired_chat_count)
|
|
128
|
+
try container.encode(started_at, forKey: .started_at)
|
|
129
|
+
try container.encode(heartbeat_at, forKey: .heartbeat_at)
|
|
130
|
+
try container.encode(stale_after_seconds, forKey: .stale_after_seconds)
|
|
131
|
+
if let audit_healthy { try container.encode(audit_healthy, forKey: .audit_healthy) }
|
|
132
|
+
else { try container.encodeNil(forKey: .audit_healthy) }
|
|
133
|
+
if let audit_last_error { try container.encode(audit_last_error, forKey: .audit_last_error) }
|
|
134
|
+
else { try container.encodeNil(forKey: .audit_last_error) }
|
|
135
|
+
try container.encode(poller, forKey: .poller)
|
|
136
|
+
}
|
|
83
137
|
}
|
|
84
138
|
|
|
85
139
|
struct TelegramCenterSetupResponse: Decodable {
|
|
@@ -98,6 +152,8 @@ struct TelegramCenterSetupResponse: Decodable {
|
|
|
98
152
|
let webhook_removed: Bool?
|
|
99
153
|
let pending_updates_dropped: Bool?
|
|
100
154
|
let bot_state_reset: Bool?
|
|
155
|
+
let bot_id: Int64?
|
|
156
|
+
let bot_username: String?
|
|
101
157
|
let operator_action: String?
|
|
102
158
|
let recovery: Recovery?
|
|
103
159
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.2.0",
|
|
5
5
|
"description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|