sneakoscope 9.0.0 → 9.0.1

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
@@ -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.0.0** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
25
+ This README documents package **SKS 9.0.1** — 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
 
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "9.0.0"
262
+ version = "9.0.1"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "9.0.0"
3
+ version = "9.0.1"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -116,7 +116,7 @@ async function parseInvocation(args, io) {
116
116
  return { ...base, request: { operation: 'status' }, label: 'Desktop Bridge status' };
117
117
  }
118
118
  if (area === 'serve' && action === undefined) {
119
- allowOnly(parsed, ['--json'], ['--settings']);
119
+ allowOnly(parsed, ['--json', '--supervised'], ['--settings']);
120
120
  const settingsPath = parsed.values.get('--settings') || '';
121
121
  if (!path.isAbsolute(settingsPath)) {
122
122
  throw new BridgeCliError('desktop_bridge_settings_path_must_be_absolute');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "9.0.0",
3
+ "package_version": "9.0.1",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -252,6 +252,15 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
252
252
  upstream.once('response', (response) => {
253
253
  const statusCode = response.statusCode || 502;
254
254
  if (statusCode >= 400) {
255
+ logHttpRejection({
256
+ code: `bridge_upstream_status_${statusCode}`,
257
+ transport: 'http',
258
+ ...(req.method === undefined ? {} : { method: req.method }),
259
+ ...(req.url === undefined ? {} : { url: req.url }),
260
+ status: statusCode,
261
+ provider_id: provider.provider_id,
262
+ public_model: request.route.public_model,
263
+ });
255
264
  void readRedactedUpstreamError(response).then((body) => {
256
265
  responseStarted = true;
257
266
  const responseHeaders = rewriteResponseHeaders(response.headers, provider.base_url, authenticatedLocalBaseUrl);
@@ -2,6 +2,10 @@ import { PACKAGE_VERSION } from '../../version.js';
2
2
  export const DESKTOP_BRIDGE_LOG_SCHEMA = 'sks.desktop-bridge-log.v2';
3
3
  export const REJECTION_LOG_BURST = 5;
4
4
  export const REJECTION_LOG_SUMMARY_INTERVAL_MS = 60_000;
5
+ function safeCatalogId(value) {
6
+ const text = String(value ?? '').replace(/[\r\n\0]/g, '').trim().slice(0, 128);
7
+ return text.length > 0 ? text : null;
8
+ }
5
9
  function safePathname(url) {
6
10
  const raw = String(url || '').trim();
7
11
  if (!raw.startsWith('/'))
@@ -62,6 +66,8 @@ export function createDesktopBridgeRejectionLogger(options = {}) {
62
66
  windows.set(code, window);
63
67
  const pathname = safePathname(event.url);
64
68
  const method = safeMethod(event.method);
69
+ const providerId = safeCatalogId(event.provider_id);
70
+ const publicModel = safeCatalogId(event.public_model);
65
71
  emit({
66
72
  event: 'sks.desktop_bridge.rejected',
67
73
  code,
@@ -69,6 +75,8 @@ export function createDesktopBridgeRejectionLogger(options = {}) {
69
75
  ...(method ? { method } : {}),
70
76
  ...(pathname ? { pathname } : {}),
71
77
  ...(Number.isInteger(event.status) ? { status: event.status } : {}),
78
+ ...(providerId ? { provider_id: providerId } : {}),
79
+ ...(publicModel ? { public_model: publicModel } : {}),
72
80
  });
73
81
  };
74
82
  }
@@ -6,6 +6,7 @@ import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
6
6
  import { assertAllowedOrigin, assertAllowedPath, assertLoopbackPeer, assertLoopbackListenHost, assertWebSocketUpgrade, prepareDesktopBridgeConfig, safeBridgeErrorCode, validatePreparedDesktopBridgeConfig, } from './security.js';
7
7
  import { createDesktopBridgePublicState, desktopBridgeListenOrigin, desktopBridgeStatePath, refreshDesktopBridgeState, removeDesktopBridgeStateIfOwned, writeDesktopBridgeState, } from './state.js';
8
8
  import { DesktopBridgeError } from './types.js';
9
+ import { PACKAGE_VERSION } from '../../version.js';
9
10
  import { DESKTOP_BRIDGE_CLIENT_PATH_PREFIX, DESKTOP_BRIDGE_DIAGNOSTIC_HEALTH_PATH, DESKTOP_BRIDGE_DIAGNOSTIC_PATH, DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL } from './types.js';
10
11
  import { forwardWebSocket } from './websocket-forward.js';
11
12
  function authenticateDesktopBridgeClient(req, input) {
@@ -316,6 +317,27 @@ export async function startPreparedDesktopBridge(input, options = {}) {
316
317
  void refreshDesktopBridgeState(statePath, state, new Date(), freshnessMs).catch(() => undefined);
317
318
  }, Math.max(1_000, Math.floor(freshnessMs / 3))) : null;
318
319
  heartbeat?.unref();
320
+ let skewCandidate = null;
321
+ let skewFired = false;
322
+ const versionCheck = options.versionSkew ? setInterval(() => {
323
+ void options.versionSkew.readInstalledVersion().then((installed) => {
324
+ if (skewFired)
325
+ return;
326
+ if (!installed || installed === PACKAGE_VERSION) {
327
+ skewCandidate = null;
328
+ return;
329
+ }
330
+ if (skewCandidate !== installed) {
331
+ skewCandidate = installed;
332
+ return;
333
+ }
334
+ skewFired = true;
335
+ if (versionCheck)
336
+ clearInterval(versionCheck);
337
+ options.versionSkew.onSkew(installed);
338
+ }).catch(() => undefined);
339
+ }, Math.max(1_000, options.versionSkew.intervalMs ?? 60_000)) : null;
340
+ versionCheck?.unref();
319
341
  let stopped = false;
320
342
  const handle = {
321
343
  server,
@@ -328,6 +350,8 @@ export async function startPreparedDesktopBridge(input, options = {}) {
328
350
  stopped = true;
329
351
  if (heartbeat)
330
352
  clearInterval(heartbeat);
353
+ if (versionCheck)
354
+ clearInterval(versionCheck);
331
355
  await closeServer(server, sockets, () => activeRequests);
332
356
  destroyDesktopBridgeUpstreamAgents();
333
357
  if (statePath)
@@ -152,14 +152,10 @@ export async function forwardWebSocket(req, client, head, config, authenticatedL
152
152
  connected = true;
153
153
  clearTimeout(timer);
154
154
  upstream.setNoDelay(true);
155
- upstream.setTimeout(config.idleTimeoutMs, () => {
156
- logWebSocketRejection({
157
- code: 'bridge_websocket_upstream_idle_timeout',
158
- transport: 'websocket',
159
- ...(req.url === undefined ? {} : { url: req.url }),
160
- });
161
- upstream.destroy(new DesktopBridgeError('bridge_websocket_upstream_idle_timeout'));
162
- });
155
+ upstream.setKeepAlive(true, 30_000);
156
+ const clientSocket = client;
157
+ if (typeof clientSocket.setKeepAlive === 'function')
158
+ clientSocket.setKeepAlive(true, 30_000);
163
159
  const headers = buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential }, target.host);
164
160
  upstream.write([`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`, ...serializeHeaders(headers), '', ''].join('\r\n'));
165
161
  if (head.length)
@@ -3,7 +3,7 @@ import { constants as fsConstants } from 'node:fs';
3
3
  import fsp from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
- import { ensureDir, exists, runProcess, which, writeTextAtomic } from '../fsx.js';
6
+ import { ensureDir, exists, packageRoot, runProcess, which, writeTextAtomic } from '../fsx.js';
7
7
  import { withFileLock } from '../locks/file-lock.js';
8
8
  import { loadCodexLbEnv } from './codex-lb-env.js';
9
9
  import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
@@ -352,7 +352,7 @@ export async function installAndStartDesktopBridgeService(options = {}) {
352
352
  return failedStatus(paths, settings, launchService(options.uid), 'settings_missing', 'desktop_bridge_sks_executable_missing');
353
353
  await prepareDesktopBridgeServicePaths(paths);
354
354
  await writeDesktopBridgeServiceSettings(paths.settings_path, { ...settings, provider_registry: runtime.config.providerRegistry, route_policy: runtime.config.routePolicy });
355
- await writeDesktopBridgeLaunchdPlist(paths.launch_agent_path, { executablePath: command.executable, arguments: [...command.arguments, 'bridge', 'serve', '--settings', paths.settings_path, '--json'], stdoutPath: paths.stdout_log_path, stderrPath: paths.stderr_log_path });
355
+ await writeDesktopBridgeLaunchdPlist(paths.launch_agent_path, { executablePath: command.executable, arguments: [...command.arguments, 'bridge', 'serve', '--settings', paths.settings_path, '--json', '--supervised'], stdoutPath: paths.stdout_log_path, stderrPath: paths.stderr_log_path });
356
356
  const run = options.run || runProcess;
357
357
  const ctl = options.launchctl || '/bin/launchctl';
358
358
  const service = launchService(options.uid);
@@ -421,11 +421,36 @@ export async function stopDesktopBridgeService(options = {}) {
421
421
  const status = await desktopBridgeServiceStatus({ ...options, home });
422
422
  return { ...status, ok: !status.running, blockers: status.running ? ['desktop_bridge_process_still_running'] : [] };
423
423
  }
424
+ function desktopBridgeIsSupervised(env = process.env, argv = process.argv) {
425
+ return env.XPC_SERVICE_NAME === DESKTOP_BRIDGE_LAUNCHD_LABEL || argv.includes('--supervised');
426
+ }
427
+ async function installedPackageVersion() {
428
+ try {
429
+ const raw = await fsp.readFile(path.join(packageRoot(), 'package.json'), 'utf8');
430
+ const version = JSON.parse(raw).version;
431
+ return typeof version === 'string' && version.length > 0 ? version : null;
432
+ }
433
+ catch {
434
+ return null;
435
+ }
436
+ }
424
437
  export async function serveDesktopBridge(options = {}) {
425
438
  let handle = null;
426
439
  try {
427
440
  const runtime = await resolveDesktopBridgeRuntimeConfig(options);
428
- handle = await startPreparedDesktopBridge(await preflightDesktopBridge(runtime.config), { statePath: runtime.paths.state_path });
441
+ const supervised = desktopBridgeIsSupervised();
442
+ handle = await startPreparedDesktopBridge(await preflightDesktopBridge(runtime.config), {
443
+ statePath: runtime.paths.state_path,
444
+ versionSkew: {
445
+ readInstalledVersion: installedPackageVersion,
446
+ onSkew: (installedVersion) => {
447
+ process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.version_skew', running: PACKAGE_VERSION, installed: installedVersion, supervised, action: supervised ? 'restarting' : 'logged_only', secret_fields_redacted: true })}\n`);
448
+ if (!supervised)
449
+ return;
450
+ void handle?.stop().catch(() => undefined).finally(() => process.exit(64));
451
+ },
452
+ },
453
+ });
429
454
  process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', 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`);
430
455
  await waitForShutdown(handle);
431
456
  return { schema: 'sks.desktop-bridge-serve.v1', ok: true, status: 'stopped', state: handle.state };
@@ -106,11 +106,3 @@ export function contextGraphExperimentLogPath(root) {
106
106
  export function contextPackPath(root) {
107
107
  return path.join(contextGraphDir(root), 'context-pack.json');
108
108
  }
109
- export function contextGraphArtifactPaths(root) {
110
- return [
111
- contextGraphSnapshotPath(root),
112
- contextGraphMetaPath(root),
113
- contextGraphPrevSnapshotPath(root),
114
- contextGraphEventLogPath(root)
115
- ];
116
- }
@@ -0,0 +1,46 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { exists, PACKAGE_VERSION, readJson, runProcess } from '../../fsx.js';
4
+ export async function runDesktopBridgeRestageStage() {
5
+ const skip = (reason) => ({ ok: true, status: 'ok', actions: [reason], blockers: [], warnings: [] });
6
+ if (process.platform !== 'darwin')
7
+ return skip('desktop_bridge_restage_not_macos');
8
+ if (process.env.NODE_TEST_CONTEXT !== undefined)
9
+ return skip('desktop_bridge_restage_skipped_under_tests');
10
+ if (process.env.SKS_SKIP_BRIDGE_RESTAGE === '1')
11
+ return skip('desktop_bridge_restage_disabled');
12
+ const home = os.homedir();
13
+ const plist = path.join(home, 'Library', 'LaunchAgents', 'com.sneakoscope.desktop-bridge.plist');
14
+ if (!(await exists(plist)))
15
+ return skip('desktop_bridge_restage_no_launch_agent');
16
+ const state = await readJson(path.join(home, '.codex', 'sks', 'desktop-bridge-state.json'), null);
17
+ const runningVersion = typeof state?.sks_version === 'string' ? state.sks_version : null;
18
+ const pid = typeof state?.pid === 'number' && Number.isInteger(state.pid) && state.pid > 1 ? state.pid : null;
19
+ if (!pid)
20
+ return skip('desktop_bridge_restage_no_running_bridge');
21
+ try {
22
+ process.kill(pid, 0);
23
+ }
24
+ catch {
25
+ return skip('desktop_bridge_restage_no_running_bridge');
26
+ }
27
+ if (runningVersion === PACKAGE_VERSION)
28
+ return skip('desktop_bridge_restage_already_current');
29
+ const uid = typeof process.getuid === 'function' ? process.getuid() : null;
30
+ if (uid === null)
31
+ return skip('desktop_bridge_restage_no_uid');
32
+ const kick = await runProcess('/bin/launchctl', ['kickstart', '-k', `gui/${uid}/com.sneakoscope.desktop-bridge`], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch(() => null);
33
+ if (!kick || kick.code !== 0) {
34
+ return {
35
+ ok: true, status: 'ok',
36
+ actions: [],
37
+ blockers: [],
38
+ warnings: [`desktop_bridge_restage_kickstart_failed:${runningVersion || 'pre-8.6.2'}`]
39
+ };
40
+ }
41
+ return {
42
+ ok: true, status: 'ok',
43
+ actions: [`desktop_bridge_restarted:${runningVersion || 'pre-8.6.2'}:${PACKAGE_VERSION}`],
44
+ blockers: [], warnings: []
45
+ };
46
+ }
@@ -10,6 +10,7 @@ import { enforceRetention } from '../retention.js';
10
10
  import { COMMANDS } from '../../cli/command-registry.js';
11
11
  import { reconcileLegacyManagedGeneration } from '../init/legacy-generation-convergence.js';
12
12
  import { runConfigFastModeNormalizeStage } from './update-migration-state/fast-mode-config.js';
13
+ import { runDesktopBridgeRestageStage } from './update-migration-state/desktop-bridge-restage.js';
13
14
  import { runSessionStateSplitStage } from './update-migration-state/session-state-split.js';
14
15
  import { runHookTrustRefreshStage, runOtherHarnessCleanupStage } from './update-migration-state/simple-stages.js';
15
16
  import { compareSemVer } from './semver.js';
@@ -258,6 +259,11 @@ const UPDATE_MIGRATION_STAGES = [
258
259
  id: 'receipt-rotation',
259
260
  min_from_version: '0.0.0',
260
261
  run: runReceiptRotationStage
262
+ },
263
+ {
264
+ id: 'desktop-bridge-restage',
265
+ min_from_version: '0.0.0',
266
+ run: runDesktopBridgeRestageStage
261
267
  }
262
268
  ];
263
269
  async function runCurrentPublicSurfaceReconcileStage(root) {
@@ -325,7 +331,7 @@ async function runCurrentPublicSurfaceReconcileStage(root) {
325
331
  }
326
332
  };
327
333
  }
328
- async function runUpdateMigrationStages(root, opts = {}) {
334
+ export async function runUpdateMigrationStages(root, opts = {}) {
329
335
  const fromVersion = opts.fromVersion || null;
330
336
  const runs = [];
331
337
  for (const stage of UPDATE_MIGRATION_STAGES) {
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '9.0.0';
1
+ export const PACKAGE_VERSION = '9.0.1';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "9.0.0",
4
+ "version": "9.0.1",
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",