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 +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +1 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +9 -0
- package/dist/core/codex-lb/desktop-bridge/rejection-log.js +8 -0
- package/dist/core/codex-lb/desktop-bridge/server.js +24 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +4 -8
- package/dist/core/codex-lb/desktop-service.js +28 -3
- package/dist/core/triwiki/context-graph/paths.js +0 -8
- package/dist/core/update/update-migration-state/desktop-bridge-restage.js +46 -0
- package/dist/core/update/update-migration-state.js +7 -1
- 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.0.
|
|
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
|
|
package/dist/commands/bridge.js
CHANGED
|
@@ -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');
|
|
@@ -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.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '9.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.
|
|
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",
|