sneakoscope 7.3.0 → 7.6.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 +129 -10
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +4 -1
- package/dist/cli/command-registry.js +2 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
- package/dist/cli/install-helpers-codex-lb-config.js +114 -9
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +788 -600
- package/dist/cli/router.js +30 -2
- package/dist/commands/codex-app.js +37 -1
- package/dist/commands/codex-lb.js +491 -300
- package/dist/config/skills-manifest.json +57 -71
- package/dist/core/align/align-route.js +127 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
- package/dist/core/codex-app/codex-app-restart.js +83 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
- package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
- package/dist/core/codex-app/menubar/constants.js +5 -0
- package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
- package/dist/core/codex-app/menubar/installer.js +18 -0
- package/dist/core/codex-app/multi-provider-router.js +22 -10
- package/dist/core/codex-app/openrouter-activate.js +26 -1
- package/dist/core/codex-app.js +118 -26
- package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
- package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/capability-runner.js +204 -0
- package/dist/core/codex-lb/capability-types.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +9 -13
- package/dist/core/codex-lb/codex-lb-setup.js +45 -8
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
- package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
- package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
- package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
- package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
- package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
- package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
- package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
- package/dist/core/codex-lb/desktop-controller.js +1410 -0
- package/dist/core/codex-lb/desktop-mode.js +32 -0
- package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
- package/dist/core/codex-lb/desktop-service.js +613 -0
- package/dist/core/codex-lb/legacy-migration.js +575 -0
- package/dist/core/codex-lb/migration-receipt.js +246 -0
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
- package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
- package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
- package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
- package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
- package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
- package/dist/core/codex-native/core-skill-manifest.js +22 -6
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
- package/dist/core/commands/align-command.js +220 -0
- package/dist/core/commands/image-ux-review-command.js +280 -68
- package/dist/core/commands/naruto-command.js +257 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +402 -34
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/doctor/skill-legacy-surface.js +1 -3
- package/dist/core/errors/message.js +3 -0
- package/dist/core/evidence/evidence-freshness.js +14 -0
- package/dist/core/feature-fixtures.js +9 -1
- package/dist/core/feature-registry.js +5 -3
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/hook-context.js +15 -2
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
- package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +33 -89
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +230 -12
- package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
- package/dist/core/imagegen/imagegen-capability.js +10 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +25 -13
- package/dist/core/init.js +1 -1
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/perf/release-latency-slo.js +12 -6
- package/dist/core/pipeline-internals/runtime-core.js +78 -0
- package/dist/core/pipeline-internals/runtime-gates.js +5 -2
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/preflight/parallel-preflight-engine.js +1 -1
- package/dist/core/proof/auto-finalize.js +98 -6
- package/dist/core/proof/route-proof-gate.js +12 -1
- package/dist/core/proof/route-proof-policy.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/release-proof-io.js +1 -3
- package/dist/core/release/release-real-contract.js +5 -4
- package/dist/core/release/release-stamp-proof.js +1 -3
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/remote/audit-idempotency.js +50 -18
- package/dist/core/remote/codex-public-activity.js +283 -0
- package/dist/core/remote/index.js +1 -0
- package/dist/core/remote/ssh-worker-client.js +4 -1
- package/dist/core/remote/worker.js +206 -27
- package/dist/core/research/research-adversarial-review.js +1 -3
- package/dist/core/research/research-claim-synthesizer.js +1 -3
- package/dist/core/research/research-falsification-runner.js +1 -3
- package/dist/core/research/research-source-evidence.js +1 -3
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +2 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +30 -10
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/secret-redaction.js +6 -0
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +85 -10
- package/dist/core/telegram/center-snapshot.js +718 -0
- package/dist/core/telegram/config.js +158 -31
- package/dist/core/telegram/hub.js +288 -33
- package/dist/core/telegram/index.js +2 -0
- package/dist/core/telegram/ingress-spool.js +419 -0
- package/dist/core/telegram/ledgers.js +188 -29
- package/dist/core/telegram/messages.js +26 -6
- package/dist/core/telegram/owner-lock.js +10 -7
- package/dist/core/telegram/runtime-projection.js +38 -3
- package/dist/core/telegram/runtime.js +298 -63
- package/dist/core/telegram/service.js +137 -8
- package/dist/core/telegram/setup.js +291 -73
- package/dist/core/text/strings.js +3 -0
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
- package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
- package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
- package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
- package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
- package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
- package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
- package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
- package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +1 -1
- package/dist/scripts/helper-dedup-check.js +0 -2
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +21 -4
- package/dist/scripts/release-real-check.js +7 -0
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +8 -3
- package/release-gates.v2.json +176 -1
- package/runtime-required-scripts.json +30 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
- package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import Cocoa
|
|
2
|
+
import Darwin
|
|
3
|
+
|
|
4
|
+
struct TelegramHubServiceSnapshot: Equatable {
|
|
5
|
+
let running: Bool
|
|
6
|
+
let receiveReady: Bool
|
|
7
|
+
let mode: String?
|
|
8
|
+
let pid: Int32?
|
|
9
|
+
|
|
10
|
+
var verifiedDirectPID: Int32? {
|
|
11
|
+
guard running, mode == "direct" else { return nil }
|
|
12
|
+
return pid
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
final class TelegramHubSupervisor {
|
|
17
|
+
private static let failureStore = TelegramHubStartupFailureStore()
|
|
18
|
+
|
|
19
|
+
static var latestStartupFailureMessage: String? {
|
|
20
|
+
failureStore.message
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private let processClient: ProcessClient
|
|
24
|
+
private let actionScript: String
|
|
25
|
+
private let projectRoot: String
|
|
26
|
+
private var process: Process?
|
|
27
|
+
private var generation = 0
|
|
28
|
+
private var intentionalStop = false
|
|
29
|
+
private var startupReady = false
|
|
30
|
+
private var startupRetryCount = 0
|
|
31
|
+
private var startupTerminationReason: String?
|
|
32
|
+
private var unexpectedRestartCount = 0
|
|
33
|
+
private let predecessorWaitDelays: [TimeInterval] = [0.25, 0.5, 1, 1, 2, 2]
|
|
34
|
+
private let startupProbeDelays: [TimeInterval] = [0.25, 0.5, 1, 1, 2, 2, 3, 3]
|
|
35
|
+
private let startupRetryDelays: [TimeInterval] = [0.5, 1.5, 3]
|
|
36
|
+
private let restartDelays: [TimeInterval] = [1, 5, 15]
|
|
37
|
+
|
|
38
|
+
init(processClient: ProcessClient, actionScript: String, projectRoot: String) {
|
|
39
|
+
self.processClient = processClient
|
|
40
|
+
self.actionScript = actionScript
|
|
41
|
+
self.projectRoot = projectRoot
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func startIfConfigured() {
|
|
45
|
+
let environment = ProcessInfo.processInfo.environment
|
|
46
|
+
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
47
|
+
let globalRoot = environment["SKS_GLOBAL_ROOT"].map { URL(fileURLWithPath: $0, isDirectory: true) }
|
|
48
|
+
?? home.appendingPathComponent(".sneakoscope-global", isDirectory: true)
|
|
49
|
+
let config = globalRoot
|
|
50
|
+
.appendingPathComponent("telegram", isDirectory: true)
|
|
51
|
+
.appendingPathComponent("config.json")
|
|
52
|
+
guard FileManager.default.fileExists(atPath: config.path) else { return }
|
|
53
|
+
start()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func start(completion: ((Bool) -> Void)? = nil) {
|
|
57
|
+
onMain {
|
|
58
|
+
self.unexpectedRestartCount = 0
|
|
59
|
+
self.startupRetryCount = 0
|
|
60
|
+
self.beginStart(completion: completion)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func stop(completion: ((Bool) -> Void)? = nil) {
|
|
65
|
+
onMain {
|
|
66
|
+
self.generation += 1
|
|
67
|
+
self.intentionalStop = true
|
|
68
|
+
self.startupReady = false
|
|
69
|
+
self.startupTerminationReason = nil
|
|
70
|
+
Self.failureStore.message = nil
|
|
71
|
+
let requestGeneration = self.generation
|
|
72
|
+
self.terminateOwnedProcess { ownedStopped in
|
|
73
|
+
guard self.generation == requestGeneration else { return }
|
|
74
|
+
self.stopLaunchAgent { result in
|
|
75
|
+
guard self.generation == requestGeneration else { return }
|
|
76
|
+
self.intentionalStop = false
|
|
77
|
+
completion?(ownedStopped && result.stopped)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func restart(completion: ((Bool) -> Void)? = nil) {
|
|
84
|
+
stop { [weak self] stopped in
|
|
85
|
+
guard let self = self, stopped else {
|
|
86
|
+
completion?(false)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
self.start(completion: completion)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func shutdown() {
|
|
94
|
+
onMain {
|
|
95
|
+
self.generation += 1
|
|
96
|
+
self.intentionalStop = true
|
|
97
|
+
self.startupReady = false
|
|
98
|
+
self.startupTerminationReason = nil
|
|
99
|
+
guard let process = self.process, process.isRunning else { return }
|
|
100
|
+
let pid = process.processIdentifier
|
|
101
|
+
process.terminate()
|
|
102
|
+
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 2) {
|
|
103
|
+
if process.isRunning { Darwin.kill(pid, SIGKILL) }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private func beginStart(completion: ((Bool) -> Void)?) {
|
|
109
|
+
if process?.isRunning == true {
|
|
110
|
+
completion?(startupReady)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
generation += 1
|
|
114
|
+
intentionalStop = false
|
|
115
|
+
startupReady = false
|
|
116
|
+
startupTerminationReason = nil
|
|
117
|
+
let requestGeneration = generation
|
|
118
|
+
stopLaunchAgent { [weak self] result in
|
|
119
|
+
guard let self = self, self.generation == requestGeneration else { return }
|
|
120
|
+
if result.stopped {
|
|
121
|
+
self.waitForVerifiedPredecessorRelease(
|
|
122
|
+
generation: requestGeneration,
|
|
123
|
+
attempt: 0,
|
|
124
|
+
completion: completion
|
|
125
|
+
)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if result.snapshot?.verifiedDirectPID != nil {
|
|
129
|
+
self.waitForVerifiedPredecessorRelease(
|
|
130
|
+
generation: requestGeneration,
|
|
131
|
+
attempt: 0,
|
|
132
|
+
completion: completion
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
self.finishStartupFailure("telegram_hub_launch_agent_stop_failed", completion: completion)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private func waitForVerifiedPredecessorRelease(
|
|
141
|
+
generation requestGeneration: Int,
|
|
142
|
+
attempt: Int,
|
|
143
|
+
completion: ((Bool) -> Void)?
|
|
144
|
+
) {
|
|
145
|
+
hubStatus { [weak self] _, snapshot in
|
|
146
|
+
guard let self = self, self.generation == requestGeneration else { return }
|
|
147
|
+
guard snapshot?.verifiedDirectPID != nil else {
|
|
148
|
+
self.launchDirect(generation: requestGeneration, completion: completion)
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
guard attempt < self.predecessorWaitDelays.count else {
|
|
152
|
+
self.finishStartupFailure("telegram_hub_predecessor_handoff_timeout", completion: completion)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
let delay = self.predecessorWaitDelays[attempt]
|
|
156
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
|
157
|
+
guard let self = self,
|
|
158
|
+
self.generation == requestGeneration,
|
|
159
|
+
!self.intentionalStop else { return }
|
|
160
|
+
self.waitForVerifiedPredecessorRelease(
|
|
161
|
+
generation: requestGeneration,
|
|
162
|
+
attempt: attempt + 1,
|
|
163
|
+
completion: completion
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func launchDirect(generation requestGeneration: Int, completion: ((Bool) -> Void)?) {
|
|
170
|
+
let process = Process()
|
|
171
|
+
let output = Pipe()
|
|
172
|
+
process.executableURL = URL(fileURLWithPath: actionScript)
|
|
173
|
+
process.arguments = [
|
|
174
|
+
"telegram", "hub", "run",
|
|
175
|
+
"--project-root", projectRoot,
|
|
176
|
+
"--center-supervised",
|
|
177
|
+
"--center-supervisor-pid", String(getpid()),
|
|
178
|
+
"--json"
|
|
179
|
+
]
|
|
180
|
+
process.currentDirectoryURL = FileManager.default.homeDirectoryForCurrentUser
|
|
181
|
+
process.standardInput = FileHandle.nullDevice
|
|
182
|
+
process.standardOutput = output
|
|
183
|
+
process.standardError = output
|
|
184
|
+
process.terminationHandler = { [weak self, weak process] _ in
|
|
185
|
+
DispatchQueue.global(qos: .utility).async {
|
|
186
|
+
let data = output.fileHandleForReading.readDataToEndOfFile()
|
|
187
|
+
let bounded = Data(data.suffix(32 * 1024))
|
|
188
|
+
let text = String(data: bounded, encoding: .utf8) ?? ""
|
|
189
|
+
DispatchQueue.main.async {
|
|
190
|
+
guard let self = self,
|
|
191
|
+
let process = process,
|
|
192
|
+
self.process === process,
|
|
193
|
+
self.generation == requestGeneration else { return }
|
|
194
|
+
self.handleDirectTermination(
|
|
195
|
+
output: text,
|
|
196
|
+
generation: requestGeneration,
|
|
197
|
+
completion: completion
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
do {
|
|
203
|
+
try process.run()
|
|
204
|
+
output.fileHandleForWriting.closeFile()
|
|
205
|
+
self.process = process
|
|
206
|
+
pollStartupReadiness(
|
|
207
|
+
for: process,
|
|
208
|
+
generation: requestGeneration,
|
|
209
|
+
attempt: 0,
|
|
210
|
+
completion: completion
|
|
211
|
+
)
|
|
212
|
+
} catch {
|
|
213
|
+
finishOrRetryStartup(
|
|
214
|
+
reason: "telegram_hub_process_launch_failed",
|
|
215
|
+
generation: requestGeneration,
|
|
216
|
+
completion: completion
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private func pollStartupReadiness(
|
|
222
|
+
for process: Process,
|
|
223
|
+
generation requestGeneration: Int,
|
|
224
|
+
attempt: Int,
|
|
225
|
+
completion: ((Bool) -> Void)?
|
|
226
|
+
) {
|
|
227
|
+
guard self.process === process, process.isRunning else { return }
|
|
228
|
+
hubStatus { [weak self, weak process] _, snapshot in
|
|
229
|
+
guard let self = self,
|
|
230
|
+
let process = process,
|
|
231
|
+
self.process === process,
|
|
232
|
+
self.generation == requestGeneration,
|
|
233
|
+
process.isRunning else { return }
|
|
234
|
+
if snapshot?.running == true,
|
|
235
|
+
snapshot?.receiveReady == true,
|
|
236
|
+
snapshot?.mode == "direct",
|
|
237
|
+
snapshot?.pid == process.processIdentifier {
|
|
238
|
+
self.startupReady = true
|
|
239
|
+
self.startupRetryCount = 0
|
|
240
|
+
Self.failureStore.message = nil
|
|
241
|
+
completion?(true)
|
|
242
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self, weak process] in
|
|
243
|
+
guard let self = self,
|
|
244
|
+
let process = process,
|
|
245
|
+
self.process === process,
|
|
246
|
+
process.isRunning else { return }
|
|
247
|
+
self.unexpectedRestartCount = 0
|
|
248
|
+
}
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
guard attempt < self.startupProbeDelays.count else {
|
|
252
|
+
self.startupTerminationReason = "telegram_hub_startup_timeout"
|
|
253
|
+
self.terminateOwnedStartupProcess(process)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
let delay = self.startupProbeDelays[attempt]
|
|
257
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self, weak process] in
|
|
258
|
+
guard let self = self,
|
|
259
|
+
let process = process,
|
|
260
|
+
self.process === process,
|
|
261
|
+
self.generation == requestGeneration,
|
|
262
|
+
process.isRunning else { return }
|
|
263
|
+
self.pollStartupReadiness(
|
|
264
|
+
for: process,
|
|
265
|
+
generation: requestGeneration,
|
|
266
|
+
attempt: attempt + 1,
|
|
267
|
+
completion: completion
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private func handleDirectTermination(
|
|
274
|
+
output: String,
|
|
275
|
+
generation requestGeneration: Int,
|
|
276
|
+
completion: ((Bool) -> Void)?
|
|
277
|
+
) {
|
|
278
|
+
process = nil
|
|
279
|
+
let wasReady = startupReady
|
|
280
|
+
startupReady = false
|
|
281
|
+
let forcedReason = startupTerminationReason
|
|
282
|
+
startupTerminationReason = nil
|
|
283
|
+
guard !intentionalStop else { return }
|
|
284
|
+
if wasReady {
|
|
285
|
+
scheduleUnexpectedRestart(after: requestGeneration)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
let reason = forcedReason ?? Self.startupFailureReason(output: output)
|
|
289
|
+
finishOrRetryStartup(reason: reason, generation: requestGeneration, completion: completion)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private func finishOrRetryStartup(
|
|
293
|
+
reason: String,
|
|
294
|
+
generation requestGeneration: Int,
|
|
295
|
+
completion: ((Bool) -> Void)?
|
|
296
|
+
) {
|
|
297
|
+
guard Self.retryableStartupFailure(reason),
|
|
298
|
+
startupRetryCount < startupRetryDelays.count else {
|
|
299
|
+
finishStartupFailure(reason, completion: completion)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
let delay = startupRetryDelays[startupRetryCount]
|
|
303
|
+
startupRetryCount += 1
|
|
304
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
|
305
|
+
guard let self = self,
|
|
306
|
+
self.generation == requestGeneration,
|
|
307
|
+
!self.intentionalStop,
|
|
308
|
+
self.process?.isRunning != true else { return }
|
|
309
|
+
self.beginStart(completion: completion)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private func finishStartupFailure(_ reason: String, completion: ((Bool) -> Void)?) {
|
|
314
|
+
Self.failureStore.message = Self.startupFailureMessage(for: reason)
|
|
315
|
+
completion?(false)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private func scheduleUnexpectedRestart(after requestGeneration: Int) {
|
|
319
|
+
guard unexpectedRestartCount < restartDelays.count else {
|
|
320
|
+
finishStartupFailure("telegram_hub_unexpected_exit", completion: nil)
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
let delay = restartDelays[unexpectedRestartCount]
|
|
324
|
+
unexpectedRestartCount += 1
|
|
325
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
|
326
|
+
guard let self = self,
|
|
327
|
+
self.generation == requestGeneration,
|
|
328
|
+
!self.intentionalStop,
|
|
329
|
+
self.process?.isRunning != true else { return }
|
|
330
|
+
self.startupRetryCount = 0
|
|
331
|
+
self.beginStart(completion: nil)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private func terminateOwnedStartupProcess(_ process: Process) {
|
|
336
|
+
guard self.process === process, process.isRunning else { return }
|
|
337
|
+
let pid = process.processIdentifier
|
|
338
|
+
process.terminate()
|
|
339
|
+
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 2) {
|
|
340
|
+
if process.isRunning { Darwin.kill(pid, SIGKILL) }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private func terminateOwnedProcess(completion: @escaping (Bool) -> Void) {
|
|
345
|
+
guard let process = process, process.isRunning else {
|
|
346
|
+
self.process = nil
|
|
347
|
+
completion(true)
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
self.process = nil
|
|
351
|
+
let pid = process.processIdentifier
|
|
352
|
+
process.terminationHandler = { _ in DispatchQueue.main.async { completion(true) } }
|
|
353
|
+
process.terminate()
|
|
354
|
+
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 2) {
|
|
355
|
+
if process.isRunning { Darwin.kill(pid, SIGKILL) }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private func stopLaunchAgent(completion: @escaping (TelegramHubStopResult) -> Void) {
|
|
360
|
+
processClient.run(
|
|
361
|
+
["telegram", "hub", "stop", "--project-root", projectRoot, "--json"],
|
|
362
|
+
timeout: 12,
|
|
363
|
+
maxOutputBytes: 32 * 1024,
|
|
364
|
+
logOutput: false
|
|
365
|
+
) { result in
|
|
366
|
+
completion(TelegramHubStopResult(
|
|
367
|
+
stopped: result.code == 0,
|
|
368
|
+
snapshot: Self.serviceSnapshot(from: result.output)
|
|
369
|
+
))
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private func hubStatus(completion: @escaping (ProcessResult, TelegramHubServiceSnapshot?) -> Void) {
|
|
374
|
+
processClient.run(
|
|
375
|
+
["telegram", "hub", "status", "--project-root", projectRoot, "--json"],
|
|
376
|
+
timeout: 5,
|
|
377
|
+
maxOutputBytes: 32 * 1024,
|
|
378
|
+
logOutput: false
|
|
379
|
+
) { result in
|
|
380
|
+
completion(result, Self.serviceSnapshot(from: result.output))
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static func serviceSnapshot(from output: String) -> TelegramHubServiceSnapshot? {
|
|
385
|
+
guard let data = output.data(using: .utf8),
|
|
386
|
+
let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
387
|
+
value["schema"] as? String == "sks.telegram-hub-service.v1" else { return nil }
|
|
388
|
+
let pid = (value["pid"] as? NSNumber)?.int32Value
|
|
389
|
+
return TelegramHubServiceSnapshot(
|
|
390
|
+
running: value["running"] as? Bool == true,
|
|
391
|
+
receiveReady: value["receive_ready"] as? Bool == true,
|
|
392
|
+
mode: value["mode"] as? String,
|
|
393
|
+
pid: pid.flatMap { $0 > 0 ? $0 : nil }
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
static func startupFailureReason(output: String) -> String {
|
|
398
|
+
if output.contains("telegram_owner_conflict") { return "telegram_hub_owner_conflict" }
|
|
399
|
+
if output.contains("telegram_409_conflict") { return "telegram_hub_external_poller_conflict" }
|
|
400
|
+
if output.contains("telegram_api_timeout")
|
|
401
|
+
|| output.contains("telegram_api_transport_failed")
|
|
402
|
+
|| output.contains("telegram_poll_failed") {
|
|
403
|
+
return "telegram_hub_network_unavailable"
|
|
404
|
+
}
|
|
405
|
+
if output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
406
|
+
return "telegram_hub_empty_startup_output"
|
|
407
|
+
}
|
|
408
|
+
return "telegram_hub_startup_failed"
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
static func retryableStartupFailure(_ reason: String) -> Bool {
|
|
412
|
+
[
|
|
413
|
+
"telegram_hub_owner_conflict",
|
|
414
|
+
"telegram_hub_external_poller_conflict",
|
|
415
|
+
"telegram_hub_network_unavailable",
|
|
416
|
+
"telegram_hub_startup_timeout",
|
|
417
|
+
"telegram_hub_empty_startup_output"
|
|
418
|
+
].contains(reason)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static func startupFailureMessage(for reason: String) -> String {
|
|
422
|
+
switch reason {
|
|
423
|
+
case "telegram_hub_predecessor_handoff_timeout":
|
|
424
|
+
return "The previous Center-supervised Telegram Hub is still shutting down. Wait a few seconds, then retry. Center did not terminate an unverified owner."
|
|
425
|
+
case "telegram_hub_owner_conflict":
|
|
426
|
+
return "Another Telegram Hub still owns this bot. Stop that Hub, then retry. Center did not terminate the other owner."
|
|
427
|
+
case "telegram_hub_external_poller_conflict":
|
|
428
|
+
return "Telegram reports another active poller for this bot. Stop the other poller, then retry."
|
|
429
|
+
case "telegram_hub_network_unavailable":
|
|
430
|
+
return "Telegram Hub could not reach the Telegram API after bounded retries. Check the network, then retry."
|
|
431
|
+
case "telegram_hub_startup_timeout":
|
|
432
|
+
return "Telegram Hub did not become ready before the bounded startup deadline. Check Telegram connectivity, then retry."
|
|
433
|
+
case "telegram_hub_launch_agent_stop_failed":
|
|
434
|
+
return "The legacy Telegram Hub service could not be stopped safely. Open Diagnostics, resolve the service error, then retry."
|
|
435
|
+
case "telegram_hub_process_launch_failed":
|
|
436
|
+
return "Telegram Hub could not launch. Open Diagnostics, confirm the SKS runtime is available, then retry."
|
|
437
|
+
default:
|
|
438
|
+
return "Telegram Hub startup failed. Open Diagnostics, resolve the reported Telegram error, then retry."
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private func onMain(_ work: @escaping () -> Void) {
|
|
443
|
+
if Thread.isMainThread { work() }
|
|
444
|
+
else { DispatchQueue.main.async(execute: work) }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private struct TelegramHubStopResult {
|
|
449
|
+
let stopped: Bool
|
|
450
|
+
let snapshot: TelegramHubServiceSnapshot?
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private final class TelegramHubStartupFailureStore: @unchecked Sendable {
|
|
454
|
+
private let lock = NSLock()
|
|
455
|
+
private var value: String?
|
|
456
|
+
|
|
457
|
+
var message: String? {
|
|
458
|
+
get {
|
|
459
|
+
lock.lock()
|
|
460
|
+
defer { lock.unlock() }
|
|
461
|
+
return value
|
|
462
|
+
}
|
|
463
|
+
set {
|
|
464
|
+
lock.lock()
|
|
465
|
+
value = newValue
|
|
466
|
+
lock.unlock()
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
@@ -34,7 +34,7 @@ final class UpdatesViewController: NSViewController, ControlCenterPage {
|
|
|
34
34
|
checkButton = NativeView.button("Check Now", target: self, action: #selector(checkNow))
|
|
35
35
|
codexUpdateButton = NativeView.button("Update Codex CLI", target: self, action: #selector(updateCodexCLI))
|
|
36
36
|
codexUpdateButton.setAccessibilityHelp("Update the operator Codex CLI using its verified installation method.")
|
|
37
|
-
reviewButton = ControlKit.primaryButton("Review and Update", target: self, action: #selector(reviewAndUpdate))
|
|
37
|
+
reviewButton = ControlKit.primaryButton("Review and Update", target: self, action: #selector(reviewAndUpdate), isDefault: true)
|
|
38
38
|
reviewButton.setAccessibilityHelp("Review the staged update first, then confirm before anything is installed.")
|
|
39
39
|
codexUpdateStatus.setAccessibilityLabel("Codex CLI update result")
|
|
40
40
|
let buttons = NSStackView(views: [checkButton, codexUpdateButton, reviewButton, progress])
|
|
@@ -46,17 +46,12 @@ final class UpdatesViewController: NSViewController, ControlCenterPage {
|
|
|
46
46
|
NativeView.card(title: "Progress", subtitle: "Authoritative stage checklist from the update receipt.", views: [stageStatus]),
|
|
47
47
|
NativeView.card(title: "Recovery", subtitle: "Rollback guidance and the previous Menu Bar app remain available if final verification fails. The update receipt records the exact recovery command and stage state.", views: [remediation])
|
|
48
48
|
])
|
|
49
|
-
loadCached()
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
deinit { receiptTimer?.invalidate() }
|
|
53
52
|
|
|
54
53
|
func refreshOnAppear() { reloadSnapshot() }
|
|
55
54
|
|
|
56
|
-
private func loadCached() {
|
|
57
|
-
reloadSnapshot()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
55
|
private func setBusy(_ value: Bool) {
|
|
61
56
|
busy = value
|
|
62
57
|
checkButton?.isEnabled = !value
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#if SKS_NATIVE_TESTING
|
|
2
|
+
enum AppRuntime {
|
|
3
|
+
static let actionScript = "/tmp/sks-menubar-action.sh"
|
|
4
|
+
static let projectRoot = "/tmp/sks-menubar-project"
|
|
5
|
+
static let buildStampPath = "/tmp/sks-menubar-build-stamp.json"
|
|
6
|
+
static let configPath = "/tmp/sks-menubar-config.json"
|
|
7
|
+
static let lastActionLogPath = "/tmp/sks-menubar-last-action.log"
|
|
8
|
+
static let operationDirectory = "/tmp/sks-menubar-operations"
|
|
9
|
+
static let codexBundleId: String? = nil
|
|
10
|
+
static let packageVersion = "test"
|
|
11
|
+
}
|
|
12
|
+
#endif
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#if SKS_NATIVE_TEST_RUNNER
|
|
2
|
+
import Cocoa
|
|
3
|
+
import Darwin
|
|
4
|
+
|
|
5
|
+
@main
|
|
6
|
+
struct NativeTelegramRegressionRunner {
|
|
7
|
+
static func main() {
|
|
8
|
+
precondition(
|
|
9
|
+
ProcessClient.alignedTimeout(
|
|
10
|
+
arguments: ["telegram", "status", "--json"],
|
|
11
|
+
requested: 8
|
|
12
|
+
) == 12
|
|
13
|
+
)
|
|
14
|
+
precondition(
|
|
15
|
+
TelegramHubSupervisor.serviceSnapshot(
|
|
16
|
+
from: #"{"schema":"sks.telegram-hub-service.v1","running":true,"receive_ready":false,"mode":"direct","pid":4242}"#
|
|
17
|
+
)?.verifiedDirectPID == 4242
|
|
18
|
+
)
|
|
19
|
+
precondition(
|
|
20
|
+
TelegramHubSupervisor.serviceSnapshot(
|
|
21
|
+
from: #"{"schema":"sks.telegram-hub-service.v1","running":false,"receive_ready":false,"mode":null,"pid":null}"#
|
|
22
|
+
)?.verifiedDirectPID == nil
|
|
23
|
+
)
|
|
24
|
+
precondition(TelegramHubSupervisor.retryableStartupFailure("telegram_hub_owner_conflict"))
|
|
25
|
+
precondition(TelegramHubSupervisor.retryableStartupFailure("telegram_hub_network_unavailable"))
|
|
26
|
+
|
|
27
|
+
let temp = FileManager.default.temporaryDirectory
|
|
28
|
+
.appendingPathComponent("sks-native-telegram-regression-\(UUID().uuidString)", isDirectory: true)
|
|
29
|
+
try! FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)
|
|
30
|
+
let client = ProcessClient(
|
|
31
|
+
actionScript: "/bin/sh",
|
|
32
|
+
logPath: temp.appendingPathComponent("process.log").path,
|
|
33
|
+
projectRoot: temp.path
|
|
34
|
+
)
|
|
35
|
+
client.run(["-c", "sleep 30"], timeout: 0.2, logOutput: false) { timeoutResult in
|
|
36
|
+
precondition(timeoutResult.code == -2)
|
|
37
|
+
precondition(timeoutResult.timedOut)
|
|
38
|
+
precondition(!timeoutResult.emptyOutput)
|
|
39
|
+
precondition(timeoutResult.output.contains("native_process_timeout"))
|
|
40
|
+
client.run(["-c", "exit 0"], timeout: 2, logOutput: false) { emptyResult in
|
|
41
|
+
precondition(emptyResult.code == -3)
|
|
42
|
+
precondition(!emptyResult.timedOut)
|
|
43
|
+
precondition(emptyResult.emptyOutput)
|
|
44
|
+
precondition(emptyResult.output.contains("native_process_empty_output"))
|
|
45
|
+
let message = RemoteTelegramViewController.telegramStatusFailureMessage(emptyResult)
|
|
46
|
+
precondition(message.contains("returned no JSON"))
|
|
47
|
+
try? FileManager.default.removeItem(at: temp)
|
|
48
|
+
print("native-telegram-regressions-ok")
|
|
49
|
+
Darwin.exit(0)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
dispatchMain()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
#endif
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#if canImport(XCTest)
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
final class ProcessClientTests: XCTestCase {
|
|
5
|
+
func testTelegramStatusTimeoutLeavesRoomForInnerProbeBudget() {
|
|
6
|
+
XCTAssertEqual(
|
|
7
|
+
ProcessClient.alignedTimeout(
|
|
8
|
+
arguments: ["telegram", "status", "--json"],
|
|
9
|
+
requested: 8
|
|
10
|
+
),
|
|
11
|
+
12
|
|
12
|
+
)
|
|
13
|
+
XCTAssertEqual(
|
|
14
|
+
ProcessClient.alignedTimeout(
|
|
15
|
+
arguments: ["remote", "readiness", "--json"],
|
|
16
|
+
requested: 8
|
|
17
|
+
),
|
|
18
|
+
8
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func testNativeTimeoutAndEmptyOutputAreStablePublicErrors() {
|
|
23
|
+
XCTAssertEqual(
|
|
24
|
+
ProcessClient.nativeFailureOutput("native_process_timeout"),
|
|
25
|
+
#"{"schema":"sks.native-process-error.v1","ok":false,"error":"native_process_timeout"}"#
|
|
26
|
+
)
|
|
27
|
+
XCTAssertEqual(
|
|
28
|
+
ProcessClient.nativeFailureOutput("native_process_empty_output"),
|
|
29
|
+
#"{"schema":"sks.native-process-error.v1","ok":false,"error":"native_process_empty_output"}"#
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
#endif
|