sneakoscope 10.1.2 → 10.1.3

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 CHANGED
@@ -16,7 +16,7 @@
16
16
  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.
17
17
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
18
18
 
19
- Current package: **SKS 10.1.2**. Install the latest stable release from npm.
19
+ Current package: **SKS 10.1.3**. Install the latest stable release from npm.
20
20
 
21
21
  [Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
22
22
 
@@ -176,6 +176,7 @@ freshness semantics.
176
176
  - [Essential Trust](docs/essential-trust.md) — verification profiles and safety boundaries.
177
177
  - [Astra guidance](docs/astra-guidance.md) — how SKS applies the official model recommendations.
178
178
  - [Agent Bridge](docs/AGENT-BRIDGE.md) — integrate through the CLI or MCP interface.
179
+ - [Codex-LB priority](docs/codex-lb-priority.md) — how Codex App WebSockets follow the Center priority setting.
179
180
  - [Release readiness](docs/release-readiness.md) — build, verify, and publish a release.
180
181
  - [Release evidence](docs/release-proof-truth.md) — what each verification result proves.
181
182
  - [Changelog](CHANGELOG.md) — changes by version.
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.1.2"
262
+ version = "10.1.3"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.1.2"
3
+ version = "10.1.3"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.1.2",
3
+ "package_version": "10.1.3",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -0,0 +1,286 @@
1
+ import net from 'node:net';
2
+ import tls from 'node:tls';
3
+ import WebSocket, { WebSocketServer } from 'ws';
4
+ import { BRIDGE_OFFICIAL_ROUTE_ID } from '../bridge-contracts.js';
5
+ import { buildOfficialPassthroughWebSocketHeaders, buildProviderWebSocketHeaders } from './header-policy.js';
6
+ import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
7
+ import { assertDesktopBridgeRouteContext, ensureDesktopBridgeRemoteTarget, isUnreachableUpstreamError, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode } from './security.js';
8
+ import { DesktopBridgeError } from './types.js';
9
+ const MAX_PENDING_MESSAGES = 256;
10
+ const CLOSE_GRACE_MS = 1_000;
11
+ const INITIAL_CREATE_TIMEOUT_MS = 60 * 60_000;
12
+ const logRejection = createDesktopBridgeRejectionLogger();
13
+ export function isResponsesWebSocketRequest(req) {
14
+ const pathname = new URL(req.url || '/', 'http://bridge.invalid').pathname;
15
+ return ['/backend-api/codex/responses', '/api/v1/responses', '/v1/responses'].includes(pathname);
16
+ }
17
+ function bytes(data) {
18
+ return Buffer.isBuffer(data) ? data : Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data);
19
+ }
20
+ export function forwardResponsesWebSocket(req, socket, head, config) {
21
+ const upgradeIdentity = resolveCodexSessionIdentity(req.headers);
22
+ const maxBytes = config.maxRequestBodyBytes ?? 16 * 1024 * 1024;
23
+ const server = new WebSocketServer({ noServer: true, clientTracking: false, perMessageDeflate: false, maxPayload: maxBytes });
24
+ try {
25
+ server.handleUpgrade(req, socket, head, (client) => {
26
+ bridge(client);
27
+ });
28
+ }
29
+ finally {
30
+ server.close();
31
+ }
32
+ function bridge(client) {
33
+ let identity = upgradeIdentity;
34
+ let upstream = null;
35
+ let upstreamSocket = null;
36
+ let bound = null;
37
+ let boundBaseUrl = null;
38
+ let boundCredentialGeneration = null;
39
+ let boundCredentialFingerprint = null;
40
+ let stopped = false;
41
+ let processing = false;
42
+ let pendingBytes = 0;
43
+ const pending = [];
44
+ let closeTimer = null;
45
+ let handshakeTimer = null;
46
+ const initialTimer = setTimeout(closeUnbound, config.websocketInitialCreateTimeoutMs ?? INITIAL_CREATE_TIMEOUT_MS);
47
+ initialTimer.unref();
48
+ const localSocket = socket;
49
+ localSocket.setKeepAlive?.(true, 30_000);
50
+ function stop() {
51
+ if (stopped)
52
+ return;
53
+ stopped = true;
54
+ clearTimeout(initialTimer);
55
+ if (handshakeTimer)
56
+ clearTimeout(handshakeTimer);
57
+ pending.length = 0;
58
+ pendingBytes = 0;
59
+ closeTimer = setTimeout(() => { client.terminate(); upstream?.terminate(); upstreamSocket?.destroy(); }, CLOSE_GRACE_MS);
60
+ closeTimer.unref();
61
+ }
62
+ function fail(error) {
63
+ if (stopped)
64
+ return;
65
+ const code = safeBridgeErrorCode(error);
66
+ logRejection({ code, transport: 'websocket', ...(req.method ? { method: req.method } : {}), ...(req.url ? { url: req.url } : {}) });
67
+ stop();
68
+ if (client.readyState === WebSocket.OPEN) {
69
+ if (client.bufferedAmount < maxBytes)
70
+ client.send(JSON.stringify({ type: 'error', error: { type: 'sks_bridge_error', code, message: code } }), () => undefined);
71
+ client.close(1011, code.slice(0, 123));
72
+ }
73
+ upstream?.terminate();
74
+ upstreamSocket?.destroy();
75
+ }
76
+ function closeUnbound() {
77
+ if (stopped || bound)
78
+ return;
79
+ logRejection({ code: 'bridge_websocket_initial_create_timeout', transport: 'websocket', ...(req.method ? { method: req.method } : {}), ...(req.url ? { url: req.url } : {}) });
80
+ stop();
81
+ if (client.readyState === WebSocket.OPEN)
82
+ client.close(1000, 'bridge_websocket_initial_create_timeout');
83
+ }
84
+ function relay(destination, data, binary) {
85
+ if (destination.readyState !== WebSocket.OPEN)
86
+ throw new DesktopBridgeError('bridge_websocket_upstream_unavailable');
87
+ if (destination.bufferedAmount + data.length > maxBytes)
88
+ throw new DesktopBridgeError('bridge_websocket_backpressure_exceeded');
89
+ destination.send(data, { binary }, (error) => { if (error)
90
+ fail(error); });
91
+ }
92
+ function routeRequest(model) {
93
+ return { public_model: model, session_id: identity.thread_id, pathname: new URL(req.url || '/', 'http://bridge.invalid').pathname, transport: 'websocket', headers: req.headers };
94
+ }
95
+ function assertBinding(route) {
96
+ if (!bound)
97
+ return;
98
+ const official = route.provider_id === BRIDGE_OFFICIAL_ROUTE_ID;
99
+ const provider = route.provider_id === BRIDGE_OFFICIAL_ROUTE_ID ? null : config.providers[route.provider_id];
100
+ const baseUrl = official ? config.officialRemote?.baseUrl : provider?.base_url;
101
+ if (route.provider_id !== bound.provider_id || baseUrl !== boundBaseUrl
102
+ || (provider && (provider.credential_generation !== boundCredentialGeneration || provider.credential_fingerprint !== boundCredentialFingerprint))) {
103
+ throw new DesktopBridgeError('bridge_websocket_route_change_forbidden');
104
+ }
105
+ }
106
+ async function resolveCreate(message) {
107
+ const model = message.create?.model;
108
+ if (typeof model !== 'string' || !model.trim())
109
+ throw new DesktopBridgeError('bridge_websocket_model_required');
110
+ const nextIdentity = resolveCodexSessionIdentity(req.headers, message.create);
111
+ if (bound && ((nextIdentity.thread_id && nextIdentity.thread_id !== identity.thread_id)
112
+ || (nextIdentity.session_id && nextIdentity.session_id !== identity.session_id))) {
113
+ throw new DesktopBridgeError('bridge_codex_session_identity_conflict');
114
+ }
115
+ if (!bound)
116
+ identity = nextIdentity;
117
+ const request = routeRequest(model);
118
+ assertBinding(assertDesktopBridgeRouteContext(request, config));
119
+ const route = await resolveAndBindDesktopBridgeRouteContext(request, config);
120
+ if (stopped || client.readyState !== WebSocket.OPEN)
121
+ return;
122
+ assertBinding(route);
123
+ if (!bound) {
124
+ bound = route;
125
+ await connect(route);
126
+ }
127
+ if (route.upstream_model !== model)
128
+ message.data = Buffer.from(JSON.stringify({ ...message.create, model: route.upstream_model }));
129
+ }
130
+ async function connect(route) {
131
+ const official = route.provider_id === BRIDGE_OFFICIAL_ROUTE_ID;
132
+ const provider = route.provider_id === BRIDGE_OFFICIAL_ROUTE_ID ? null : config.providers[route.provider_id];
133
+ const remote = official ? config.officialRemote : provider?.remote;
134
+ if (!remote)
135
+ throw new DesktopBridgeError('bridge_websocket_upstream_unavailable');
136
+ boundBaseUrl = official ? remote.baseUrl : provider.base_url;
137
+ boundCredentialGeneration = provider?.credential_generation ?? null;
138
+ boundCredentialFingerprint = provider?.credential_fingerprint ?? null;
139
+ const credential = provider ? await config.resolveProviderCredential(provider.provider_id, provider.credential_generation) : null;
140
+ if (credential && (credential.provider_id !== provider.provider_id || credential.generation !== boundCredentialGeneration
141
+ || (boundCredentialFingerprint && credential.fingerprint !== boundCredentialFingerprint)))
142
+ throw new DesktopBridgeError('bridge_provider_credential_generation_mismatch');
143
+ await ensureDesktopBridgeRemoteTarget(remote, config.remoteLookup);
144
+ if (stopped || client.readyState !== WebSocket.OPEN)
145
+ return;
146
+ const target = resolveDesktopBridgeTarget(req.url, remote);
147
+ const outgoing = official ? buildOfficialPassthroughWebSocketHeaders(req.headers, target.host)
148
+ : buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential: credential }, target.host);
149
+ const headers = {};
150
+ for (const [name, value] of Object.entries(outgoing)) {
151
+ if (name.startsWith('sec-websocket-') || name === 'connection' || name === 'upgrade' || name === 'content-length')
152
+ continue;
153
+ if (value !== undefined)
154
+ headers[name] = Array.isArray(value) ? value.join(', ') : String(value);
155
+ }
156
+ target.protocol = remote.secure ? 'wss:' : 'ws:';
157
+ let connected = false;
158
+ handshakeTimer = setTimeout(() => {
159
+ if (!connected)
160
+ void refreshDesktopBridgeRemoteTarget(remote, config.remoteLookup);
161
+ fail(new DesktopBridgeError('bridge_websocket_upstream_handshake_timeout'));
162
+ }, config.connectTimeoutMs);
163
+ handshakeTimer.unref();
164
+ upstream = new WebSocket(target, client.protocol ? [client.protocol] : [], {
165
+ headers, perMessageDeflate: false, maxPayload: maxBytes,
166
+ handshakeTimeout: config.connectTimeoutMs, followRedirects: false,
167
+ createConnection: () => {
168
+ const connection = remote.secure
169
+ ? tls.connect({ host: remote.address, port: remote.port, ...(remote.tlsServername ? { servername: remote.tlsServername } : {}) })
170
+ : net.connect({ host: remote.address, port: remote.port, family: remote.family });
171
+ upstreamSocket = connection;
172
+ connection.setKeepAlive(true, 30_000);
173
+ connection.once(remote.secure ? 'secureConnect' : 'connect', () => { connected = true; });
174
+ return connection;
175
+ },
176
+ });
177
+ const peer = upstream;
178
+ peer.on('error', (error) => {
179
+ if (isUnreachableUpstreamError(error))
180
+ void refreshDesktopBridgeRemoteTarget(remote, config.remoteLookup);
181
+ fail(new DesktopBridgeError('bridge_websocket_upstream_unavailable'));
182
+ });
183
+ peer.on('unexpected-response', (_request, response) => {
184
+ response.destroy();
185
+ fail(new DesktopBridgeError(`bridge_websocket_upgrade_failed_${response.statusCode || 502}`));
186
+ });
187
+ peer.on('message', (data, binary) => {
188
+ if (stopped)
189
+ return;
190
+ try {
191
+ relay(client, bytes(data), binary);
192
+ }
193
+ catch (error) {
194
+ fail(error);
195
+ }
196
+ });
197
+ peer.on('close', (code, reason) => {
198
+ upstreamSocket?.destroy();
199
+ stop();
200
+ if (client.readyState === WebSocket.OPEN) {
201
+ if (code === 1005)
202
+ client.close();
203
+ else
204
+ client.close(code === 1006 ? 1011 : code, code === 1006 ? 'bridge_websocket_upstream_closed' : reason);
205
+ }
206
+ clearCloseTimer();
207
+ });
208
+ await new Promise((resolve, reject) => {
209
+ peer.once('open', resolve);
210
+ peer.once('error', reject);
211
+ peer.once('close', () => reject(new DesktopBridgeError('bridge_websocket_upstream_closed')));
212
+ });
213
+ if (handshakeTimer)
214
+ clearTimeout(handshakeTimer);
215
+ clearTimeout(initialTimer);
216
+ }
217
+ async function drain() {
218
+ if (processing || stopped)
219
+ return;
220
+ processing = true;
221
+ try {
222
+ if (!bound) {
223
+ const firstCreate = pending.find((message) => message.create);
224
+ if (!firstCreate)
225
+ return;
226
+ await resolveCreate(firstCreate);
227
+ firstCreate.create = null;
228
+ }
229
+ while (!stopped && client.readyState === WebSocket.OPEN && pending.length) {
230
+ const message = pending[0];
231
+ if (message.create)
232
+ await resolveCreate(message);
233
+ if (stopped || client.readyState !== WebSocket.OPEN)
234
+ return;
235
+ relay(upstream, message.data, message.binary);
236
+ pending.shift();
237
+ pendingBytes -= message.originalBytes;
238
+ }
239
+ }
240
+ catch (error) {
241
+ fail(error);
242
+ }
243
+ finally {
244
+ processing = false;
245
+ }
246
+ }
247
+ function clearCloseTimer() {
248
+ if (client.readyState === WebSocket.CLOSED && (!upstream || upstream.readyState === WebSocket.CLOSED) && closeTimer)
249
+ clearTimeout(closeTimer);
250
+ }
251
+ client.on('message', (data, binary) => {
252
+ if (stopped)
253
+ return;
254
+ const payload = bytes(data);
255
+ let create = null;
256
+ try {
257
+ const parsed = JSON.parse(payload.toString('utf8'));
258
+ if (parsed && typeof parsed === 'object' && parsed.type === 'response.create')
259
+ create = parsed;
260
+ }
261
+ catch { }
262
+ if (pending.length >= MAX_PENDING_MESSAGES || pendingBytes + payload.length > maxBytes) {
263
+ fail(new DesktopBridgeError('bridge_websocket_pending_limit_exceeded'));
264
+ return;
265
+ }
266
+ pending.push({ data: payload, binary, create, originalBytes: payload.length });
267
+ pendingBytes += payload.length;
268
+ void drain();
269
+ });
270
+ client.on('error', () => { stop(); upstream?.terminate(); upstreamSocket?.destroy(); client.terminate(); });
271
+ client.once('close', (code, reason) => {
272
+ stop();
273
+ if (upstream?.readyState === WebSocket.OPEN) {
274
+ if (code === 1005)
275
+ upstream.close();
276
+ else
277
+ upstream.close(code === 1006 ? 1011 : code, reason);
278
+ }
279
+ else if (upstream?.readyState === WebSocket.CONNECTING) {
280
+ upstream.terminate();
281
+ upstreamSocket?.destroy();
282
+ }
283
+ clearCloseTimer();
284
+ });
285
+ }
286
+ }
@@ -581,6 +581,10 @@ export function validateDesktopBridgeConfig(config) {
581
581
  && (!Number.isInteger(config.maxConnections) || config.maxConnections < 1 || config.maxConnections > 2_048)) {
582
582
  throw new DesktopBridgeError('bridge_connection_limit_invalid');
583
583
  }
584
+ if (config.websocketInitialCreateTimeoutMs !== undefined
585
+ && (!Number.isFinite(config.websocketInitialCreateTimeoutMs) || config.websocketInitialCreateTimeoutMs < 1_000 || config.websocketInitialCreateTimeoutMs > 86_400_000)) {
586
+ throw new DesktopBridgeError('bridge_websocket_initial_create_timeout_invalid');
587
+ }
584
588
  for (const origin of config.allowedOrigins)
585
589
  normalizeAllowedOrigin(origin);
586
590
  if (!config.providerRegistry)
@@ -7,6 +7,7 @@ import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
7
7
  import { rewriteLocationHeader } from './location-rewrite.js';
8
8
  import { desktopBridgeOfficialPassthroughEnabled, ensureDesktopBridgeRemoteTarget, isUnreachableUpstreamError, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader, canonicalSessionId } from './security.js';
9
9
  import { desktopBridgeListenOrigin } from './state.js';
10
+ import { forwardResponsesWebSocket, isResponsesWebSocketRequest } from './responses-websocket.js';
10
11
  import { DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL, DesktopBridgeError, } from './types.js';
11
12
  const MAX_HEAD = 64 * 1024;
12
13
  const REDACTED_RESPONSE_HEADERS = new Set(['authorization', 'cookie', 'proxy-authenticate', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-codex-lb-api-key']);
@@ -137,6 +138,15 @@ function writeUpgradeFailure(client, error, req) {
137
138
  + JSON.stringify({ error: { type: 'sks_bridge_error', code, retryable: !permanent } }));
138
139
  }
139
140
  export async function forwardWebSocket(req, client, head, config, authenticatedLocalBaseUrl = desktopBridgeListenOrigin(config)) {
141
+ if (isResponsesWebSocketRequest(req)) {
142
+ try {
143
+ forwardResponsesWebSocket(req, client, head, config);
144
+ }
145
+ catch (error) {
146
+ writeUpgradeFailure(client, error, req);
147
+ }
148
+ return;
149
+ }
140
150
  let prepared;
141
151
  try {
142
152
  prepared = await prepareDesktopBridgeWebSocketRequest(req, config);
@@ -22,7 +22,7 @@ export async function syncCatalog(options) {
22
22
  : {};
23
23
  return commandResult('catalog.sync', result.ok === true, status, { catalog_sync: result }, result.ok === true ? [] : stringArray(activation.blockers), options);
24
24
  }
25
- export async function syncCatalogInternal(options) {
25
+ export async function syncCatalogInternal(options, behavior = {}) {
26
26
  const paths = controllerPaths(options);
27
27
  const historicalIntent = inspectHistoricalDesktopBridgeIntent(await readText(paths.configPath, ''));
28
28
  if (historicalIntent.blockers.length > 0)
@@ -155,7 +155,7 @@ export async function syncCatalogInternal(options) {
155
155
  };
156
156
  }
157
157
  const serviceBefore = await (options.serviceStatusImpl || desktopBridgeServiceStatus)({ ...options, home: paths.home });
158
- if (serviceBefore.installed || serviceBefore.running) {
158
+ if (behavior.restartService !== false && (serviceBefore.installed || serviceBefore.running)) {
159
159
  await (options.bootstrapServiceImpl || bootstrapExistingDesktopBridgeService)({
160
160
  ...options,
161
161
  home: paths.home,
@@ -12,7 +12,7 @@ import { commandResult, controllerPaths, nowIso, persistRuntimeSettings, provide
12
12
  import { desktopBridgeStatusV3, loadCore, statusFromCore } from './status.js';
13
13
  import { verifyDesktopBridgeV3 } from './verification.js';
14
14
  export async function ensureDesktopBridge(options, operation) {
15
- const sync = await syncCatalogInternal(options);
15
+ const sync = await syncCatalogInternal(options, { restartService: false });
16
16
  let core = await loadCore(options);
17
17
  if (!core.activeCatalog.ok || !core.policy) {
18
18
  return commandResult(operation, true, statusFromCore(core, options), { catalog_sync: sync }, syncResultBlockers(sync), options);
@@ -35,7 +35,7 @@ export async function repairDesktopBridge(options) {
35
35
  let core = await loadCore(options);
36
36
  if (!core.activeCatalog.ok || !core.policy)
37
37
  return ensureDesktopBridge(options, 'repair');
38
- await persistRuntimeSettings(core, options);
38
+ await persistRuntimeSettings(core, options, { restartService: false });
39
39
  const service = await (options.installServiceImpl || installAndStartDesktopBridgeService)({
40
40
  ...options,
41
41
  home: core.paths.home,
@@ -116,11 +116,13 @@ export async function persistRuntimeSettings(core, options, behavior = {}) {
116
116
  await stopAfterFailedRestart(restartOptions, options);
117
117
  throw error;
118
118
  }
119
- if (!restarted.ok || !restarted.running) {
119
+ if (!restarted.running) {
120
120
  await stopAfterFailedRestart(restartOptions, options);
121
121
  const rootCause = restarted.blockers.find((blocker) => blocker === 'desktop_bridge_entry_macos_protected_folder');
122
122
  throw new Error(rootCause || restarted.blockers[0] || 'desktop_bridge_restart_failed');
123
123
  }
124
+ if (!restarted.ok)
125
+ throw new Error(restarted.blockers[0] || 'desktop_bridge_restart_failed');
124
126
  }
125
127
  export async function quiesceRunningBridge(core, options) {
126
128
  if (!core.service.running)
@@ -287,6 +287,7 @@ export function authPriorityStatusFromCore(core) {
287
287
  : profile.state !== 'ready' ? 'codex_lb_route_not_ready'
288
288
  : !core.activeCatalog.ok || core.activeCatalog.route_index.providers['codex-lb'].state !== 'ready' || !core.policy || !Object.entries(core.policy.model_routes).some(([model, route]) => /^(?:gpt-[0-9]|o[0-9]|codex-mini)/.test(model) && !model.includes(':') && route.provider_id === 'codex-lb')
289
289
  ? 'codex_lb_eligible_route_missing'
290
- : !core.service.running ? 'desktop_bridge_not_running' : null;
290
+ : !core.service.running ? 'desktop_bridge_not_running'
291
+ : !core.service.ok ? 'desktop_bridge_runtime_not_ready' : null;
291
292
  return { enabled: true, state: error ? 'unavailable' : 'active', error };
292
293
  }
@@ -373,22 +373,24 @@ export async function installAndStartDesktopBridgeService(options = {}) {
373
373
  return failedStatus(paths, settings, service, 'missing', 'desktop_bridge_launchd_bootstrap_failed');
374
374
  await run(ctl, ['kickstart', '-k', service], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch(() => undefined);
375
375
  const status = await waitForBridge({ ...options, home });
376
- if (!status.ok) {
377
- await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
378
- if (await launchTargetsProtectedFolder([command.executable, ...command.arguments], home)) {
379
- return withProtectedFolderBlocker(status);
376
+ if (status.running) {
377
+ if (!status.ok)
378
+ return status;
379
+ try {
380
+ await cleanupRetiredDesktopBridgeRuntime(retired);
381
+ return status;
382
+ }
383
+ catch (error) {
384
+ await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
385
+ await removeStaleState(paths.state_path, options.processExists);
386
+ return failedStatus(paths, settings, service, 'missing', safeServiceError(error));
380
387
  }
381
- return status;
382
- }
383
- try {
384
- await cleanupRetiredDesktopBridgeRuntime(retired);
385
- return status;
386
388
  }
387
- catch (error) {
388
- await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
389
- await removeStaleState(paths.state_path, options.processExists);
390
- return failedStatus(paths, settings, service, 'missing', safeServiceError(error));
389
+ await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
390
+ if (await launchTargetsProtectedFolder([command.executable, ...command.arguments], home)) {
391
+ return withProtectedFolderBlocker(status);
391
392
  }
393
+ return status;
392
394
  }
393
395
  export async function bootstrapExistingDesktopBridgeService(options = {}) {
394
396
  const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
@@ -710,6 +712,11 @@ function normalizeProviderRegistrySnapshot(value) {
710
712
  function overridePaths(base, options) { return { settings_path: options.settingsPath || base.settings_path, state_path: options.statePath || base.state_path, client_capability_path: options.clientCapabilityPath || base.client_capability_path, launch_agent_path: options.launchAgentPath || base.launch_agent_path, stdout_log_path: options.stdoutLogPath || base.stdout_log_path, stderr_log_path: options.stderrLogPath || base.stderr_log_path }; }
711
713
  function launchDomain(uid = typeof process.getuid === 'function' ? process.getuid() : 0) { return `gui/${uid}`; }
712
714
  function launchService(uid) { return `${launchDomain(uid)}/${DESKTOP_BRIDGE_LAUNCHD_LABEL}`; }
715
+ export function launchCommandForExecutable(executable, resolved, execPath = process.execPath) {
716
+ return /\.[cm]?js$/i.test(resolved)
717
+ ? { executable: path.resolve(execPath), arguments: [path.resolve(resolved)] }
718
+ : { executable: path.resolve(executable), arguments: [] };
719
+ }
713
720
  async function resolveLaunchCommand(options, home) {
714
721
  if (options.executablePath) {
715
722
  return await exists(path.resolve(options.executablePath))
@@ -723,7 +730,7 @@ async function resolveLaunchCommand(options, home) {
723
730
  }
724
731
  const sks = await which('sks').catch(() => null);
725
732
  if (sks)
726
- candidates.push({ executable: path.resolve(sks), arguments: [] });
733
+ candidates.push(launchCommandForExecutable(sks, await fsp.realpath(sks).catch(() => sks)));
727
734
  let rejectedProtected = false;
728
735
  for (const candidate of candidates) {
729
736
  if (await launchTargetsProtectedFolder([candidate.executable, ...candidate.arguments], home)) {
@@ -783,7 +790,7 @@ export async function bootstrapLaunchdWithRetry(options, domain, service, plistP
783
790
  async function inspectLaunchd(options, service) { if ((options.platform || process.platform) !== 'darwin')
784
791
  return { loaded: false, running: false }; const result = await (options.run || runProcess)(options.launchctl || '/bin/launchctl', ['print', service], { timeoutMs: 3_000, maxOutputBytes: 32 * 1024 }).catch(() => null); if (!result || result.code !== 0)
785
792
  return { loaded: false, running: false }; const text = `${result.stdout}\n${result.stderr}`; return { loaded: true, running: /state = running/.test(text) && /pid = \d+/.test(text) }; }
786
- async function waitForBridge(options) { let status = await desktopBridgeServiceStatus(options); for (let i = 0; i < 150 && !status.ok; i += 1) {
793
+ async function waitForBridge(options) { let status = await desktopBridgeServiceStatus(options); for (let i = 0; i < 150 && !status.running; i += 1) {
787
794
  await delay(100);
788
795
  status = await desktopBridgeServiceStatus(options);
789
796
  } return status; }
@@ -1,73 +1,69 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
- import { exists, PACKAGE_VERSION, readJson, runProcess } from '../../fsx.js';
4
- import { bootstrapExistingDesktopBridgeService, desktopBridgeServicePaths } from '../../codex-lb/desktop-service.js';
3
+ import { exists, PACKAGE_VERSION } from '../../fsx.js';
4
+ import { executeDesktopBridgeCommandV3 } from '../../codex-lb/desktop-controller-v3.js';
5
+ import { desktopBridgeRuntimeVersion } from '../../codex-lb/desktop-bridge/state.js';
6
+ import { desktopBridgeServicePaths, desktopBridgeServiceStatus } from '../../codex-lb/desktop-service.js';
5
7
  export async function desktopBridgeRestage(options = {}) {
6
8
  const skip = (reason) => ({ ok: true, status: 'ok', actions: [reason], blockers: [], warnings: [] });
7
9
  const warn = (warnings) => ({ ok: true, status: 'ok', actions: [], blockers: [], warnings });
8
10
  const env = options.env || process.env;
11
+ const platform = options.platform || process.platform;
9
12
  const version = options.packageVersion || PACKAGE_VERSION;
10
- const reachesRealLaunchd = !(options.run && options.bootstrapService);
11
- if ((options.platform || process.platform) !== 'darwin')
13
+ if (platform !== 'darwin')
12
14
  return skip('desktop_bridge_restage_not_macos');
13
- if (reachesRealLaunchd && (env.NODE_TEST_CONTEXT !== undefined || env.SKS_TEST_ISOLATION === '1')) {
15
+ const reachesRealLaunchd = !(options.serviceStatus && options.executeCommand);
16
+ const isolated = [process.env, env].some((candidate) => candidate.NODE_TEST_CONTEXT !== undefined
17
+ || candidate.SKS_TEST_ISOLATION === '1'
18
+ || candidate.SKS_RELEASE_UPGRADE_SMOKE === '1');
19
+ if (reachesRealLaunchd && isolated)
14
20
  return skip('desktop_bridge_restage_skipped_under_tests');
15
- }
16
21
  if (env.SKS_SKIP_BRIDGE_RESTAGE === '1')
17
22
  return skip('desktop_bridge_restage_disabled');
18
23
  const home = options.home || path.resolve(env.HOME || os.homedir());
19
24
  const paths = desktopBridgeServicePaths(home);
20
25
  if (!(await exists(paths.launch_agent_path)))
21
26
  return skip('desktop_bridge_restage_no_launch_agent');
22
- const state = await readJson(paths.state_path, null);
23
- const runningVersion = typeof state?.sks_version === 'string' ? state.sks_version : null;
24
- const pid = typeof state?.pid === 'number' && Number.isInteger(state.pid) && state.pid > 1 ? state.pid : null;
25
- if (pid === null || !(options.processAlive || processAlive)(pid)) {
26
- if (!(await exists(paths.settings_path)))
27
- return skip('desktop_bridge_restage_no_managed_bridge');
28
- const bootstrap = await (options.bootstrapService || bootstrapExistingDesktopBridgeService)({ home })
29
- .catch(() => null);
30
- if (bootstrap?.running) {
31
- return {
32
- ok: true,
33
- status: 'ok',
34
- actions: [`desktop_bridge_bootstrapped:${version}`],
35
- blockers: [],
36
- warnings: bootstrap.blockers.map((blocker) => `desktop_bridge_restage_bootstrap_incomplete:${blocker}`)
37
- };
38
- }
39
- return warn([
40
- ...(bootstrap?.blockers || ['desktop_bridge_restage_bootstrap_failed'])
41
- .map((blocker) => `desktop_bridge_restage_bootstrap_incomplete:${blocker}`),
42
- 'Desktop Bridge is installed but not running: run `sks bridge repair` from your home directory'
43
- ]);
44
- }
45
- if (runningVersion === version)
27
+ if (!(await exists(paths.settings_path)))
28
+ return skip('desktop_bridge_restage_no_managed_bridge');
29
+ const serviceOptions = { home, env, platform };
30
+ const statusImpl = options.serviceStatus || desktopBridgeServiceStatus;
31
+ const before = await statusImpl(serviceOptions).catch(() => null);
32
+ const runningVersion = desktopBridgeRuntimeVersion(before?.state);
33
+ if (before?.ok && before.running && before.loaded && runningVersion === version) {
46
34
  return skip('desktop_bridge_restage_already_current');
47
- const uid = options.uid === undefined
48
- ? (typeof process.getuid === 'function' ? process.getuid() : null)
49
- : options.uid;
50
- if (uid === null)
51
- return skip('desktop_bridge_restage_no_uid');
52
- const kick = await (options.run || runProcess)('/bin/launchctl', ['kickstart', '-k', `gui/${uid}/com.sneakoscope.desktop-bridge`], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch(() => null);
53
- if (!kick || kick.code !== 0) {
54
- return warn([`desktop_bridge_restage_kickstart_failed:${runningVersion || 'pre-8.6.2'}`]);
55
35
  }
56
- return {
57
- ok: true, status: 'ok',
58
- actions: [`desktop_bridge_restarted:${runningVersion || 'pre-8.6.2'}:${version}`],
59
- blockers: [], warnings: []
60
- };
36
+ const repair = await (options.executeCommand || executeDesktopBridgeCommandV3)({ operation: 'repair' }, serviceOptions).catch(() => null);
37
+ const after = await statusImpl(serviceOptions).catch(() => null);
38
+ const repairedVersion = desktopBridgeRuntimeVersion(after?.state);
39
+ const command = repair?.schema === 'sks.desktop-bridge-command-result.v1' ? repair : null;
40
+ const commandBlockers = command?.execution.blockers || [];
41
+ if (command?.ok && command.execution.ok && commandBlockers.length === 0
42
+ && after?.ok && after.running && after.loaded && repairedVersion === version) {
43
+ return {
44
+ ok: true,
45
+ status: 'ok',
46
+ actions: [before?.running
47
+ ? `desktop_bridge_restarted:${runningVersion || 'pre-8.6.2'}:${version}`
48
+ : `desktop_bridge_bootstrapped:${version}`],
49
+ blockers: [],
50
+ warnings: []
51
+ };
52
+ }
53
+ const incomplete = [...new Set([
54
+ ...commandBlockers,
55
+ ...(after?.blockers || []),
56
+ ...(!command?.ok || !command.execution.ok ? ['desktop_bridge_repair_failed'] : []),
57
+ ...(!after?.running || !after.loaded ? ['desktop_bridge_service_not_running'] : []),
58
+ ...(repairedVersion !== version
59
+ ? [`desktop_bridge_runtime_version_unverified:${repairedVersion || 'unknown'}:${version}`]
60
+ : [])
61
+ ])];
62
+ return warn([
63
+ ...incomplete.map((blocker) => `desktop_bridge_restage_incomplete:${blocker}`),
64
+ 'Desktop Bridge update repair incomplete: run `sks bridge repair` from your home directory'
65
+ ]);
61
66
  }
62
67
  export function runDesktopBridgeRestageStage() {
63
68
  return desktopBridgeRestage();
64
69
  }
65
- function processAlive(pid) {
66
- try {
67
- process.kill(pid, 0);
68
- return true;
69
- }
70
- catch {
71
- return false;
72
- }
73
- }
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '10.1.2';
1
+ export const PACKAGE_VERSION = '10.1.3';
@@ -26,7 +26,7 @@ struct AuthPriorityState: Equatable {
26
26
  switch error {
27
27
  case "codex_lb_provider_disabled": return "On, unavailable · enable Codex-LB below"
28
28
  case "codex_lb_credential_missing": return "On, unavailable · connect your Codex-LB account below"
29
- case "desktop_bridge_not_running": return "On, unavailable · open Bridge diagnostics and repair the bridge service"
29
+ case "desktop_bridge_not_running", "desktop_bridge_runtime_not_ready": return "On, unavailable · open Bridge diagnostics and repair the bridge service"
30
30
  case "codex_lb_route_not_ready", "codex_lb_eligible_route_missing": return "On, unavailable · validate Codex-LB and refresh its model catalog"
31
31
  default: return "On, unavailable · check your Codex-LB connection below"
32
32
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "10.1.2",
4
+ "version": "10.1.3",
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",