sneakoscope 9.2.5 → 9.2.6
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 +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/doctor.js +10 -2
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +33 -9
- package/dist/core/codex-lb/desktop-bridge/security.js +85 -5
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +95 -63
- package/dist/core/codex-lb/desktop-service.js +8 -2
- package/dist/core/doctor/desktop-bridge-catalog-repair.js +95 -15
- package/dist/core/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Proof-first orchestration for Codex CLI, ChatGPT Desktop, AI coding agents, mult
|
|
|
22
22
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
23
23
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
24
24
|
|
|
25
|
-
This README documents package **SKS 9.2.
|
|
25
|
+
This README documents package **SKS 9.2.6** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
|
|
26
26
|
|
|
27
27
|
Use the official latest stable SKS and Codex CLI releases. The Codex compatibility SSOT is always the **current latest stable** host; capability probes measure what that host can actually do. Product docs do not crown a fixed `0.x.y` string as SSOT (release pins and schema directories are measured artifacts for the current package, not a permanent product version claim). Menu Bar / Center induce updates to the latest stable build. Run `sks update-check` for what is installed and read the capability report for what is supported. Install SSOT is npm `sneakoscope@latest`; PATH `sks` and Menu Bar stamped generation must match that version or gates fail. It resolves managed SKS skills from the authoritative global install, preserves a runnable Naruto child slot when `max_threads=2`, and keeps Menu Bar repair transactional so stamped generations remain verifiable. Naruto uses stable opt-in multi-agent V2 when the host exposes it (Codex official multi-agent wrap-only; SKS does not reimplement a parallel runtime). Local code search is mode-separated (`sks search files|text|structure|symbol|context`); `context` is answered by the compiled TriWiki Context Graph (`context-graph.json` is exhaustive authority; `context-pack.json` and managed `AGENTS.md` are bounded projections) — see [docs/architecture/context-graph.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/architecture/context-graph.md) and [docs/PRODUCT-CONTRACT.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/PRODUCT-CONTRACT.md). See [CHANGELOG.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/CHANGELOG.md).
|
|
28
28
|
|
package/dist/commands/doctor.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getCodexInfo } from '../core/codex-adapter.js';
|
|
|
8
8
|
import { rustInfo } from '../core/rust-accelerator.js';
|
|
9
9
|
import { codexAppIntegrationStatus } from '../core/codex-app.js';
|
|
10
10
|
import { desktopBridgeStatusV3 } from '../core/codex-lb/desktop-controller-v3.js';
|
|
11
|
-
import { repairDoctorDesktopBridgeCatalog } from '../core/doctor/desktop-bridge-catalog-repair.js';
|
|
11
|
+
import { readDesktopBridgeUnreachableUpstreamEvidence, repairDoctorDesktopBridgeCatalog } from '../core/doctor/desktop-bridge-catalog-repair.js';
|
|
12
12
|
import { inspectCodexConfigReadability } from '../core/codex/codex-config-readability.js';
|
|
13
13
|
import { inspectOAuthCallbackPortConflict, oauthCallbackDoctorGuidance } from '../core/codex/oauth-callback-port-diagnostic.js';
|
|
14
14
|
import { inventoryCodexPermissionProfiles } from '../core/codex/codex-permission-profiles.js';
|
|
@@ -72,6 +72,14 @@ export async function inspectDoctorDesktopBridgeStatus(input = {}, deps = {}) {
|
|
|
72
72
|
const blockers = expected && status.readiness.ready !== true
|
|
73
73
|
? (status.readiness.blockers || []).map(String).filter(Boolean)
|
|
74
74
|
: [];
|
|
75
|
+
const recoveryActions = (status.recovery_actions || []).map(String).filter(Boolean);
|
|
76
|
+
if (expected && status.management?.managed === true) {
|
|
77
|
+
const evidence = await (deps.desktopBridgeUpstreamEvidenceImpl || readDesktopBridgeUnreachableUpstreamEvidence)(home).catch(() => null);
|
|
78
|
+
if (evidence) {
|
|
79
|
+
blockers.push(`desktop_bridge_upstream_unreachable:${evidence.code}`);
|
|
80
|
+
recoveryActions.push('Run `sks doctor --fix` (or `sks bridge repair`) to restart the Desktop Bridge and re-resolve its upstream address.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
75
83
|
return {
|
|
76
84
|
schema: 'sks.doctor-desktop-bridge.v1',
|
|
77
85
|
ok: blockers.length === 0,
|
|
@@ -82,7 +90,7 @@ export async function inspectDoctorDesktopBridgeStatus(input = {}, deps = {}) {
|
|
|
82
90
|
providers: status.providers,
|
|
83
91
|
blockers,
|
|
84
92
|
warnings: (status.readiness.warnings || []).map(String).filter(Boolean),
|
|
85
|
-
recovery_actions:
|
|
93
|
+
recovery_actions: recoveryActions
|
|
86
94
|
};
|
|
87
95
|
}
|
|
88
96
|
catch (error) {
|
|
@@ -5,7 +5,7 @@ import { pipeline } from 'node:stream/promises';
|
|
|
5
5
|
import { BRIDGE_OFFICIAL_ROUTE_ID } from '../bridge-contracts.js';
|
|
6
6
|
import { buildOfficialPassthroughHeaders, buildProviderUpstreamHeaders, rewriteResponseHeaders } from './header-policy.js';
|
|
7
7
|
import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
|
|
8
|
-
import { resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader } from './security.js';
|
|
8
|
+
import { ensureDesktopBridgeRemoteTarget, isUnreachableUpstreamError, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader } from './security.js';
|
|
9
9
|
import { desktopBridgeListenOrigin } from './state.js';
|
|
10
10
|
import { DesktopBridgeError } from './types.js';
|
|
11
11
|
const MAX_UPSTREAM_ERROR_BODY_BYTES = 1024 * 1024;
|
|
@@ -251,6 +251,8 @@ class StalePooledSocketFailure extends Error {
|
|
|
251
251
|
this.reason = reason;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
+
class UnreachableUpstreamFailure extends StalePooledSocketFailure {
|
|
255
|
+
}
|
|
254
256
|
export async function forwardHttp(req, res, config, prepared, authenticatedLocalBaseUrl = desktopBridgeListenOrigin(config)) {
|
|
255
257
|
try {
|
|
256
258
|
const request = prepared || await prepareDesktopBridgeRequest(req, config);
|
|
@@ -264,6 +266,7 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
|
|
|
264
266
|
const upstreamBaseUrl = official ? remote.baseUrl : provider.base_url;
|
|
265
267
|
if (!official && !request.credential)
|
|
266
268
|
throw new DesktopBridgeError('bridge_provider_credential_invalid');
|
|
269
|
+
await ensureDesktopBridgeRemoteTarget(remote, config.remoteLookup);
|
|
267
270
|
const target = resolveDesktopBridgeTarget(req.url, remote);
|
|
268
271
|
const transport = remote.secure ? https : http;
|
|
269
272
|
const headers = official
|
|
@@ -303,6 +306,13 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
|
|
|
303
306
|
req.once('aborted', abort);
|
|
304
307
|
res.once('close', abort);
|
|
305
308
|
upstream.once('error', (error) => {
|
|
309
|
+
if (!responseStarted && isUnreachableUpstreamError(error)) {
|
|
310
|
+
if (replayable && canReplay) {
|
|
311
|
+
finish(new UnreachableUpstreamFailure(error));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
void refreshDesktopBridgeRemoteTarget(remote, config.remoteLookup);
|
|
315
|
+
}
|
|
306
316
|
if (!responseStarted && replayable && !useFreshConnection && isStalePooledSocketFailure(upstream, error)) {
|
|
307
317
|
finish(new StalePooledSocketFailure(error));
|
|
308
318
|
return;
|
|
@@ -405,16 +415,30 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
|
|
|
405
415
|
if (!(error instanceof StalePooledSocketFailure) || remainingReplays <= 0)
|
|
406
416
|
throw error;
|
|
407
417
|
remainingReplays -= 1;
|
|
408
|
-
if (
|
|
418
|
+
if (error instanceof UnreachableUpstreamFailure) {
|
|
419
|
+
const staleAddress = remote.address;
|
|
420
|
+
await refreshDesktopBridgeRemoteTarget(remote, config.remoteLookup);
|
|
409
421
|
agent.destroy();
|
|
410
|
-
upstreamAgents.delete(`${remote.secure ? 'https' : 'http'}:${
|
|
422
|
+
upstreamAgents.delete(`${remote.secure ? 'https' : 'http'}:${staleAddress}:${remote.port}`);
|
|
423
|
+
logHttpRejection({
|
|
424
|
+
code: `bridge_upstream_unreachable_rerouted:${underlyingErrorCode(error.reason) || 'unknown'}`,
|
|
425
|
+
transport: 'http',
|
|
426
|
+
...(req.method === undefined ? {} : { method: req.method }),
|
|
427
|
+
...(req.url === undefined ? {} : { url: req.url }),
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
if (!useFreshConnection) {
|
|
432
|
+
agent.destroy();
|
|
433
|
+
upstreamAgents.delete(`${remote.secure ? 'https' : 'http'}:${remote.address}:${remote.port}`);
|
|
434
|
+
}
|
|
435
|
+
logHttpRejection({
|
|
436
|
+
code: `bridge_upstream_socket_stale_replayed:${underlyingErrorCode(error.reason) || 'unknown'}`,
|
|
437
|
+
transport: 'http',
|
|
438
|
+
...(req.method === undefined ? {} : { method: req.method }),
|
|
439
|
+
...(req.url === undefined ? {} : { url: req.url }),
|
|
440
|
+
});
|
|
411
441
|
}
|
|
412
|
-
logHttpRejection({
|
|
413
|
-
code: `bridge_upstream_socket_stale_replayed:${underlyingErrorCode(error.reason) || 'unknown'}`,
|
|
414
|
-
transport: 'http',
|
|
415
|
-
...(req.method === undefined ? {} : { method: req.method }),
|
|
416
|
-
...(req.url === undefined ? {} : { url: req.url }),
|
|
417
|
-
});
|
|
418
442
|
useFreshConnection = true;
|
|
419
443
|
const backoffIndex = TRANSIENT_UPSTREAM_REPLAY_LIMIT - remainingReplays - 1;
|
|
420
444
|
const backoffMs = TRANSIENT_UPSTREAM_REPLAY_BACKOFF_MS[Math.max(0, backoffIndex)]
|
|
@@ -7,6 +7,7 @@ const MIN_HIGH_PORT = 49_152;
|
|
|
7
7
|
const MAX_PORT = 65_535;
|
|
8
8
|
const MAX_SESSION_PINS = 10_000;
|
|
9
9
|
const sessionPinMutationQueues = new WeakMap();
|
|
10
|
+
const remoteRefreshStates = new WeakMap();
|
|
10
11
|
const FORBIDDEN_REMOTE_ADDRESSES = new net.BlockList();
|
|
11
12
|
FORBIDDEN_REMOTE_ADDRESSES.addSubnet('0.0.0.0', 8, 'ipv4');
|
|
12
13
|
FORBIDDEN_REMOTE_ADDRESSES.addSubnet('10.0.0.0', 8, 'ipv4');
|
|
@@ -369,7 +370,7 @@ const defaultLookup = async (hostname) => {
|
|
|
369
370
|
return { address: row.address, family: row.family };
|
|
370
371
|
});
|
|
371
372
|
};
|
|
372
|
-
export async function resolveDesktopBridgeRemoteTarget(raw, lookup = defaultLookup) {
|
|
373
|
+
export async function resolveDesktopBridgeRemoteTarget(raw, lookup = defaultLookup, options = {}) {
|
|
373
374
|
const remote = validateRemoteUrl(raw);
|
|
374
375
|
const hostname = stripIpv6Brackets(remote.hostname);
|
|
375
376
|
const family = net.isIP(hostname);
|
|
@@ -388,7 +389,9 @@ export async function resolveDesktopBridgeRemoteTarget(raw, lookup = defaultLook
|
|
|
388
389
|
if (!loopback && addresses.some((row) => FORBIDDEN_REMOTE_ADDRESSES.check(row.address, row.family === 4 ? 'ipv4' : 'ipv6'))) {
|
|
389
390
|
throw new DesktopBridgeError('bridge_remote_dns_private_address');
|
|
390
391
|
}
|
|
391
|
-
const selected = addresses
|
|
392
|
+
const selected = (options.preferAddress ? addresses.find((row) => row.address === options.preferAddress) : undefined)
|
|
393
|
+
?? addresses.find((row) => row.address !== options.avoidAddress)
|
|
394
|
+
?? addresses[0];
|
|
392
395
|
if (!selected)
|
|
393
396
|
throw new DesktopBridgeError('bridge_remote_dns_empty');
|
|
394
397
|
const target = {
|
|
@@ -396,8 +399,85 @@ export async function resolveDesktopBridgeRemoteTarget(raw, lookup = defaultLook
|
|
|
396
399
|
port: Number(remote.port || (remote.protocol === 'https:' ? 443 : 80)), secure: remote.protocol === 'https:',
|
|
397
400
|
address: selected.address, family: selected.family, ...(family ? {} : { tlsServername: hostname }),
|
|
398
401
|
};
|
|
402
|
+
remoteRefreshStates.set(target, { resolvedAt: Date.now(), lastRefreshAt: null, inflight: null });
|
|
399
403
|
return target;
|
|
400
404
|
}
|
|
405
|
+
function deferredRemoteTarget(remote, hostname) {
|
|
406
|
+
return {
|
|
407
|
+
baseUrl: remote.toString().replace(/\/$/, ''), origin: remote.origin, hostname,
|
|
408
|
+
port: Number(remote.port || (remote.protocol === 'https:' ? 443 : 80)), secure: remote.protocol === 'https:',
|
|
409
|
+
address: '0.0.0.0', family: 4, ...(net.isIP(hostname) ? {} : { tlsServername: hostname }),
|
|
410
|
+
unresolved: true,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
async function resolveOrDeferRemoteTarget(raw, lookup) {
|
|
414
|
+
try {
|
|
415
|
+
return await resolveDesktopBridgeRemoteTarget(raw, lookup);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
if (!(error instanceof DesktopBridgeError) || error.code !== 'bridge_remote_dns_failed')
|
|
419
|
+
throw error;
|
|
420
|
+
const remote = validateRemoteUrl(raw);
|
|
421
|
+
return deferredRemoteTarget(remote, stripIpv6Brackets(remote.hostname));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const UNREACHABLE_UPSTREAM_ERROR_CODES = new Set([
|
|
425
|
+
'EHOSTUNREACH', 'ENETUNREACH', 'ENETDOWN', 'EHOSTDOWN', 'EADDRNOTAVAIL', 'ECONNREFUSED', 'ETIMEDOUT',
|
|
426
|
+
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
427
|
+
]);
|
|
428
|
+
export function isUnreachableUpstreamError(error) {
|
|
429
|
+
if (error instanceof DesktopBridgeError)
|
|
430
|
+
return error.code === 'bridge_upstream_connect_timeout';
|
|
431
|
+
const code = error?.code;
|
|
432
|
+
return typeof code === 'string' && UNREACHABLE_UPSTREAM_ERROR_CODES.has(code);
|
|
433
|
+
}
|
|
434
|
+
export const REMOTE_REFRESH_COOLDOWN_MS = 5_000;
|
|
435
|
+
export const REMOTE_TARGET_TTL_MS = 5 * 60_000;
|
|
436
|
+
export async function refreshDesktopBridgeRemoteTarget(remote, lookup = defaultLookup, reason = 'unreachable') {
|
|
437
|
+
if (net.isIP(remote.hostname))
|
|
438
|
+
return false;
|
|
439
|
+
const state = remoteRefreshStates.get(remote) ?? { resolvedAt: null, lastRefreshAt: null, inflight: null };
|
|
440
|
+
if (state.inflight)
|
|
441
|
+
return state.inflight;
|
|
442
|
+
if (state.lastRefreshAt !== null && Date.now() - state.lastRefreshAt < REMOTE_REFRESH_COOLDOWN_MS)
|
|
443
|
+
return false;
|
|
444
|
+
const inflight = (async () => {
|
|
445
|
+
let resolvedAt = state.resolvedAt;
|
|
446
|
+
try {
|
|
447
|
+
const fresh = await resolveDesktopBridgeRemoteTarget(remote.baseUrl, lookup, reason === 'stale'
|
|
448
|
+
? { preferAddress: remote.address }
|
|
449
|
+
: reason === 'unreachable' ? { avoidAddress: remote.address } : {});
|
|
450
|
+
const changed = remote.unresolved === true || fresh.address !== remote.address || fresh.family !== remote.family;
|
|
451
|
+
remote.address = fresh.address;
|
|
452
|
+
remote.family = fresh.family;
|
|
453
|
+
delete remote.unresolved;
|
|
454
|
+
resolvedAt = Date.now();
|
|
455
|
+
return changed;
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
remoteRefreshStates.set(remote, { resolvedAt, lastRefreshAt: Date.now(), inflight: null });
|
|
462
|
+
}
|
|
463
|
+
})();
|
|
464
|
+
remoteRefreshStates.set(remote, { ...state, inflight });
|
|
465
|
+
return inflight;
|
|
466
|
+
}
|
|
467
|
+
export async function ensureDesktopBridgeRemoteTarget(remote, lookup = defaultLookup, ttlMs = REMOTE_TARGET_TTL_MS) {
|
|
468
|
+
if (remote.unresolved) {
|
|
469
|
+
await refreshDesktopBridgeRemoteTarget(remote, lookup, 'unresolved');
|
|
470
|
+
if (remote.unresolved)
|
|
471
|
+
throw new DesktopBridgeError('bridge_remote_dns_failed');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (net.isIP(remote.hostname))
|
|
475
|
+
return;
|
|
476
|
+
const state = remoteRefreshStates.get(remote);
|
|
477
|
+
if (state?.resolvedAt !== null && state?.resolvedAt !== undefined && Date.now() - state.resolvedAt >= ttlMs) {
|
|
478
|
+
await refreshDesktopBridgeRemoteTarget(remote, lookup, 'stale');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
401
481
|
async function prepareProvider(provider, lookup) {
|
|
402
482
|
const remote = validateRemoteUrl(provider.base_url);
|
|
403
483
|
const hostname = stripIpv6Brackets(remote.hostname);
|
|
@@ -414,7 +494,7 @@ async function prepareProvider(provider, lookup) {
|
|
|
414
494
|
},
|
|
415
495
|
};
|
|
416
496
|
}
|
|
417
|
-
const target = await
|
|
497
|
+
const target = await resolveOrDeferRemoteTarget(provider.base_url, lookup);
|
|
418
498
|
return { ...provider, base_url: target.baseUrl, remote: target };
|
|
419
499
|
}
|
|
420
500
|
function assertRegistryAndPolicy(config, registry) {
|
|
@@ -525,9 +605,9 @@ export async function prepareDesktopBridgeConfig(config, lookup = defaultLookup)
|
|
|
525
605
|
}));
|
|
526
606
|
const providers = Object.fromEntries(entries);
|
|
527
607
|
const officialRemote = config.officialPassthrough
|
|
528
|
-
? await
|
|
608
|
+
? await resolveOrDeferRemoteTarget(config.officialPassthrough.baseUrl, lookup)
|
|
529
609
|
: null;
|
|
530
|
-
return { ...config, providers, officialRemote };
|
|
610
|
+
return { ...config, providers, officialRemote, remoteLookup: lookup };
|
|
531
611
|
}
|
|
532
612
|
export function validatePreparedDesktopBridgeConfig(config) {
|
|
533
613
|
validateDesktopBridgeConfig(config);
|
|
@@ -5,7 +5,7 @@ import { BRIDGE_OFFICIAL_ROUTE_ID } from '../bridge-contracts.js';
|
|
|
5
5
|
import { buildOfficialPassthroughWebSocketHeaders, buildProviderWebSocketHeaders } from './header-policy.js';
|
|
6
6
|
import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
|
|
7
7
|
import { rewriteLocationHeader } from './location-rewrite.js';
|
|
8
|
-
import { desktopBridgeOfficialPassthroughEnabled, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader, canonicalSessionId } from './security.js';
|
|
8
|
+
import { desktopBridgeOfficialPassthroughEnabled, ensureDesktopBridgeRemoteTarget, isUnreachableUpstreamError, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader, canonicalSessionId } from './security.js';
|
|
9
9
|
import { desktopBridgeListenOrigin } from './state.js';
|
|
10
10
|
import { DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL, DesktopBridgeError, } from './types.js';
|
|
11
11
|
const MAX_HEAD = 64 * 1024;
|
|
@@ -152,71 +152,103 @@ export async function forwardWebSocket(req, client, head, config, authenticatedL
|
|
|
152
152
|
writeUpgradeFailure(client, new DesktopBridgeError(official ? 'bridge_official_passthrough_unavailable' : 'bridge_provider_route_unavailable'), req);
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
const pin = remote;
|
|
156
|
+
const upstreamBaseUrl = official ? pin.baseUrl : provider.base_url;
|
|
157
|
+
const target = resolveDesktopBridgeTarget(req.url, pin);
|
|
158
|
+
try {
|
|
159
|
+
await ensureDesktopBridgeRemoteTarget(pin, config.remoteLookup);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
writeUpgradeFailure(client, error, req);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
160
165
|
const key = String(req.headers['sec-websocket-key'] || '');
|
|
161
166
|
const requestedProtocol = String(req.headers['sec-websocket-protocol'] || '').split(',')[0]?.trim() || null;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
clearTimeout(timer);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
167
|
+
connectUpstreamForUpgrade(1);
|
|
168
|
+
function connectUpstreamForUpgrade(retriesLeft) {
|
|
169
|
+
const upstream = pin.secure
|
|
170
|
+
? tls.connect({ host: pin.address, port: pin.port, ...(pin.tlsServername ? { servername: pin.tlsServername } : {}) })
|
|
171
|
+
: net.connect({ host: pin.address, port: pin.port, family: pin.family });
|
|
172
|
+
let connected = false;
|
|
173
|
+
let retrying = false;
|
|
174
|
+
let response = Buffer.alloc(0);
|
|
175
|
+
const timer = setTimeout(() => upstream.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout')), config.connectTimeoutMs);
|
|
176
|
+
timer.unref();
|
|
177
|
+
const fail = (error) => { clearTimeout(timer); if (!connected)
|
|
178
|
+
writeUpgradeFailure(client, error, req);
|
|
179
|
+
else
|
|
180
|
+
client.destroy(); };
|
|
181
|
+
const onConnected = () => {
|
|
182
|
+
connected = true;
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
upstream.setNoDelay(true);
|
|
185
|
+
upstream.setKeepAlive(true, 30_000);
|
|
186
|
+
const clientSocket = client;
|
|
187
|
+
if (typeof clientSocket.setKeepAlive === 'function')
|
|
188
|
+
clientSocket.setKeepAlive(true, 30_000);
|
|
189
|
+
const headers = official
|
|
190
|
+
? buildOfficialPassthroughWebSocketHeaders(req.headers, target.host)
|
|
191
|
+
: buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential: credential }, target.host);
|
|
192
|
+
upstream.write([`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`, ...serializeHeaders(headers), '', ''].join('\r\n'));
|
|
193
|
+
if (head.length)
|
|
194
|
+
upstream.write(head);
|
|
195
|
+
const onData = (chunk) => {
|
|
196
|
+
response = Buffer.concat([response, chunk]);
|
|
197
|
+
if (response.length > MAX_HEAD) {
|
|
198
|
+
upstream.destroy(new DesktopBridgeError('bridge_websocket_response_headers_too_large'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const boundary = response.indexOf('\r\n\r\n');
|
|
202
|
+
if (boundary < 0)
|
|
203
|
+
return;
|
|
204
|
+
upstream.off('data', onData);
|
|
205
|
+
const raw = response.subarray(0, boundary);
|
|
206
|
+
const remaining = response.subarray(boundary + 4);
|
|
207
|
+
try {
|
|
208
|
+
validateUpgrade(raw, key, requestedProtocol);
|
|
209
|
+
client.write(rewriteUpgradeResponseHead(raw, upstreamBaseUrl, authenticatedLocalBaseUrl));
|
|
210
|
+
if (remaining.length)
|
|
211
|
+
client.write(remaining);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
upstream.destroy(error instanceof Error ? error : undefined);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
client.pipe(upstream);
|
|
218
|
+
upstream.pipe(client);
|
|
219
|
+
};
|
|
220
|
+
upstream.on('data', onData);
|
|
208
221
|
};
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
if (pin.secure)
|
|
223
|
+
upstream.once('secureConnect', onConnected);
|
|
224
|
+
else
|
|
225
|
+
upstream.once('connect', onConnected);
|
|
226
|
+
upstream.once('error', (error) => {
|
|
227
|
+
if (!connected && isUnreachableUpstreamError(error)) {
|
|
228
|
+
const refreshed = refreshDesktopBridgeRemoteTarget(pin, config.remoteLookup);
|
|
229
|
+
if (retriesLeft > 0 && !client.destroyed) {
|
|
230
|
+
retrying = true;
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
const cause = error?.code;
|
|
233
|
+
logWebSocketRejection({
|
|
234
|
+
code: `bridge_upstream_unreachable_rerouted:${typeof cause === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(cause) ? cause : 'unknown'}`,
|
|
235
|
+
transport: 'websocket',
|
|
236
|
+
...(req.method === undefined ? {} : { method: req.method }),
|
|
237
|
+
...(req.url === undefined ? {} : { url: req.url }),
|
|
238
|
+
});
|
|
239
|
+
void refreshed.then(() => { if (!client.destroyed)
|
|
240
|
+
connectUpstreamForUpgrade(retriesLeft - 1); });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
fail(error);
|
|
245
|
+
});
|
|
246
|
+
upstream.once('close', () => { clearTimeout(timer); if (retrying)
|
|
247
|
+
return; if (!client.destroyed)
|
|
248
|
+
client.end(); });
|
|
249
|
+
client.once('error', () => upstream.destroy());
|
|
250
|
+
client.once('close', () => upstream.destroy());
|
|
251
|
+
}
|
|
220
252
|
}
|
|
221
253
|
function encodeClientFrame(opcode, payload) {
|
|
222
254
|
if (payload.length > 125)
|
|
@@ -14,6 +14,7 @@ import { captureCodexAuthSnapshot } from './desktop-auth-invariant.js';
|
|
|
14
14
|
import { applyOfficialModelPassthrough, bridgeRoutePolicyPath, writeBridgeRoutingPolicy } from './provider-route-policy.js';
|
|
15
15
|
import { desktopBridgeRuntimeVersion, desktopBridgeRuntimeVersionStale } from './desktop-bridge/state.js';
|
|
16
16
|
import { PACKAGE_VERSION } from '../version.js';
|
|
17
|
+
import { BRIDGE_OFFICIAL_ROUTE_ID } from './bridge-contracts.js';
|
|
17
18
|
import { DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, DESKTOP_BRIDGE_LAUNCHD_LABEL, desktopBridgeConfigGeneration, desktopBridgeLaunchdPlistPath, desktopBridgeProcessExists, desktopBridgeStatePath, getDesktopBridgeStatus, preflightDesktopBridge, readDesktopBridgeState, safeBridgeErrorCode, writeDesktopBridgeLaunchdPlist, selectAvailableDesktopBridgePort, startPreparedDesktopBridge, DESKTOP_BRIDGE_STATE_SCHEMA, DESKTOP_BRIDGE_OFFICIAL_UPSTREAM_BASE_URL, DesktopBridgeError, } from './desktop-bridge/index.js';
|
|
18
19
|
export const DEFAULT_DESKTOP_BRIDGE_HOST = '127.0.0.1';
|
|
19
20
|
export const DEFAULT_DESKTOP_BRIDGE_PORT = 49_152;
|
|
@@ -483,7 +484,12 @@ export async function serveDesktopBridge(options = {}) {
|
|
|
483
484
|
await autoApplyOfficialModelsAtServe(runtime, serveHome, options);
|
|
484
485
|
const supervised = desktopBridgeIsSupervised();
|
|
485
486
|
const skewMarkerPath = path.join(path.dirname(runtime.paths.state_path), 'desktop-bridge-skew-restart.json');
|
|
486
|
-
|
|
487
|
+
const prepared = await preflightDesktopBridge(runtime.config);
|
|
488
|
+
const deferredUpstreams = [
|
|
489
|
+
...Object.values(prepared.providers).filter((provider) => provider.enabled && provider.remote.unresolved).map((provider) => provider.provider_id),
|
|
490
|
+
...(prepared.officialRemote?.unresolved ? [BRIDGE_OFFICIAL_ROUTE_ID] : []),
|
|
491
|
+
];
|
|
492
|
+
handle = await startPreparedDesktopBridge(prepared, {
|
|
487
493
|
statePath: runtime.paths.state_path,
|
|
488
494
|
versionSkew: {
|
|
489
495
|
readInstalledVersion: installedPackageVersion,
|
|
@@ -502,7 +508,7 @@ export async function serveDesktopBridge(options = {}) {
|
|
|
502
508
|
},
|
|
503
509
|
},
|
|
504
510
|
});
|
|
505
|
-
process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, secret_fields_redacted: true })}\n`);
|
|
511
|
+
process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, ...(deferredUpstreams.length ? { deferred_upstreams: deferredUpstreams } : {}), secret_fields_redacted: true })}\n`);
|
|
506
512
|
await waitForShutdown(handle);
|
|
507
513
|
return { schema: 'sks.desktop-bridge-serve.v1', ok: true, status: 'stopped', state: handle.state };
|
|
508
514
|
}
|
|
@@ -1,30 +1,110 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { desktopBridgeStatusV3, executeDesktopBridgeCommandV3 } from '../codex-lb/desktop-controller-v3.js';
|
|
4
|
-
import { bootstrapExistingDesktopBridgeService, desktopBridgeServiceStatus } from '../codex-lb/desktop-service.js';
|
|
5
|
-
import { desktopBridgeRuntimeVersion, desktopBridgeRuntimeVersionStale } from '../codex-lb/desktop-bridge/state.js';
|
|
5
|
+
import { bootstrapExistingDesktopBridgeService, desktopBridgeServicePaths, desktopBridgeServiceStatus } from '../codex-lb/desktop-service.js';
|
|
6
|
+
import { desktopBridgeRuntimeVersion, desktopBridgeRuntimeVersionStale, readDesktopBridgeState } from '../codex-lb/desktop-bridge/state.js';
|
|
6
7
|
import { PACKAGE_VERSION } from '../version.js';
|
|
7
|
-
export
|
|
8
|
-
|
|
8
|
+
export const BRIDGE_UNREACHABLE_EVIDENCE_WINDOW_MS = 10 * 60_000;
|
|
9
|
+
const BRIDGE_LOG_TAIL_BYTES = 256 * 1024;
|
|
10
|
+
const UNREACHABLE_REJECTION_CODE = /^bridge_(?:websocket_)?upstream_unavailable/;
|
|
11
|
+
const REROUTED_REJECTION_CODE = /^bridge_upstream_unreachable_rerouted/;
|
|
12
|
+
export function detectUnreachableUpstreamEvidence(logTail, startedAt, nowMs) {
|
|
13
|
+
const startedMs = startedAt ? Date.parse(startedAt) : Number.NaN;
|
|
14
|
+
const cutoffMs = Math.max(Number.isFinite(startedMs) ? startedMs : 0, nowMs - BRIDGE_UNREACHABLE_EVIDENCE_WINDOW_MS);
|
|
15
|
+
let latest = null;
|
|
16
|
+
let latestReroute = Number.NEGATIVE_INFINITY;
|
|
17
|
+
for (const line of logTail.split('\n')) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed.startsWith('{') || !trimmed.includes('"sks.desktop_bridge.rejected'))
|
|
20
|
+
continue;
|
|
21
|
+
let record;
|
|
22
|
+
try {
|
|
23
|
+
record = JSON.parse(trimmed);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (record.event !== 'sks.desktop_bridge.rejected' && record.event !== 'sks.desktop_bridge.rejected_summary')
|
|
29
|
+
continue;
|
|
30
|
+
const code = typeof record.code === 'string' ? record.code : '';
|
|
31
|
+
const unreachable = UNREACHABLE_REJECTION_CODE.test(code);
|
|
32
|
+
const rerouted = !unreachable && REROUTED_REJECTION_CODE.test(code);
|
|
33
|
+
if (!unreachable && !rerouted)
|
|
34
|
+
continue;
|
|
35
|
+
const at = typeof record.at === 'string' ? Date.parse(record.at) : Number.NaN;
|
|
36
|
+
if (!Number.isFinite(at) || at < cutoffMs)
|
|
37
|
+
continue;
|
|
38
|
+
if (rerouted) {
|
|
39
|
+
latestReroute = Math.max(latestReroute, at);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!latest || at > latest.at)
|
|
43
|
+
latest = { at, code };
|
|
44
|
+
}
|
|
45
|
+
if (!latest || latest.at <= latestReroute)
|
|
46
|
+
return null;
|
|
47
|
+
return latest.code;
|
|
48
|
+
}
|
|
49
|
+
export async function readDesktopBridgeUnreachableUpstreamEvidence(home, deps = {}) {
|
|
50
|
+
const paths = desktopBridgeServicePaths(home);
|
|
51
|
+
const state = await readDesktopBridgeState(paths.state_path).catch(() => null);
|
|
52
|
+
if (!state)
|
|
53
|
+
return null;
|
|
54
|
+
const tail = await (deps.readLogTailImpl || readBridgeLogTail)(paths.stdout_log_path, BRIDGE_LOG_TAIL_BYTES).catch(() => '');
|
|
55
|
+
const code = detectUnreachableUpstreamEvidence(tail, state.started_at, (deps.nowMs || Date.now)());
|
|
56
|
+
return code ? { code, started_at: state.started_at } : null;
|
|
57
|
+
}
|
|
58
|
+
async function readBridgeLogTail(logPath, maxBytes) {
|
|
59
|
+
const handle = await fsp.open(logPath, 'r');
|
|
60
|
+
try {
|
|
61
|
+
const { size } = await handle.stat();
|
|
62
|
+
const start = Math.max(0, size - maxBytes);
|
|
63
|
+
const buffer = Buffer.alloc(size - start);
|
|
64
|
+
await handle.read(buffer, 0, buffer.length, start);
|
|
65
|
+
return buffer.toString('utf8');
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await handle.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function restartStaleDesktopBridgeRuntime(input, deps = {}) {
|
|
72
|
+
const usingRealImpls = !deps.serviceStatusImpl && !deps.bootstrapImpl && !deps.readLogTailImpl;
|
|
73
|
+
if (usingRealImpls && (process.env.SKS_TEST_ISOLATION === '1' || process.env.SKS_RELEASE_UPGRADE_SMOKE === '1')) {
|
|
9
74
|
return { restarted: false, warnings: [], blockers: [] };
|
|
10
75
|
}
|
|
11
|
-
const service = await desktopBridgeServiceStatus({ home: input.home }).catch(() => null);
|
|
12
|
-
if (!service?.running
|
|
76
|
+
const service = await (deps.serviceStatusImpl || desktopBridgeServiceStatus)({ home: input.home }).catch(() => null);
|
|
77
|
+
if (!service?.running)
|
|
13
78
|
return { restarted: false, warnings: [], blockers: [] };
|
|
79
|
+
const versionStale = desktopBridgeRuntimeVersionStale(service.state);
|
|
80
|
+
let unreachable = null;
|
|
81
|
+
if (!versionStale) {
|
|
82
|
+
const logPath = service.paths?.stdout_log_path;
|
|
83
|
+
const tail = logPath
|
|
84
|
+
? await (deps.readLogTailImpl || readBridgeLogTail)(logPath, BRIDGE_LOG_TAIL_BYTES).catch(() => '')
|
|
85
|
+
: '';
|
|
86
|
+
unreachable = detectUnreachableUpstreamEvidence(tail, service.state?.started_at, (deps.nowMs || Date.now)());
|
|
87
|
+
if (!unreachable)
|
|
88
|
+
return { restarted: false, warnings: [], blockers: [] };
|
|
14
89
|
}
|
|
15
90
|
const running = desktopBridgeRuntimeVersion(service.state) || 'pre-8.6.2';
|
|
91
|
+
const blocker = versionStale
|
|
92
|
+
? `desktop_bridge_runtime_version_stale:${running}:${PACKAGE_VERSION}`
|
|
93
|
+
: `desktop_bridge_upstream_unreachable:${unreachable}`;
|
|
16
94
|
if (!input.fix) {
|
|
17
|
-
return {
|
|
18
|
-
restarted: false,
|
|
19
|
-
warnings: [],
|
|
20
|
-
blockers: [`desktop_bridge_runtime_version_stale:${running}:${PACKAGE_VERSION}`]
|
|
21
|
-
};
|
|
95
|
+
return { restarted: false, warnings: [], blockers: [blocker] };
|
|
22
96
|
}
|
|
23
|
-
const restarted = await bootstrapExistingDesktopBridgeService({ home: input.home }).catch(() => null);
|
|
97
|
+
const restarted = await (deps.bootstrapImpl || bootstrapExistingDesktopBridgeService)({ home: input.home }).catch(() => null);
|
|
24
98
|
const nowRunning = restarted?.running === true && !desktopBridgeRuntimeVersionStale(restarted.state);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
99
|
+
if (!nowRunning)
|
|
100
|
+
return { restarted: false, warnings: [], blockers: [blocker] };
|
|
101
|
+
return {
|
|
102
|
+
restarted: true,
|
|
103
|
+
warnings: [versionStale
|
|
104
|
+
? `desktop_bridge_runtime_restarted:${running}:${PACKAGE_VERSION}`
|
|
105
|
+
: `desktop_bridge_upstream_unreachable_restarted:${unreachable}`],
|
|
106
|
+
blockers: []
|
|
107
|
+
};
|
|
28
108
|
}
|
|
29
109
|
export const CATALOG_REPAIR_MAX_ATTEMPTS = 2;
|
|
30
110
|
export async function repairDoctorDesktopBridgeCatalog(input, deps = {}) {
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '9.2.
|
|
1
|
+
export const PACKAGE_VERSION = '9.2.6';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "9.2.
|
|
4
|
+
"version": "9.2.6",
|
|
5
5
|
"description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|