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,494 +1,51 @@
|
|
|
1
1
|
import Cocoa
|
|
2
2
|
|
|
3
|
-
final class RemoteCodingViewController: NSViewController
|
|
4
|
-
private static let
|
|
5
|
-
private static let
|
|
6
|
-
|
|
7
|
-
private let processClient: ProcessClient
|
|
8
|
-
private let telegramService: TelegramMenuBarService
|
|
9
|
-
private let serviceBadge = NativeView.badge("Checking Telegram service", color: .systemBlue)
|
|
10
|
-
private let serviceDetail = NativeView.detail("Reading secret-free Telegram liveness status…")
|
|
11
|
-
private let pairingDetail = NativeView.detail("No pairing code has been issued in this session.")
|
|
12
|
-
private let serviceSpinner = NativeView.spinner(label: "Telegram operation in progress")
|
|
13
|
-
private lazy var setupButton = NativeView.button("Enter Bot Token…", target: self, action: #selector(enterToken))
|
|
14
|
-
private lazy var pairButton = NativeView.button("Generate Pairing Code", target: self, action: #selector(generatePairingCode))
|
|
15
|
-
private lazy var startButton = NativeView.button("Start", target: self, action: #selector(startService))
|
|
16
|
-
private lazy var stopButton = NativeView.button("Stop", target: self, action: #selector(stopService))
|
|
17
|
-
private lazy var restartButton = NativeView.button("Restart", target: self, action: #selector(restartService))
|
|
18
|
-
private lazy var refreshButton = NativeView.button("Refresh Status", target: self, action: #selector(refreshStatusAction))
|
|
19
|
-
private var operationInFlight = false
|
|
20
|
-
private var tokenConfigured = false
|
|
21
|
-
private var pollerRunning = false
|
|
22
|
-
|
|
23
|
-
init(processClient: ProcessClient, telegramService: TelegramMenuBarService) {
|
|
24
|
-
self.processClient = processClient
|
|
25
|
-
self.telegramService = telegramService
|
|
26
|
-
super.init(nibName: nil, bundle: nil)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
required init?(coder: NSCoder) { nil }
|
|
3
|
+
final class RemoteCodingViewController: NSViewController {
|
|
4
|
+
private static let websiteURL = URL(string: "https://paseo.sh/")!
|
|
5
|
+
private static let docsURL = URL(string: "https://paseo.sh/docs")!
|
|
30
6
|
|
|
31
7
|
override func loadView() {
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
refreshButton.setAccessibilityHelp("Refresh secret-free bot, pairing, audit, and poller status.")
|
|
40
|
-
pairingDetail.isSelectable = true
|
|
41
|
-
pairingDetail.setAccessibilityLabel("Telegram pairing instructions")
|
|
42
|
-
serviceDetail.setAccessibilityLabel("Telegram service status detail")
|
|
8
|
+
let websiteButton = NativeView.button(
|
|
9
|
+
"Visit Paseo",
|
|
10
|
+
target: self,
|
|
11
|
+
action: #selector(openWebsite)
|
|
12
|
+
)
|
|
13
|
+
websiteButton.setAccessibilityLabel("Visit Paseo website")
|
|
14
|
+
websiteButton.setAccessibilityHelp("Open the independent Paseo website in your default browser.")
|
|
43
15
|
|
|
44
|
-
let
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
NativeView.detail(
|
|
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
|
-
),
|
|
52
|
-
NativeView.detail(
|
|
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."
|
|
54
|
-
),
|
|
55
|
-
NativeView.row([botFatherButton, setupButton])
|
|
56
|
-
]
|
|
16
|
+
let docsButton = NativeView.button(
|
|
17
|
+
"Read Paseo Docs",
|
|
18
|
+
target: self,
|
|
19
|
+
action: #selector(openDocs)
|
|
57
20
|
)
|
|
21
|
+
docsButton.setAccessibilityLabel("Read Paseo documentation")
|
|
22
|
+
docsButton.setAccessibilityHelp("Open the official Paseo documentation in your default browser.")
|
|
58
23
|
|
|
59
|
-
let
|
|
60
|
-
title: "
|
|
61
|
-
subtitle: "
|
|
24
|
+
let recommendation = NativeView.card(
|
|
25
|
+
title: "Paseo (recommended)",
|
|
26
|
+
subtitle: "Run Codex from your desktop, phone, web browser, or terminal.",
|
|
62
27
|
views: [
|
|
63
28
|
NativeView.detail(
|
|
64
|
-
"
|
|
29
|
+
"Paseo is an independent open-source project. Sneakoscope does not install it, bundle its daemon, configure its authentication or relay, monitor it, or depend on it. Review Paseo's own documentation and security guidance before connecting devices."
|
|
65
30
|
),
|
|
66
|
-
|
|
67
|
-
pairButton
|
|
68
|
-
]
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
let statusCard = NativeView.card(
|
|
72
|
-
title: "Service Status",
|
|
73
|
-
subtitle: "Control the single SKS-owned poller for this companion session and inspect secret-free health evidence.",
|
|
74
|
-
views: [
|
|
75
|
-
NativeView.row([serviceBadge, serviceSpinner]),
|
|
76
|
-
serviceDetail,
|
|
77
|
-
NativeView.row([startButton, stopButton, restartButton, refreshButton])
|
|
31
|
+
NativeView.row([websiteButton, docsButton])
|
|
78
32
|
]
|
|
79
33
|
)
|
|
80
34
|
|
|
81
35
|
view = NativeView.page([
|
|
82
|
-
NativeView.title("
|
|
36
|
+
NativeView.title("Remote Coding"),
|
|
83
37
|
NativeView.detail(
|
|
84
|
-
"
|
|
38
|
+
"For remote and cross-device coding, Sneakoscope recommends evaluating Paseo as a separate companion for Codex."
|
|
85
39
|
),
|
|
86
|
-
|
|
87
|
-
pairingCard,
|
|
88
|
-
statusCard
|
|
40
|
+
recommendation
|
|
89
41
|
])
|
|
90
|
-
updateControls()
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
func refreshOnAppear() { refreshStatus() }
|
|
94
|
-
|
|
95
|
-
@objc private func openBotFather() {
|
|
96
|
-
NSWorkspace.shared.open(Self.botFatherURL)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@objc private func enterToken() {
|
|
100
|
-
if TelegramPrivateFileStore.operatorEnvironmentOverrideActive() {
|
|
101
|
-
showAttention(
|
|
102
|
-
"Environment-managed token is active",
|
|
103
|
-
detail: "SKS is using an operator-managed Telegram token environment override. Remove TELEGRAM_BOT_TOKEN or SKS_TELEGRAM_BOT_TOKEN and relaunch the companion before storing a BotFather token file."
|
|
104
|
-
)
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
promptForToken(removeWebhook: false)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private func promptForToken(removeWebhook: Bool) {
|
|
111
|
-
guard !operationInFlight, let window = view.window else { return }
|
|
112
|
-
AlertFactory.textSheet(
|
|
113
|
-
window: window,
|
|
114
|
-
title: "Enter Telegram Bot Token",
|
|
115
|
-
message: removeWebhook
|
|
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
|
-
secure: true,
|
|
119
|
-
placeholder: "123456789:BotFatherToken",
|
|
120
|
-
actionTitle: removeWebhook ? "Remove Webhook & Save" : "Verify & Save"
|
|
121
|
-
) { [weak self] token in
|
|
122
|
-
guard let self, let token else { return }
|
|
123
|
-
let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
124
|
-
guard self.validTokenShape(normalizedToken) else {
|
|
125
|
-
self.showAttention(
|
|
126
|
-
"Token not saved",
|
|
127
|
-
detail: "That value does not match the BotFather token format. Copy the complete HTTP API token and try again."
|
|
128
|
-
)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
self.saveToken(normalizedToken, removeWebhook: removeWebhook)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
@objc private func generatePairingCode() {
|
|
136
|
-
guard !operationInFlight else { return }
|
|
137
|
-
beginOperation("Issuing a short-lived pairing code…")
|
|
138
|
-
processClient.run(
|
|
139
|
-
["telegram", "pair", "--json"],
|
|
140
|
-
environment: telegramSecretFreeEnvironment,
|
|
141
|
-
timeout: NativeView.statusTimeout,
|
|
142
|
-
logOutput: false
|
|
143
|
-
) { [weak self] result in
|
|
144
|
-
guard let self else { return }
|
|
145
|
-
self.endOperation()
|
|
146
|
-
guard result.code == 0,
|
|
147
|
-
let response = self.decode(TelegramCenterPairResponse.self, result.output),
|
|
148
|
-
response.ok,
|
|
149
|
-
let code = response.code,
|
|
150
|
-
code.range(of: #"^\d{6}-[A-F0-9]{4}$"#, options: .regularExpression) != nil else {
|
|
151
|
-
self.showAttention(
|
|
152
|
-
"Pairing code unavailable",
|
|
153
|
-
detail: self.publicFailure(result.output, fallback: "SKS could not create a pairing code. Refresh status and try again.")
|
|
154
|
-
)
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
let expiration = response.expires_at.map { " It expires at \($0)." } ?? ""
|
|
158
|
-
let instruction = response.instruction
|
|
159
|
-
?? "Send /start \(code) to this bot from the intended private chat."
|
|
160
|
-
let postPair = response.post_pair_command ?? "/sks status {}"
|
|
161
|
-
let confirmation = response.confirmation_grammar ?? "/confirm <nonce>"
|
|
162
|
-
let statusGuidance = instruction.contains(postPair) ? "" : " Then try \(postPair)."
|
|
163
|
-
self.pairingDetail.stringValue = "\(instruction)\(expiration)\(statusGuidance) Confirm prompted actions with \(confirmation)."
|
|
164
|
-
self.pairingDetail.setAccessibilityValue(self.pairingDetail.stringValue)
|
|
165
|
-
self.showProgress("Pairing code ready", detail: "Use the displayed code once in the intended private chat.")
|
|
166
|
-
self.refreshStatus(after: 1)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
@objc private func startService() { runLifecycle(.start) }
|
|
171
|
-
@objc private func stopService() { runLifecycle(.stop) }
|
|
172
|
-
@objc private func restartService() { runLifecycle(.restart) }
|
|
173
|
-
@objc private func refreshStatusAction() { refreshStatus() }
|
|
174
|
-
|
|
175
|
-
private enum LifecycleAction { case start, stop, restart }
|
|
176
|
-
|
|
177
|
-
private func runLifecycle(_ action: LifecycleAction) {
|
|
178
|
-
guard !operationInFlight else { return }
|
|
179
|
-
let actionName: String
|
|
180
|
-
switch action {
|
|
181
|
-
case .start: actionName = "Starting"
|
|
182
|
-
case .stop: actionName = "Stopping"
|
|
183
|
-
case .restart: actionName = "Restarting"
|
|
184
|
-
}
|
|
185
|
-
beginOperation("\(actionName) Telegram polling…")
|
|
186
|
-
|
|
187
|
-
switch action {
|
|
188
|
-
case .stop:
|
|
189
|
-
let task = telegramService.stop()
|
|
190
|
-
Task { @MainActor [weak self] in
|
|
191
|
-
await task.value
|
|
192
|
-
self?.endOperation()
|
|
193
|
-
self?.showProgress(
|
|
194
|
-
"Telegram polling stopped",
|
|
195
|
-
detail: "The token and paired chats were preserved. Relaunching the SKS companion starts polling again."
|
|
196
|
-
)
|
|
197
|
-
self?.refreshStatus(after: 0.2)
|
|
198
|
-
}
|
|
199
|
-
case .start, .restart:
|
|
200
|
-
let task = action == .start ? telegramService.start() : telegramService.restart()
|
|
201
|
-
Task { @MainActor [weak self] in
|
|
202
|
-
do {
|
|
203
|
-
let receipt = try await task.value
|
|
204
|
-
self?.endOperation()
|
|
205
|
-
self?.render(receipt)
|
|
206
|
-
} catch {
|
|
207
|
-
self?.endOperation()
|
|
208
|
-
self?.showAttention(
|
|
209
|
-
"Telegram polling did not start",
|
|
210
|
-
detail: "Refresh status for the verified blocker. Re-enter the BotFather token if identity validation failed."
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
self?.refreshStatus(after: 0.3)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
private func saveToken(_ token: String, removeWebhook: Bool) {
|
|
219
|
-
let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
220
|
-
guard validTokenShape(normalizedToken) else {
|
|
221
|
-
showAttention(
|
|
222
|
-
"Token not saved",
|
|
223
|
-
detail: "That value does not match the BotFather token format. Copy the complete token and try again."
|
|
224
|
-
)
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
beginOperation("Verifying bot identity with Telegram…")
|
|
228
|
-
var arguments = ["telegram", "setup", "--token-stdin", "--json"]
|
|
229
|
-
if TelegramPrivateFileStore.operatorEnvironmentOverrideActive() {
|
|
230
|
-
arguments.append("--operator-env-override-active")
|
|
231
|
-
}
|
|
232
|
-
if removeWebhook { arguments.append("--remove-webhook") }
|
|
233
|
-
processClient.run(
|
|
234
|
-
arguments,
|
|
235
|
-
stdin: normalizedToken + "\n",
|
|
236
|
-
environment: telegramSecretFreeEnvironment,
|
|
237
|
-
timeout: NativeView.mutationTimeout,
|
|
238
|
-
logOutput: false
|
|
239
|
-
) { [weak self] result in
|
|
240
|
-
guard let self else { return }
|
|
241
|
-
let response = self.decode(TelegramCenterSetupResponse.self, result.output)
|
|
242
|
-
guard result.code == 0,
|
|
243
|
-
let response,
|
|
244
|
-
response.ok,
|
|
245
|
-
response.getme_verified == true,
|
|
246
|
-
response.token_stored == true else {
|
|
247
|
-
self.endOperation()
|
|
248
|
-
if !removeWebhook,
|
|
249
|
-
response?.error == "telegram_webhook_configured_remove_consent_required" {
|
|
250
|
-
self.confirmWebhookRemoval()
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
if response?.error == "telegram_operator_env_override_active" {
|
|
254
|
-
self.showAttention(
|
|
255
|
-
"Environment-managed token is active",
|
|
256
|
-
detail: response?.operator_action
|
|
257
|
-
?? "Remove the operator-managed Telegram token environment override and relaunch before storing a file token."
|
|
258
|
-
)
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
|
-
if response?.partial_success == true {
|
|
262
|
-
self.showAttention(
|
|
263
|
-
"Telegram setup partially completed",
|
|
264
|
-
detail: self.partialSetupRecovery(response)
|
|
265
|
-
)
|
|
266
|
-
return
|
|
267
|
-
}
|
|
268
|
-
self.showAttention(
|
|
269
|
-
"Token not saved",
|
|
270
|
-
detail: self.publicFailure(
|
|
271
|
-
result.output,
|
|
272
|
-
fallback: "Telegram setup could not complete. Check the connection and SKS diagnostics, then try again."
|
|
273
|
-
)
|
|
274
|
-
)
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
let restart = self.telegramService.restart()
|
|
278
|
-
Task { @MainActor [weak self] in
|
|
279
|
-
do {
|
|
280
|
-
let receipt = try await restart.value
|
|
281
|
-
self?.endOperation()
|
|
282
|
-
self?.render(receipt)
|
|
283
|
-
let selectedBot = response.bot_username.map { "@\($0)" } ?? "The selected bot"
|
|
284
|
-
self?.showProgress(
|
|
285
|
-
"Bot verified and connected",
|
|
286
|
-
detail: response.bot_state_reset == true
|
|
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."
|
|
289
|
-
)
|
|
290
|
-
} catch {
|
|
291
|
-
self?.endOperation()
|
|
292
|
-
self?.showAttention(
|
|
293
|
-
"Token saved; poller needs attention",
|
|
294
|
-
detail: "The verified token was stored, but the resident poller did not restart. Refresh status for the blocker."
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
self?.refreshStatus(after: 0.3)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
private func confirmWebhookRemoval() {
|
|
303
|
-
guard let window = view.window else { return }
|
|
304
|
-
AlertFactory.confirmSheet(
|
|
305
|
-
window: window,
|
|
306
|
-
title: "Remove the existing Telegram webhook?",
|
|
307
|
-
message: "Telegram cannot run getUpdates while this bot has a webhook. Removing it changes delivery: the existing webhook endpoint will stop receiving updates and SKS long polling can start. SKS requests removal without dropping pending updates. The token was not retained; after Continue, paste it again to authorize this one change.",
|
|
308
|
-
destructive: true,
|
|
309
|
-
actionTitle: "Continue"
|
|
310
|
-
) { [weak self] confirmed in
|
|
311
|
-
guard confirmed else { return }
|
|
312
|
-
self?.promptForToken(removeWebhook: true)
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
private var telegramSecretFreeEnvironment: [String: String] {
|
|
317
|
-
["TELEGRAM_BOT_TOKEN": "", "SKS_TELEGRAM_BOT_TOKEN": ""]
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private func refreshStatus(after delay: TimeInterval = 0) {
|
|
321
|
-
guard delay > 0 else {
|
|
322
|
-
loadStatus()
|
|
323
|
-
return
|
|
324
|
-
}
|
|
325
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in self?.loadStatus() }
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
private func loadStatus() {
|
|
329
|
-
guard !operationInFlight else { return }
|
|
330
|
-
beginOperation("Refreshing secret-free Telegram status…")
|
|
331
|
-
processClient.run(
|
|
332
|
-
["telegram", "doctor", "--json"],
|
|
333
|
-
environment: telegramSecretFreeEnvironment,
|
|
334
|
-
timeout: NativeView.statusTimeout,
|
|
335
|
-
logOutput: false
|
|
336
|
-
) { [weak self] result in
|
|
337
|
-
guard let self else { return }
|
|
338
|
-
self.endOperation()
|
|
339
|
-
guard let response = self.decode(TelegramCenterDoctorResponse.self, result.output) else {
|
|
340
|
-
self.tokenConfigured = false
|
|
341
|
-
self.pollerRunning = false
|
|
342
|
-
self.showAttention("Status unavailable", detail: "SKS could not read the Telegram liveness receipt.")
|
|
343
|
-
return
|
|
344
|
-
}
|
|
345
|
-
self.render(response)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private func render(_ response: TelegramCenterDoctorResponse) {
|
|
350
|
-
tokenConfigured = response.token_configured
|
|
351
|
-
pollerRunning = response.poller.running
|
|
352
|
-
let paired = response.paired_chat_count == 1 ? "1 paired chat" : "\(response.paired_chat_count) paired chats"
|
|
353
|
-
let source = tokenSourceDescription(response.token_source)
|
|
354
|
-
if response.ok {
|
|
355
|
-
NativeView.setBadge(serviceBadge, text: "Ready", color: .systemGreen)
|
|
356
|
-
serviceDetail.stringValue = "Bot identity verified · \(source) · \(paired) · poller running · audit healthy."
|
|
357
|
-
} else if response.self_heal_action == "operator_remove_webhook"
|
|
358
|
-
|| response.poller.last_error?.range(of: "409|webhook", options: .regularExpression) != nil {
|
|
359
|
-
NativeView.setBadge(serviceBadge, text: "Webhook blocks polling", color: .systemOrange)
|
|
360
|
-
serviceDetail.stringValue = "This bot has an active Telegram webhook, so getUpdates polling cannot run. Choose Enter Bot Token again; SKS will explain the delivery change and request explicit consent before removing the webhook without dropping pending updates."
|
|
361
|
-
} else if !response.token_configured {
|
|
362
|
-
NativeView.setBadge(serviceBadge, text: "Bot token required", color: .systemOrange)
|
|
363
|
-
serviceDetail.stringValue = "Create a bot with @BotFather, then enter and verify its HTTP API token."
|
|
364
|
-
} else if !response.bot_identity_valid {
|
|
365
|
-
NativeView.setBadge(serviceBadge, text: "Bot identity not verified", color: .systemRed)
|
|
366
|
-
serviceDetail.stringValue = "Telegram could not validate the stored bot identity. Enter the current BotFather token again."
|
|
367
|
-
} else if !response.audit_healthy {
|
|
368
|
-
NativeView.setBadge(serviceBadge, text: "Audit unavailable", color: .systemRed)
|
|
369
|
-
serviceDetail.stringValue = "Remote control is stopped because its local audit ledger is unavailable. Repair local SKS permissions before restarting."
|
|
370
|
-
} else if response.paired_chat_count == 0 {
|
|
371
|
-
NativeView.setBadge(serviceBadge, text: "Private chat not paired", color: .systemOrange)
|
|
372
|
-
serviceDetail.stringValue = "Bot verified · \(source) · no paired chats · \(response.poller.running ? "poller running" : "poller stopped"). Generate a pairing code and use it once in the intended private chat."
|
|
373
|
-
} else if !response.poller.running {
|
|
374
|
-
NativeView.setBadge(serviceBadge, text: "Poller stopped", color: .systemOrange)
|
|
375
|
-
serviceDetail.stringValue = "Bot identity verified · \(source) · \(paired) · poller stopped. Choose Start to resume."
|
|
376
|
-
} else {
|
|
377
|
-
NativeView.setBadge(serviceBadge, text: "Needs attention", color: .systemOrange)
|
|
378
|
-
serviceDetail.stringValue = "Bot identity verified · \(paired) · poller degraded after \(response.poller.consecutive_failures) consecutive failures. Refresh status or Restart."
|
|
379
|
-
}
|
|
380
|
-
serviceDetail.setAccessibilityValue(serviceDetail.stringValue)
|
|
381
|
-
updateControls()
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private func render(_ receipt: TelegramLivenessReceipt) {
|
|
385
|
-
tokenConfigured = receipt.token_configured
|
|
386
|
-
pollerRunning = receipt.running && receipt.poller.running
|
|
387
|
-
let paired = receipt.paired_chat_count == 1 ? "1 paired chat" : "\(receipt.paired_chat_count) paired chats"
|
|
388
|
-
let source = tokenSourceDescription(receipt.token_source.rawValue)
|
|
389
|
-
NativeView.setBadge(
|
|
390
|
-
serviceBadge,
|
|
391
|
-
text: pollerRunning ? "Running" : "Stopped",
|
|
392
|
-
color: pollerRunning ? .systemGreen : .systemOrange
|
|
393
|
-
)
|
|
394
|
-
serviceDetail.stringValue = "Bot identity \(receipt.bot_identity_valid ? "verified" : "not verified") · \(source) · \(paired) · poller \(pollerRunning ? "running" : "stopped") · audit \(receipt.audit_healthy == true ? "healthy" : "unavailable")."
|
|
395
|
-
serviceDetail.setAccessibilityValue(serviceDetail.stringValue)
|
|
396
|
-
updateControls()
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private func beginOperation(_ detail: String) {
|
|
400
|
-
operationInFlight = true
|
|
401
|
-
serviceSpinner.startAnimation(nil)
|
|
402
|
-
serviceDetail.stringValue = detail
|
|
403
|
-
updateControls()
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
private func endOperation() {
|
|
407
|
-
operationInFlight = false
|
|
408
|
-
serviceSpinner.stopAnimation(nil)
|
|
409
|
-
updateControls()
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
private func showProgress(_ title: String, detail: String) {
|
|
413
|
-
NativeView.setBadge(serviceBadge, text: title, color: .systemBlue)
|
|
414
|
-
serviceDetail.stringValue = detail
|
|
415
|
-
serviceDetail.setAccessibilityValue(detail)
|
|
416
|
-
updateControls()
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
private func showAttention(_ title: String, detail: String) {
|
|
420
|
-
NativeView.setBadge(serviceBadge, text: title, color: .systemOrange)
|
|
421
|
-
serviceDetail.stringValue = detail
|
|
422
|
-
serviceDetail.setAccessibilityValue(detail)
|
|
423
|
-
updateControls()
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private func updateControls() {
|
|
427
|
-
setupButton.isEnabled = !operationInFlight
|
|
428
|
-
pairButton.isEnabled = !operationInFlight && tokenConfigured && pollerRunning
|
|
429
|
-
startButton.isEnabled = !operationInFlight && tokenConfigured && !pollerRunning
|
|
430
|
-
stopButton.isEnabled = !operationInFlight && pollerRunning
|
|
431
|
-
restartButton.isEnabled = !operationInFlight && tokenConfigured
|
|
432
|
-
refreshButton.isEnabled = !operationInFlight
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
private func validTokenShape(_ value: String) -> Bool {
|
|
436
|
-
value.utf8.count <= 1024
|
|
437
|
-
&& value.range(of: Self.tokenPattern, options: .regularExpression) != nil
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
private func decode<T: Decodable>(_ type: T.Type, _ output: String) -> T? {
|
|
441
|
-
guard let data = output.data(using: .utf8) else { return nil }
|
|
442
|
-
return try? JSONDecoder().decode(type, from: data)
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private func publicFailure(_ output: String, fallback: String) -> String {
|
|
446
|
-
guard let data = output.data(using: .utf8),
|
|
447
|
-
let row = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
448
|
-
let error = row["error"] as? String else { return fallback }
|
|
449
|
-
switch error {
|
|
450
|
-
case "telegram_token_invalid", "telegram_token_stdin_empty":
|
|
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."
|
|
460
|
-
case let value where value.contains("401") || value.contains("Unauthorized"):
|
|
461
|
-
return "Telegram rejected this token. Copy the current token from @BotFather and try again."
|
|
462
|
-
case let value where value.contains("timeout") || value.contains("network"):
|
|
463
|
-
return "Telegram identity verification could not complete. Check the network connection and try again."
|
|
464
|
-
default:
|
|
465
|
-
return fallback
|
|
466
|
-
}
|
|
467
42
|
}
|
|
468
43
|
|
|
469
|
-
private func
|
|
470
|
-
|
|
471
|
-
if response?.webhook_removed == true {
|
|
472
|
-
parts.append("The existing webhook was removed without dropping pending updates.")
|
|
473
|
-
}
|
|
474
|
-
if response?.bot_state_reset == true {
|
|
475
|
-
parts.append("Bot-scoped pairing and poll state were reset safely.")
|
|
476
|
-
}
|
|
477
|
-
if response?.token_stored == false {
|
|
478
|
-
parts.append("The token was not stored; rerun secure setup to finish.")
|
|
479
|
-
}
|
|
480
|
-
if let note = response?.recovery?.note, !note.isEmpty { parts.append(note) }
|
|
481
|
-
return parts.isEmpty
|
|
482
|
-
? "Review the secret-free recovery status, then rerun secure setup."
|
|
483
|
-
: parts.joined(separator: " ")
|
|
44
|
+
@objc private func openWebsite() {
|
|
45
|
+
NSWorkspace.shared.open(Self.websiteURL)
|
|
484
46
|
}
|
|
485
47
|
|
|
486
|
-
private func
|
|
487
|
-
|
|
488
|
-
case "env": return "operator environment token"
|
|
489
|
-
case "user_secret_file": return "private token file"
|
|
490
|
-
case "none": return "no token source"
|
|
491
|
-
default: return "token source unknown"
|
|
492
|
-
}
|
|
48
|
+
@objc private func openDocs() {
|
|
49
|
+
NSWorkspace.shared.open(Self.docsURL)
|
|
493
50
|
}
|
|
494
51
|
}
|
|
@@ -51,43 +51,6 @@ enum SecureProcessEnvelope {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
if sourceSchema == "sks.telegram-setup-command.v1" {
|
|
55
|
-
for key in [
|
|
56
|
-
"getme_verified", "token_stored", "partial_success",
|
|
57
|
-
"storage_attempted", "webhook_removed", "pending_updates_dropped",
|
|
58
|
-
"bot_rotated", "bot_state_reset", "restart_required"
|
|
59
|
-
] {
|
|
60
|
-
if let value = object[key] as? Bool { envelope[key] = value }
|
|
61
|
-
}
|
|
62
|
-
if let source = object["token_source"] as? String,
|
|
63
|
-
["env", "user_secret_file", "none", "unchanged"].contains(source) {
|
|
64
|
-
envelope["token_source"] = source
|
|
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
|
-
}
|
|
78
|
-
if let action = boundedString(object["operator_action"], limit: 480) {
|
|
79
|
-
envelope["operator_action"] = action
|
|
80
|
-
}
|
|
81
|
-
if let recovery = object["recovery"] as? [String: Any] {
|
|
82
|
-
var publicRecovery: [String: String] = [:]
|
|
83
|
-
for key in ["action", "command", "note"] {
|
|
84
|
-
if let value = boundedString(recovery[key], limit: 480) {
|
|
85
|
-
publicRecovery[key] = value
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if !publicRecovery.isEmpty { envelope["recovery"] = publicRecovery }
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
54
|
}
|
|
92
55
|
}
|
|
93
56
|
if !secureOk, envelope["error"] == nil {
|
|
@@ -118,12 +81,7 @@ enum SecureProcessEnvelope {
|
|
|
118
81
|
arguments[2] == "configure" {
|
|
119
82
|
return ["sks.desktop-bridge-command-result.v1"]
|
|
120
83
|
}
|
|
121
|
-
|
|
122
|
-
case ("telegram", "setup"):
|
|
123
|
-
return ["sks.telegram-setup-command.v1"]
|
|
124
|
-
default:
|
|
125
|
-
return []
|
|
126
|
-
}
|
|
84
|
+
return []
|
|
127
85
|
}
|
|
128
86
|
|
|
129
87
|
private static func recoveryFields(from object: [String: Any]) -> [String: Any] {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { assertGate, emitGate, readText } from './gate-lib.js';
|
|
3
3
|
import { createCodexAppServerV2Client, currentTimeResponse } from '../core/codex-control/codex-app-server-v2-client.js';
|
|
4
|
-
import {
|
|
4
|
+
import { CURRENT_CODEX_RUNTIME_CONTRACT } from '../core/codex-compat/codex-runtime-contract.js';
|
|
5
|
+
import { detectCodexCurrentCapability } from '../core/codex-control/codex-current-capability.js';
|
|
5
6
|
const requireReal = process.argv.includes('--require-real') || process.env.SKS_REQUIRE_CODEX_CURRENT_APP_SERVER === '1';
|
|
6
7
|
const clientSource = readText('src/core/codex-control/codex-app-server-v2-client.ts');
|
|
7
|
-
const
|
|
8
|
+
const capability = await detectCodexCurrentCapability({ root: process.cwd(), requireReal: true });
|
|
8
9
|
assertGate(clientSource.includes('resolveCodexRuntime'), 'app-server-v2 client must use the shared Codex runtime resolver');
|
|
9
10
|
assertGate(clientSource.includes('currentTime/read'), 'app-server-v2 client must implement currentTime/read server request handling');
|
|
10
11
|
assertGate(clientSource.includes("request('thread/list'"), 'app-server-v2 client must wrap native thread/list');
|
|
11
12
|
assertGate(clientSource.includes("request('thread/read'"), 'app-server-v2 client must wrap native thread/read');
|
|
12
13
|
assertGate(clientSource.includes('searchThreads'), 'app-server-v2 client must expose search over native thread list searchTerm');
|
|
13
|
-
assertGate(
|
|
14
|
-
assertGate(
|
|
15
|
-
assertGate(
|
|
16
|
-
assertGate(
|
|
14
|
+
assertGate(capability.ok, 'resolved Codex runtime must generate a compatible App Server schema', capability);
|
|
15
|
+
assertGate(capability.probe_mode === 'real-schema', 'App Server compatibility must use runtime-generated schema evidence', capability);
|
|
16
|
+
assertGate(capability.feature_states.native_thread_list_search_schema.supported, 'runtime schema must expose thread list/read/search', capability);
|
|
17
|
+
assertGate(capability.feature_states.plugin_catalog_refresh_schema.supported, 'runtime schema must expose portable plugin catalog operations', capability);
|
|
17
18
|
const deterministic = currentTimeResponse(new Date('2026-06-23T00:00:00.000Z'));
|
|
18
19
|
assertGate(deterministic.utcIso === '2026-06-23T00:00:00.000Z', 'currentTime/read UTC ISO must be deterministic');
|
|
19
20
|
assertGate(deterministic.unixTimeSeconds === 1782172800, 'currentTime/read seconds must be Unix UTC seconds');
|
|
@@ -29,7 +30,7 @@ if (requireReal) {
|
|
|
29
30
|
runtime_sha256: runtimeIdentity.sha256,
|
|
30
31
|
list_returned_object: Boolean(list && typeof list === 'object')
|
|
31
32
|
};
|
|
32
|
-
assertGate(runtimeIdentity.version ===
|
|
33
|
+
assertGate(runtimeIdentity.version === CURRENT_CODEX_RUNTIME_CONTRACT.requiredCliVersion, `app-server-v2 require-real must resolve Codex ${CURRENT_CODEX_RUNTIME_CONTRACT.requiredCliVersion}`, realProbe);
|
|
33
34
|
assertGate(realProbe.list_returned_object === true, 'app-server-v2 require-real thread/list must return an object', realProbe);
|
|
34
35
|
}
|
|
35
36
|
finally {
|
|
@@ -40,7 +41,7 @@ emitGate('codex:current:app-server-v2', {
|
|
|
40
41
|
current_time_handler: true,
|
|
41
42
|
thread_list: true,
|
|
42
43
|
thread_read: true,
|
|
43
|
-
|
|
44
|
+
generated_schema_sha256: capability.generated_schema_sha256,
|
|
44
45
|
thread_search_via_list_search_term: true,
|
|
45
46
|
real_probe: realProbe
|
|
46
47
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { assertGate, emitGate } from './gate-lib.js';
|
|
3
|
-
import {
|
|
3
|
+
import { CURRENT_CODEX_RUNTIME_CONTRACT } from '../core/codex-compat/codex-runtime-contract.js';
|
|
4
4
|
import { compareSemverLike } from '../core/codex-compat/codex-version-policy.js';
|
|
5
5
|
import { resolveOfficialCodexPackageRuntime } from '../core/codex-runtime/resolve-codex-runtime.js';
|
|
6
6
|
const resolved = await resolveOfficialCodexPackageRuntime({ requestedBy: 'codex-current-binary-identity-check' });
|
|
7
7
|
assertGate(resolved.ok && resolved.identity !== null, 'Official Codex runtime identity must resolve', resolved);
|
|
8
8
|
const identity = resolved.identity;
|
|
9
|
-
assertGate(compareSemverLike(identity.version,
|
|
9
|
+
assertGate(compareSemverLike(identity.version, CURRENT_CODEX_RUNTIME_CONTRACT.requiredCliVersion) >= 0, `Codex runtime must satisfy ${CURRENT_CODEX_RUNTIME_CONTRACT.requiredCliVersion}`, identity);
|
|
10
10
|
assertGate(identity.sha256.length === 64, 'Codex runtime identity must include SHA-256', identity);
|
|
11
11
|
assertGate(identity.trusted === true, 'Codex runtime identity must be trusted before execution', identity);
|
|
12
12
|
assertGate(identity.trust_basis === (process.platform === 'darwin'
|