sneakoscope 8.1.2 → 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 +96 -58
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +5 -51
- package/dist/cli/command-manifest-lite.js +2 -1
- package/dist/cli/command-registry.js +2 -1
- package/dist/cli/global-mode-router.js +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +112 -914
- package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
- package/dist/cli/install-helpers-install-support.js +4 -20
- package/dist/cli/install-helpers.js +36 -2716
- package/dist/cli/router.js +9 -9
- package/dist/commands/bridge-command-output.js +111 -0
- package/dist/commands/bridge.js +320 -0
- package/dist/commands/codex-app.js +39 -123
- package/dist/commands/doctor-helpers.js +380 -0
- package/dist/commands/doctor-profile.js +3 -0
- package/dist/commands/doctor.js +114 -873
- package/dist/commands/mad-sks.js +0 -4
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/agent-effort-policy.js +11 -21
- package/dist/core/agents/agent-recursion-guard.js +26 -0
- package/dist/core/agents/agent-runner-codex-exec.js +7 -9
- package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
- package/dist/core/architecture-hardening/state/state-service.js +49 -13
- package/dist/core/auto-review.js +15 -9
- package/dist/core/codex/codex-config-guard.js +8 -0
- package/dist/core/codex-adapter.js +8 -10
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
- package/dist/core/codex-app/codex-app-launcher.js +10 -12
- package/dist/core/codex-app/codex-app-restart.js +10 -12
- package/dist/core/codex-app/menubar/constants.js +2 -5
- package/dist/core/codex-app/menubar/global-install.js +3 -2
- package/dist/core/codex-app/menubar/installer/result.js +23 -0
- package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
- package/dist/core/codex-app/menubar/installer.js +5 -96
- package/dist/core/codex-app/menubar/migration.js +1 -42
- package/dist/core/codex-app/openrouter-provider.js +0 -78
- package/dist/core/codex-app.js +97 -360
- package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
- package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
- package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
- package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
- package/dist/core/codex-control/codex-task-runner.js +16 -60
- package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
- package/dist/core/codex-exec-output-schema.js +11 -13
- package/dist/core/codex-lb/bridge-contracts.js +2 -0
- package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
- package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
- package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
- package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
- package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
- package/dist/core/codex-lb/capability-runner.js +505 -201
- package/dist/core/codex-lb/codex-lb-env.js +179 -12
- package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
- package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
- package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
- package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
- package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
- package/dist/core/codex-lb/combined-catalog.js +12 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
- package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
- package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
- package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
- package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
- package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
- package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
- package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
- package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
- package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
- package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
- package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
- package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
- package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
- package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
- package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
- package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
- package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
- package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
- package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
- package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
- package/dist/core/codex-lb/desktop-controller.js +2 -1730
- package/dist/core/codex-lb/desktop-service.js +462 -627
- package/dist/core/codex-lb/migration-receipt.js +560 -118
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
- package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
- package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
- package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
- package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
- package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
- package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
- package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
- package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
- package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
- package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
- package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
- package/dist/core/codex-lb/provider-credentials/types.js +1 -0
- package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
- package/dist/core/codex-lb/provider-credentials.js +5 -0
- package/dist/core/codex-lb/provider-registry.js +323 -0
- package/dist/core/codex-lb/provider-route-policy.js +118 -0
- package/dist/core/codex-lb/request-route-resolver.js +172 -0
- package/dist/core/codex-lb/route-index.js +148 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
- package/dist/core/commands/basic-cli.js +1 -1
- package/dist/core/commands/check-command.js +3 -10
- package/dist/core/commands/gate-result-contract.js +10 -14
- package/dist/core/commands/intent-normalization/normalizer.js +1 -1
- package/dist/core/commands/mad-sks-command.js +2 -6
- package/dist/core/commands/release-command.js +11 -1
- package/dist/core/commands/telegram-command.js +46 -6
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
- package/dist/core/doctor/skill-legacy-surface.js +5 -5
- package/dist/core/evidence/v2/evidence-key.js +30 -3
- package/dist/core/feature-fixtures.js +8 -1
- package/dist/core/feature-registry.js +3 -2
- package/dist/core/hooks-runtime/stop-finalization.js +11 -1
- package/dist/core/hooks-runtime/subagent-context.js +15 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +73 -87
- package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
- package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
- package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
- package/dist/core/imagegen/imagegen-capability.js +43 -151
- package/dist/core/init/skills/filesystem.js +89 -0
- package/dist/core/init/skills/inventory.js +97 -0
- package/dist/core/init/skills/manifest.js +151 -0
- package/dist/core/init/skills.js +5 -325
- package/dist/core/init.js +0 -63
- package/dist/core/install/installed-package-smoke.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
- package/dist/core/policy/latest-version-guidance.js +15 -2
- package/dist/core/preflight/parallel-preflight-engine.js +27 -51
- package/dist/core/provider/provider-badge.js +9 -4
- package/dist/core/provider/provider-context.js +57 -107
- package/dist/core/providers/openrouter/openrouter-account.js +3 -1
- package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
- package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
- package/dist/core/release/gate-manifest.js +0 -1
- package/dist/core/release/main-push-guard.js +13 -0
- package/dist/core/release/main-push-receipt.js +11 -0
- package/dist/core/release/physical-release-gates.js +664 -61
- package/dist/core/release/publish-preflight.js +3 -3
- package/dist/core/release/release-gate-batch-runner.js +19 -6
- package/dist/core/release/release-gate-dag.js +33 -6
- package/dist/core/release/release-gate-node.js +1 -3
- package/dist/core/release/release-gate-output-contract.js +55 -0
- package/dist/core/release/release-pack-contract.js +1 -1
- package/dist/core/release/release-pack-receipt.js +209 -8
- package/dist/core/release/release-real-contract.js +1 -1
- package/dist/core/release/stage-publish.js +106 -28
- package/dist/core/retention/temp-lease.js +204 -0
- package/dist/core/retention.js +18 -195
- package/dist/core/routes.js +1 -2
- package/dist/core/safety/intent-contract/intent-contract.js +3 -3
- package/dist/core/subagents/naruto-host-credentials.js +6 -1
- package/dist/core/subagents/official-subagent-config.js +27 -9
- package/dist/core/subagents/official-subagent-runner.js +14 -31
- package/dist/core/subagents/wave-lifecycle.js +36 -8
- package/dist/core/subagents/wave-parent-guidance.js +2 -1
- package/dist/core/telegram/doctor.js +2 -2
- package/dist/core/telegram/liveness.js +10 -4
- package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
- package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
- package/dist/core/update/update-migration-state/session-state-split.js +247 -0
- package/dist/core/update/update-migration-state/simple-stages.js +46 -0
- package/dist/core/update/update-migration-state.js +4 -481
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
- package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
- package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
- package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
- package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
- package/dist/scripts/canonical-test-runner.js +7 -1
- package/dist/scripts/check-runtime-schemas.js +7 -2
- package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
- package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
- package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
- package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
- package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
- package/dist/scripts/desktop-bridge-transport-client.js +116 -0
- package/dist/scripts/desktop-bridge-unification-check.js +276 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
- package/dist/scripts/gate-policy-audit-check.js +52 -2
- package/dist/scripts/provider-badge-context-check.js +55 -12
- package/dist/scripts/publish-preflight.js +1 -2
- package/dist/scripts/release-affected-selector-check.js +1 -2
- package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
- package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
- package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
- package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
- package/dist/scripts/release-gate-batch-runner-check.js +9 -0
- package/dist/scripts/release-gate-dag-runner-check.js +6 -0
- package/dist/scripts/release-gate-dag-runner.js +4 -2
- package/dist/scripts/release-metadata-check.js +16 -3
- package/dist/scripts/release-pack-receipt.js +29 -6
- package/dist/scripts/release-physical-gates-check.js +39 -0
- package/dist/scripts/release-real-check.js +7 -5
- package/package.json +6 -8
- package/release-gates.v2.json +156 -157
- package/runtime-required-scripts.json +11 -11
- package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
- package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
- package/schemas/release/release-gate-node.schema.json +2 -3
- package/dist/cli/codex-lb-setup-warning-output.js +0 -17
- package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
- package/dist/commands/codex-lb.js +0 -1232
- package/dist/core/architecture-hardening/migration/migration.js +0 -111
- package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
- package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
- package/dist/core/codex-app/child-policy/child-policy.js +0 -35
- package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
- package/dist/core/codex-app/glm-model-profile.js +0 -1
- package/dist/core/codex-app/glm-profile-installer.js +0 -191
- package/dist/core/codex-app/glm-profile-schema.js +0 -42
- package/dist/core/codex-app/multi-provider-router.js +0 -391
- package/dist/core/codex-app/openrouter-activate.js +0 -405
- package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
- package/dist/core/codex-app/provider-child-policy.js +0 -64
- package/dist/core/codex-app/provider-mode.js +0 -91
- package/dist/core/codex-app/provider-session-policy.js +0 -85
- package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
- package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
- package/dist/core/codex-lb/codex-lb-setup.js +0 -293
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
- package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
- package/dist/core/codex-lb/desktop-mode.js +0 -32
- package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
- package/dist/core/codex-lb/legacy-migration.js +0 -576
- package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
- package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
- package/dist/core/codex-lb/routing-truth.js +0 -327
- package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
- package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
- package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +0 -231
|
@@ -1,151 +1,326 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
1
2
|
import net from 'node:net';
|
|
2
3
|
import tls from 'node:tls';
|
|
3
|
-
import {
|
|
4
|
+
import { buildProviderWebSocketHeaders } from './header-policy.js';
|
|
4
5
|
import { rewriteLocationHeader } from './location-rewrite.js';
|
|
5
|
-
import { resolveDesktopBridgeTarget } from './security.js';
|
|
6
|
+
import { resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, singleBridgeHeader } from './security.js';
|
|
6
7
|
import { desktopBridgeListenOrigin } from './state.js';
|
|
7
|
-
import { DesktopBridgeError } from './types.js';
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
'cookie',
|
|
12
|
-
'proxy-authenticate',
|
|
13
|
-
'proxy-authorization',
|
|
14
|
-
'set-cookie',
|
|
15
|
-
'x-codex-lb-api-key',
|
|
16
|
-
]);
|
|
8
|
+
import { DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL, DesktopBridgeError, } from './types.js';
|
|
9
|
+
const MAX_HEAD = 64 * 1024;
|
|
10
|
+
const REDACTED_RESPONSE_HEADERS = new Set(['authorization', 'cookie', 'proxy-authenticate', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-codex-lb-api-key']);
|
|
11
|
+
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
|
17
12
|
function serializeHeaders(headers) {
|
|
18
|
-
return Object.entries(headers).flatMap(([name, value]) => {
|
|
19
|
-
if (value === undefined)
|
|
20
|
-
return [];
|
|
21
|
-
if (Array.isArray(value))
|
|
22
|
-
return value.map((entry) => `${name}: ${String(entry)}`);
|
|
23
|
-
return [`${name}: ${String(value)}`];
|
|
24
|
-
});
|
|
13
|
+
return Object.entries(headers).flatMap(([name, value]) => value === undefined ? [] : Array.isArray(value) ? value.map((v) => `${name}: ${v}`) : [`${name}: ${value}`]);
|
|
25
14
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
const lines =
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
throw new DesktopBridgeError('
|
|
15
|
+
function expectedAccept(key) { return createHash('sha1').update(`${key}${WS_MAGIC}`).digest('base64'); }
|
|
16
|
+
function parseResponseHead(head) {
|
|
17
|
+
const lines = head.toString('latin1').split('\r\n');
|
|
18
|
+
const match = lines.shift()?.match(/^HTTP\/1\.[01] (\d{3})(?: |$)/);
|
|
19
|
+
if (!match)
|
|
20
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_upgrade_no_response');
|
|
21
|
+
const headers = new Map();
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
if (!line)
|
|
24
|
+
continue;
|
|
25
|
+
const colon = line.indexOf(':');
|
|
26
|
+
if (colon <= 0 || /^[ \t]/.test(line))
|
|
27
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_upgrade_no_response');
|
|
28
|
+
const name = line.slice(0, colon).toLowerCase();
|
|
29
|
+
if (headers.has(name))
|
|
30
|
+
headers.set(name, `${headers.get(name)}, ${line.slice(colon + 1).trim()}`);
|
|
31
|
+
else
|
|
32
|
+
headers.set(name, line.slice(colon + 1).trim());
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
return { status: Number(match[1]), headers };
|
|
35
|
+
}
|
|
36
|
+
function validateUpgrade(head, key, requestedProtocol) {
|
|
37
|
+
const parsed = parseResponseHead(head);
|
|
38
|
+
if (parsed.status !== 101)
|
|
39
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_upgrade_failed');
|
|
40
|
+
if (parsed.headers.get('sec-websocket-accept') !== expectedAccept(key))
|
|
41
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_accept_invalid');
|
|
42
|
+
const protocol = parsed.headers.get('sec-websocket-protocol') || null;
|
|
43
|
+
if (requestedProtocol && protocol !== requestedProtocol)
|
|
44
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_protocol_mismatch');
|
|
45
|
+
return { status: parsed.status, protocol };
|
|
46
|
+
}
|
|
47
|
+
function rewriteUpgradeResponseHead(head, providerBaseUrl, localOrigin) {
|
|
48
|
+
const lines = head.toString('latin1').split('\r\n');
|
|
49
|
+
const status = lines.shift();
|
|
50
|
+
if (!status)
|
|
51
|
+
throw new DesktopBridgeError('bridge_websocket_response_invalid');
|
|
52
|
+
const output = [status];
|
|
34
53
|
for (const line of lines) {
|
|
35
54
|
if (!line)
|
|
36
55
|
continue;
|
|
37
|
-
if (/^[ \t]/.test(line))
|
|
38
|
-
throw new DesktopBridgeError('bridge_websocket_response_invalid');
|
|
39
56
|
const colon = line.indexOf(':');
|
|
40
57
|
if (colon <= 0)
|
|
41
58
|
throw new DesktopBridgeError('bridge_websocket_response_invalid');
|
|
42
|
-
const
|
|
43
|
-
const name = rawName.toLowerCase();
|
|
59
|
+
const name = line.slice(0, colon).toLowerCase();
|
|
44
60
|
const value = line.slice(colon + 1).trim();
|
|
45
|
-
if (
|
|
61
|
+
if (REDACTED_RESPONSE_HEADERS.has(name) || name.startsWith('access-control-'))
|
|
46
62
|
continue;
|
|
47
|
-
|
|
48
|
-
output.push(`${rawName}: ${rewriteLocationHeader(value, config.remoteBaseUrl, desktopBridgeListenOrigin(config))}`);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
output.push(line);
|
|
63
|
+
output.push(name === 'location' ? `${line.slice(0, colon)}: ${rewriteLocationHeader(value, providerBaseUrl, localOrigin)}` : line);
|
|
52
64
|
}
|
|
53
65
|
return Buffer.from(`${output.join('\r\n')}\r\n\r\n`, 'latin1');
|
|
54
66
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
export async function prepareDesktopBridgeWebSocketRequest(req, config) {
|
|
68
|
+
const sessionIdentity = resolveCodexSessionIdentity(req.headers);
|
|
69
|
+
const route = await resolveAndBindDesktopBridgeRouteContext({
|
|
70
|
+
public_model: singleBridgeHeader(req.headers, 'x-sks-model') || '',
|
|
71
|
+
session_id: sessionIdentity.thread_id,
|
|
72
|
+
pathname: new URL(req.url || '/', 'http://bridge.invalid').pathname,
|
|
73
|
+
transport: 'websocket', headers: req.headers,
|
|
74
|
+
}, config);
|
|
75
|
+
const provider = config.providers[route.provider_id];
|
|
76
|
+
if (!provider)
|
|
77
|
+
throw new DesktopBridgeError('bridge_provider_route_unavailable');
|
|
78
|
+
const credential = await config.resolveProviderCredential(route.provider_id, provider.credential_generation);
|
|
79
|
+
if (credential.provider_id !== route.provider_id || credential.generation !== provider.credential_generation
|
|
80
|
+
|| (provider.credential_fingerprint && credential.fingerprint !== provider.credential_fingerprint)) {
|
|
81
|
+
throw new DesktopBridgeError('bridge_provider_credential_generation_mismatch');
|
|
82
|
+
}
|
|
83
|
+
return { route, provider, credential };
|
|
84
|
+
}
|
|
85
|
+
function writeUpgradeFailure(client) {
|
|
86
|
+
if (!client.destroyed)
|
|
87
|
+
client.end('HTTP/1.1 502 Bad Gateway\r\nContent-Type: application/json\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n{"error":{"type":"sks_bridge_error","code":"bridge_websocket_upstream_unavailable"}}');
|
|
64
88
|
}
|
|
65
|
-
export function forwardWebSocket(req,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
89
|
+
export async function forwardWebSocket(req, client, head, config, authenticatedLocalBaseUrl = desktopBridgeListenOrigin(config)) {
|
|
90
|
+
let prepared;
|
|
91
|
+
try {
|
|
92
|
+
prepared = await prepareDesktopBridgeWebSocketRequest(req, config);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
writeUpgradeFailure(client);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
const { provider, credential } = prepared;
|
|
99
|
+
const target = resolveDesktopBridgeTarget(req.url, provider.remote);
|
|
100
|
+
const upstream = provider.remote.secure
|
|
101
|
+
? tls.connect({ host: provider.remote.address, port: provider.remote.port, ...(provider.remote.tlsServername ? { servername: provider.remote.tlsServername } : {}) })
|
|
102
|
+
: net.connect({ host: provider.remote.address, port: provider.remote.port, family: provider.remote.family });
|
|
103
|
+
const key = String(req.headers['sec-websocket-key'] || '');
|
|
104
|
+
const requestedProtocol = String(req.headers['sec-websocket-protocol'] || '').split(',')[0]?.trim() || null;
|
|
78
105
|
let connected = false;
|
|
79
|
-
let
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!connected)
|
|
87
|
-
writeUpgradeFailure(clientSocket);
|
|
88
|
-
else
|
|
89
|
-
clientSocket.destroy();
|
|
90
|
-
};
|
|
106
|
+
let response = Buffer.alloc(0);
|
|
107
|
+
const timer = setTimeout(() => upstream.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout')), config.connectTimeoutMs);
|
|
108
|
+
timer.unref();
|
|
109
|
+
const fail = () => { clearTimeout(timer); if (!connected)
|
|
110
|
+
writeUpgradeFailure(client);
|
|
111
|
+
else
|
|
112
|
+
client.destroy(); };
|
|
91
113
|
const onConnected = () => {
|
|
92
114
|
connected = true;
|
|
93
|
-
clearTimeout(
|
|
115
|
+
clearTimeout(timer);
|
|
94
116
|
upstream.setNoDelay(true);
|
|
95
|
-
upstream.setTimeout(config.idleTimeoutMs, () =>
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
if (clientSocket instanceof net.Socket) {
|
|
99
|
-
clientSocket.setNoDelay(true);
|
|
100
|
-
clientSocket.setTimeout(config.idleTimeoutMs, () => clientSocket.destroy());
|
|
101
|
-
}
|
|
102
|
-
const headers = buildWebSocketHeaders(req.headers, config, target.host);
|
|
103
|
-
const requestHead = [
|
|
104
|
-
`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`,
|
|
105
|
-
...serializeHeaders(headers),
|
|
106
|
-
'',
|
|
107
|
-
'',
|
|
108
|
-
].join('\r\n');
|
|
109
|
-
upstream.write(requestHead);
|
|
117
|
+
upstream.setTimeout(config.idleTimeoutMs, () => upstream.destroy());
|
|
118
|
+
const headers = buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential }, target.host);
|
|
119
|
+
upstream.write([`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`, ...serializeHeaders(headers), '', ''].join('\r\n'));
|
|
110
120
|
if (head.length)
|
|
111
121
|
upstream.write(head);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (responseHead.length > MAX_UPGRADE_RESPONSE_HEAD_BYTES) {
|
|
122
|
+
const onData = (chunk) => {
|
|
123
|
+
response = Buffer.concat([response, chunk]);
|
|
124
|
+
if (response.length > MAX_HEAD) {
|
|
116
125
|
upstream.destroy(new DesktopBridgeError('bridge_websocket_response_headers_too_large'));
|
|
117
126
|
return;
|
|
118
127
|
}
|
|
119
|
-
const boundary =
|
|
128
|
+
const boundary = response.indexOf('\r\n\r\n');
|
|
120
129
|
if (boundary < 0)
|
|
121
130
|
return;
|
|
122
|
-
upstream.off('data',
|
|
123
|
-
const
|
|
124
|
-
const remaining =
|
|
131
|
+
upstream.off('data', onData);
|
|
132
|
+
const raw = response.subarray(0, boundary);
|
|
133
|
+
const remaining = response.subarray(boundary + 4);
|
|
125
134
|
try {
|
|
126
|
-
|
|
135
|
+
validateUpgrade(raw, key, requestedProtocol);
|
|
136
|
+
client.write(rewriteUpgradeResponseHead(raw, provider.base_url, authenticatedLocalBaseUrl));
|
|
127
137
|
if (remaining.length)
|
|
128
|
-
|
|
138
|
+
client.write(remaining);
|
|
129
139
|
}
|
|
130
140
|
catch (error) {
|
|
131
141
|
upstream.destroy(error instanceof Error ? error : undefined);
|
|
132
142
|
return;
|
|
133
143
|
}
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
client.pipe(upstream);
|
|
145
|
+
upstream.pipe(client);
|
|
136
146
|
};
|
|
137
|
-
upstream.on('data',
|
|
147
|
+
upstream.on('data', onData);
|
|
138
148
|
};
|
|
139
|
-
if (
|
|
149
|
+
if (provider.remote.secure)
|
|
140
150
|
upstream.once('secureConnect', onConnected);
|
|
141
151
|
else
|
|
142
152
|
upstream.once('connect', onConnected);
|
|
143
153
|
upstream.once('error', fail);
|
|
144
|
-
upstream.once('close', () => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
upstream.once('close', () => { clearTimeout(timer); if (!client.destroyed)
|
|
155
|
+
client.end(); });
|
|
156
|
+
client.once('error', () => upstream.destroy());
|
|
157
|
+
client.once('close', () => upstream.destroy());
|
|
158
|
+
}
|
|
159
|
+
function encodeClientFrame(opcode, payload) {
|
|
160
|
+
if (payload.length > 125)
|
|
161
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_frame_send_failed');
|
|
162
|
+
const mask = randomBytes(4);
|
|
163
|
+
const encoded = Buffer.alloc(payload.length);
|
|
164
|
+
for (let i = 0; i < payload.length; i += 1)
|
|
165
|
+
encoded[i] = (payload[i] || 0) ^ (mask[i % 4] || 0);
|
|
166
|
+
return Buffer.concat([Buffer.from([0x80 | opcode, 0x80 | payload.length]), mask, encoded]);
|
|
167
|
+
}
|
|
168
|
+
function decodeServerFrame(buffer) {
|
|
169
|
+
if (buffer.length < 2)
|
|
170
|
+
return null;
|
|
171
|
+
const length = buffer[1] & 0x7f;
|
|
172
|
+
if ((buffer[1] & 0x80) !== 0 || length === 126 || length === 127)
|
|
173
|
+
throw new DesktopBridgeError('desktop_bridge_websocket_frame_invalid');
|
|
174
|
+
if (buffer.length < 2 + length)
|
|
175
|
+
return null;
|
|
176
|
+
return { opcode: buffer[0] & 0x0f, payload: buffer.subarray(2, 2 + length), consumed: 2 + length };
|
|
177
|
+
}
|
|
178
|
+
function probeResult(input) {
|
|
179
|
+
return {
|
|
180
|
+
schema: 'sks.desktop-bridge-websocket-probe.v2', state: input.state, terminal_stage: input.terminal_stage,
|
|
181
|
+
root_cause: input.root_cause, status_code: input.status_code ?? null, negotiated_protocol: input.negotiated_protocol ?? null,
|
|
182
|
+
upgrade_verified: input.upgrade_verified === true, protocol_verified: input.protocol_verified === true,
|
|
183
|
+
frame_round_trip_verified: input.frame_round_trip_verified === true, clean_close_verified: input.clean_close_verified === true,
|
|
184
|
+
latency_ms: input.latency_ms ?? null, blockers: input.root_cause ? [input.root_cause] : [], warnings: input.warnings || [],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
async function probeOnce(options) {
|
|
188
|
+
const started = Date.now();
|
|
189
|
+
let url;
|
|
190
|
+
try {
|
|
191
|
+
url = new URL(options.url);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return probeResult({ state: 'blocked', terminal_stage: 'tcp_connect', root_cause: 'desktop_bridge_websocket_target_invalid' });
|
|
195
|
+
}
|
|
196
|
+
if (url.protocol !== 'ws:' && url.protocol !== 'wss:')
|
|
197
|
+
return probeResult({ state: 'blocked', terminal_stage: 'tcp_connect', root_cause: 'desktop_bridge_websocket_target_invalid' });
|
|
198
|
+
const key = randomBytes(16).toString('base64');
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
let settled = false;
|
|
201
|
+
let connected = false;
|
|
202
|
+
let upgraded = false;
|
|
203
|
+
let protocolVerified = false;
|
|
204
|
+
let frameVerified = false;
|
|
205
|
+
let buffer = Buffer.alloc(0);
|
|
206
|
+
const socket = url.protocol === 'wss:' ? tls.connect({ host: url.hostname, port: Number(url.port || 443), servername: url.hostname }) : net.connect({ host: url.hostname, port: Number(url.port || 80) });
|
|
207
|
+
let stageTimer;
|
|
208
|
+
const timeoutResult = () => probeResult({
|
|
209
|
+
state: 'failed',
|
|
210
|
+
terminal_stage: frameVerified ? 'clean_close' : upgraded ? 'frame_round_trip' : connected ? 'websocket_upgrade' : 'tcp_connect',
|
|
211
|
+
root_cause: frameVerified ? 'desktop_bridge_websocket_close_failed' : upgraded ? 'desktop_bridge_websocket_frame_receive_failed' : connected ? 'desktop_bridge_websocket_upgrade_no_response' : 'desktop_bridge_tcp_connect_timeout',
|
|
212
|
+
upgrade_verified: upgraded, protocol_verified: protocolVerified, frame_round_trip_verified: frameVerified,
|
|
213
|
+
});
|
|
214
|
+
const finish = (result) => { if (settled)
|
|
215
|
+
return; settled = true; clearTimeout(stageTimer); socket.destroy(); resolve({ ...result, latency_ms: Date.now() - started }); };
|
|
216
|
+
stageTimer = setTimeout(() => finish(timeoutResult()), Math.min(options.connectTimeoutMs, options.stageTimeoutMs));
|
|
217
|
+
stageTimer.unref();
|
|
218
|
+
socket.once('error', () => finish(probeResult({ state: 'failed', terminal_stage: upgraded ? 'frame_round_trip' : 'tcp_connect', root_cause: upgraded ? 'desktop_bridge_websocket_frame_receive_failed' : 'desktop_bridge_tcp_connect_failed', upgrade_verified: upgraded, protocol_verified: protocolVerified })));
|
|
219
|
+
const onConnected = () => {
|
|
220
|
+
connected = true;
|
|
221
|
+
clearTimeout(stageTimer);
|
|
222
|
+
stageTimer = setTimeout(() => finish(timeoutResult()), options.stageTimeoutMs);
|
|
223
|
+
stageTimer.unref();
|
|
224
|
+
const headers = [`GET ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`, 'Connection: Upgrade', 'Upgrade: websocket', 'Sec-WebSocket-Version: 13', `Sec-WebSocket-Key: ${key}`, `Sec-WebSocket-Protocol: ${options.protocol}`];
|
|
225
|
+
if (options.origin)
|
|
226
|
+
headers.push(`Origin: ${options.origin}`);
|
|
227
|
+
socket.write(`${headers.join('\r\n')}\r\n\r\n`);
|
|
228
|
+
};
|
|
229
|
+
if (url.protocol === 'wss:')
|
|
230
|
+
socket.once('secureConnect', onConnected);
|
|
231
|
+
else
|
|
232
|
+
socket.once('connect', onConnected);
|
|
233
|
+
socket.once('close', () => {
|
|
234
|
+
if (settled)
|
|
235
|
+
return;
|
|
236
|
+
finish(probeResult({
|
|
237
|
+
state: 'failed', terminal_stage: frameVerified ? 'clean_close' : upgraded ? 'frame_round_trip' : connected ? 'websocket_upgrade' : 'tcp_connect',
|
|
238
|
+
root_cause: frameVerified ? 'desktop_bridge_websocket_close_failed' : upgraded ? 'desktop_bridge_websocket_frame_receive_failed' : connected ? 'desktop_bridge_websocket_upgrade_no_response' : 'desktop_bridge_tcp_connect_failed',
|
|
239
|
+
upgrade_verified: upgraded, protocol_verified: protocolVerified, frame_round_trip_verified: frameVerified,
|
|
240
|
+
}));
|
|
241
|
+
});
|
|
242
|
+
socket.on('data', (chunk) => {
|
|
243
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
244
|
+
if (!upgraded) {
|
|
245
|
+
if (buffer.length > MAX_HEAD)
|
|
246
|
+
return finish(probeResult({ state: 'failed', terminal_stage: 'websocket_upgrade', root_cause: 'desktop_bridge_websocket_upgrade_no_response' }));
|
|
247
|
+
const boundary = buffer.indexOf('\r\n\r\n');
|
|
248
|
+
if (boundary < 0)
|
|
249
|
+
return;
|
|
250
|
+
try {
|
|
251
|
+
const parsed = validateUpgrade(buffer.subarray(0, boundary), key, options.protocol);
|
|
252
|
+
upgraded = true;
|
|
253
|
+
protocolVerified = parsed.protocol === options.protocol;
|
|
254
|
+
buffer = buffer.subarray(boundary + 4);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
const code = error instanceof DesktopBridgeError ? error.code : 'desktop_bridge_websocket_upgrade_no_response';
|
|
258
|
+
const stage = code.includes('protocol') ? 'websocket_protocol' : 'websocket_upgrade';
|
|
259
|
+
return finish(probeResult({ state: 'failed', terminal_stage: stage, root_cause: code }));
|
|
260
|
+
}
|
|
261
|
+
if (options.handshakeOnly)
|
|
262
|
+
return finish(probeResult({ state: options.requestedLevel === 'shallow' ? 'not_attempted' : 'degraded', terminal_stage: 'websocket_protocol', root_cause: null, status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified, frame_round_trip_verified: false, clean_close_verified: false, warnings: ['websocket_handshake_only_frame_not_attempted'] }));
|
|
263
|
+
try {
|
|
264
|
+
socket.write(encodeClientFrame(1, options.framePayload));
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return finish(probeResult({ state: 'failed', terminal_stage: 'frame_round_trip', root_cause: 'desktop_bridge_websocket_frame_send_failed', status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified }));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
while (upgraded && !settled) {
|
|
271
|
+
let frame;
|
|
272
|
+
try {
|
|
273
|
+
frame = decodeServerFrame(buffer);
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return finish(probeResult({ state: 'failed', terminal_stage: 'frame_round_trip', root_cause: 'desktop_bridge_websocket_frame_invalid', status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified }));
|
|
277
|
+
}
|
|
278
|
+
if (!frame)
|
|
279
|
+
return;
|
|
280
|
+
buffer = buffer.subarray(frame.consumed);
|
|
281
|
+
if (frame.opcode === 1 || frame.opcode === 2) {
|
|
282
|
+
if (!frame.payload.equals(options.framePayload))
|
|
283
|
+
return finish(probeResult({ state: 'failed', terminal_stage: 'frame_round_trip', root_cause: 'desktop_bridge_websocket_frame_invalid', status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified }));
|
|
284
|
+
frameVerified = true;
|
|
285
|
+
socket.write(encodeClientFrame(8, Buffer.from([0x03, 0xe8])));
|
|
286
|
+
}
|
|
287
|
+
else if (frame.opcode === 8 && frameVerified) {
|
|
288
|
+
return finish(probeResult({ state: 'verified', terminal_stage: 'complete', root_cause: null, status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified, frame_round_trip_verified: true, clean_close_verified: true }));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
148
292
|
});
|
|
149
|
-
|
|
150
|
-
|
|
293
|
+
}
|
|
294
|
+
export async function probeDesktopBridgeWebSocket(input) {
|
|
295
|
+
const maxRetries = Math.min(2, Math.max(0, input.maxRetries ?? 2));
|
|
296
|
+
const total = Math.min(30_000, Math.max(500, input.totalTimeoutMs ?? 10_000));
|
|
297
|
+
const started = Date.now();
|
|
298
|
+
const warnings = [];
|
|
299
|
+
const options = {
|
|
300
|
+
url: input.url, origin: input.origin || null, protocol: input.protocol || DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL,
|
|
301
|
+
framePayload: Buffer.isBuffer(input.framePayload) ? input.framePayload : Buffer.from(input.framePayload || 'sks-desktop-bridge-probe'),
|
|
302
|
+
handshakeOnly: input.handshakeOnly === true, requestedLevel: input.requestedLevel || 'transport', connectTimeoutMs: Math.max(100, input.connectTimeoutMs ?? 2_000),
|
|
303
|
+
stageTimeoutMs: Math.max(100, input.stageTimeoutMs ?? 2_000), totalTimeoutMs: total, maxRetries, jitter: input.jitter || Math.random,
|
|
304
|
+
};
|
|
305
|
+
let result = probeResult({ state: 'failed', terminal_stage: 'tcp_connect', root_cause: 'desktop_bridge_tcp_connect_failed' });
|
|
306
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
307
|
+
const remaining = total - (Date.now() - started);
|
|
308
|
+
if (remaining <= 0)
|
|
309
|
+
break;
|
|
310
|
+
result = await probeOnce({
|
|
311
|
+
...options,
|
|
312
|
+
connectTimeoutMs: Math.min(options.connectTimeoutMs, remaining),
|
|
313
|
+
stageTimeoutMs: Math.min(options.stageTimeoutMs, remaining),
|
|
314
|
+
});
|
|
315
|
+
if (result.root_cause === null)
|
|
316
|
+
return { ...result, warnings: [...warnings, ...result.warnings] };
|
|
317
|
+
if (attempt < maxRetries) {
|
|
318
|
+
warnings.push(`desktop_bridge_websocket_retry:${attempt + 1}:${result.root_cause || 'unknown'}`);
|
|
319
|
+
const delay = Math.min(500, 50 * (2 ** attempt) + Math.floor(Math.max(0, Math.min(1, options.jitter())) * 25));
|
|
320
|
+
if (Date.now() - started + delay >= total)
|
|
321
|
+
break;
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { ...result, blockers: result.root_cause ? [result.root_cause] : [], warnings: [...warnings, ...result.warnings] };
|
|
151
326
|
}
|