sneakoscope 9.0.0 → 9.0.2
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/bin/fast-inline.js +8 -4
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/doctor-console.js +291 -0
- package/dist/commands/doctor-helpers.js +4 -1
- package/dist/commands/doctor-profile.js +10 -0
- package/dist/commands/doctor.js +21 -246
- 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/fsx.js +17 -11
- 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.2** — 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/bin/fast-inline.js
CHANGED
|
@@ -95,12 +95,16 @@ async function hookUserPromptSubmitPerfInline() {
|
|
|
95
95
|
})}\n`);
|
|
96
96
|
}
|
|
97
97
|
function findProjectRootSync(fs, start) {
|
|
98
|
+
const home = process.env.HOME || process.env.USERPROFILE || null;
|
|
99
|
+
const homeDir = home ? stripTrailingSlash(home) : null;
|
|
98
100
|
let dir = normalizeStart(start);
|
|
99
101
|
for (;;) {
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
if (dir !== homeDir) {
|
|
103
|
+
if (fs.existsSync(joinPath(dir, '.sneakoscope')))
|
|
104
|
+
return dir;
|
|
105
|
+
if (fs.existsSync(joinPath(dir, 'AGENTS.md')) && fs.existsSync(joinPath(dir, 'package.json')))
|
|
106
|
+
return dir;
|
|
107
|
+
}
|
|
104
108
|
const parent = parentDir(dir);
|
|
105
109
|
if (parent === dir)
|
|
106
110
|
return null;
|
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');
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { CURRENT_CODEX_RUNTIME_CONTRACT } from '../core/codex-compat/codex-runtime-contract.js';
|
|
2
|
+
import { DOCTOR_CONSOLE_NOT_MEASURED, doctorDedupeStatus, doctorSkillStatus, formatCodexDoctorConsoleStatus, nativeCapabilityStatus, sksMenuBarRunningVersionConsoleLines, uniqueNativeManualActions } from './doctor-helpers.js';
|
|
3
|
+
export { DOCTOR_CONSOLE_NOT_MEASURED };
|
|
4
|
+
export function renderDoctorConsoleReport(result, extras) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
const line = (text) => lines.push(text);
|
|
7
|
+
const skippedOr = (skipped, measured) => (skipped ? DOCTOR_CONSOLE_NOT_MEASURED : measured);
|
|
8
|
+
const ready = result.ready || {};
|
|
9
|
+
const codex = result.codex || {};
|
|
10
|
+
const codexConfig = result.codex_config || {};
|
|
11
|
+
const oauthDiagnostic = result.oauth_callback_port_diagnostic || { conflict: false, listeners: [], warnings: [] };
|
|
12
|
+
const context7Repair = result.context7_repair || {};
|
|
13
|
+
const codexStartupRepair = result.codex_startup_repair || {};
|
|
14
|
+
const codexConfigSyntaxRepair = result.codex_config_syntax_repair;
|
|
15
|
+
const rust = result.rust || {};
|
|
16
|
+
const codexApp = result.codex_app || {};
|
|
17
|
+
const runtimeReadiness = result.runtime_readiness || { notes: [], repair_actions: [] };
|
|
18
|
+
const codexNativeFeatureMatrix = result.codex_native_feature_matrix || {};
|
|
19
|
+
const repair = result.repair || {};
|
|
20
|
+
const doctorNativeCapabilityRepair = repair.doctor_native_capability;
|
|
21
|
+
const commandAliasCleanup = result.command_aliases || {};
|
|
22
|
+
const codexAppHarnessMatrix = result.codex_app_harness_matrix || {};
|
|
23
|
+
const codexAppUi = result.codex_app_ui || {};
|
|
24
|
+
const sksMenuBar = result.sks_menubar || {};
|
|
25
|
+
const providerContext = result.provider_context || {};
|
|
26
|
+
const imagegen = result.imagegen || {};
|
|
27
|
+
const imagegenRepair = result.imagegen_repair || {};
|
|
28
|
+
const computerUseRepair = repair.computer_use || {};
|
|
29
|
+
const browserUseRepair = repair.browser_use || {};
|
|
30
|
+
const mcpTransportCollisionRepair = repair.mcp_transport_collision;
|
|
31
|
+
const codexCurrentAppSection = result.codex_current_app || {};
|
|
32
|
+
const codexCurrentAppDoctor = codexCurrentAppSection.doctor || {};
|
|
33
|
+
const pluginPolicy = codexCurrentAppSection.plugin_app_template_policy;
|
|
34
|
+
const desktopBridge = result.desktop_bridge || {};
|
|
35
|
+
const permissionProfiles = result.codex_permission_profiles || {};
|
|
36
|
+
const configRepair = repair.codex_config;
|
|
37
|
+
const migrationJournal = repair.migration_journal;
|
|
38
|
+
const sksUpdate = repair.sks_update;
|
|
39
|
+
const globalSksInstallCleanup = repair.global_sks_installs;
|
|
40
|
+
line('SKS Doctor');
|
|
41
|
+
for (const warning of result.arg_warnings || [])
|
|
42
|
+
line(`Argument warning: ${warning}`);
|
|
43
|
+
for (const warning of result.official_subagent_config?.warnings || [])
|
|
44
|
+
line(`Official subagent warning: ${warning}`);
|
|
45
|
+
line(`Root: ${result.root}`);
|
|
46
|
+
if (extras.rootIsHome) {
|
|
47
|
+
line('Note: this is your home directory, not a project — project rows below describe the home folder. Run from your project: cd <your-project> && sks doctor');
|
|
48
|
+
}
|
|
49
|
+
line(`Node: ${result.node?.ok ? 'ok' : 'fail'} ${result.node?.version || ''}`);
|
|
50
|
+
line(`Codex: ${codex.bin ? 'ok' : 'missing'} ${codex.version || ''}`);
|
|
51
|
+
if (oauthDiagnostic.conflict) {
|
|
52
|
+
const listeners = (oauthDiagnostic.listeners || [])
|
|
53
|
+
.map((listener) => `${listener.command} pid ${listener.pid} ${listener.address}`)
|
|
54
|
+
.join(', ');
|
|
55
|
+
line(`OAuth callback port 1455: warning (${listeners})`);
|
|
56
|
+
for (const action of extras.oauthCallbackOperatorActions)
|
|
57
|
+
line(` action: ${action}`);
|
|
58
|
+
}
|
|
59
|
+
const actual = (codexConfig.checks || []).find((check) => check.name === 'actual_codex_cli_config_load');
|
|
60
|
+
line('Project config:');
|
|
61
|
+
line(` node read: ${ready.codex_config_readable_by_node ? 'ok' : 'failed'}`);
|
|
62
|
+
line(` codex cli read: ${ready.codex_config_readable_by_codex_cli ? 'ok' : (actual?.status || 'failed')}`);
|
|
63
|
+
line('Context7 MCP:');
|
|
64
|
+
line(` transport: ${context7Repair.preferred_transport || 'remote'}`);
|
|
65
|
+
line(` repair: ${context7Repair.ok ? 'ok' : 'blocked'}`);
|
|
66
|
+
for (const action of context7Repair.actions || [])
|
|
67
|
+
line(` - ${action}`);
|
|
68
|
+
for (const warning of context7Repair.warnings || [])
|
|
69
|
+
line(` warning: ${warning}`);
|
|
70
|
+
line('Codex startup config:');
|
|
71
|
+
line(` repair: ${codexStartupRepair.ok ? 'ok' : 'blocked'}`);
|
|
72
|
+
for (const action of codexStartupRepair.actions || [])
|
|
73
|
+
line(` - ${action}`);
|
|
74
|
+
for (const action of codexStartupRepair.manual_actions || [])
|
|
75
|
+
line(` manual: ${action}`);
|
|
76
|
+
for (const warning of codexStartupRepair.warnings || [])
|
|
77
|
+
line(` warning: ${warning}`);
|
|
78
|
+
if (codexConfigSyntaxRepair) {
|
|
79
|
+
line('Codex config syntax:');
|
|
80
|
+
line(` repair: ${codexConfigSyntaxRepair.ok ? 'ok' : 'blocked'}`);
|
|
81
|
+
for (const action of codexConfigSyntaxRepair.actions || [])
|
|
82
|
+
line(` - ${action}`);
|
|
83
|
+
for (const action of codexConfigSyntaxRepair.manual_actions || [])
|
|
84
|
+
line(` manual: ${action}`);
|
|
85
|
+
for (const warning of codexConfigSyntaxRepair.warnings || [])
|
|
86
|
+
line(` warning: ${warning}`);
|
|
87
|
+
}
|
|
88
|
+
line(` codex doctor: ${formatCodexDoctorConsoleStatus(result.codex_doctor)}`);
|
|
89
|
+
line(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
|
|
90
|
+
line(`Codex App: ${codexApp.skipped === true ? DOCTOR_CONSOLE_NOT_MEASURED : ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
|
|
91
|
+
const featureMatrixSkipped = codexNativeFeatureMatrix.skipped === true;
|
|
92
|
+
line('SKS Runtime Readiness:');
|
|
93
|
+
line(` Codex Native: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.codex_native))}`);
|
|
94
|
+
line(` Loop Mesh: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.loop_mesh))}`);
|
|
95
|
+
line(` QA Visual: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.qa_visual))}`);
|
|
96
|
+
line(` Research Sources: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.research_sources))}`);
|
|
97
|
+
line(` Image Follow-up: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.image_followup))}`);
|
|
98
|
+
if (!featureMatrixSkipped) {
|
|
99
|
+
for (const note of runtimeReadiness.notes || [])
|
|
100
|
+
line(` ${note}`);
|
|
101
|
+
if ((runtimeReadiness.repair_actions || []).length) {
|
|
102
|
+
line('Repair actions:');
|
|
103
|
+
for (const action of runtimeReadiness.repair_actions)
|
|
104
|
+
line(` - ${action}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const nativeCapabilitySource = doctorNativeCapabilityRepair?.native_capabilities;
|
|
108
|
+
const nativeCapabilitiesSkipped = nativeCapabilitySource?.skipped === true;
|
|
109
|
+
const nativeCapabilityRows = Array.isArray(nativeCapabilitySource?.capabilities)
|
|
110
|
+
? nativeCapabilitySource.capabilities
|
|
111
|
+
: [];
|
|
112
|
+
const capabilityRow = (id, fallback) => skippedOr(nativeCapabilitiesSkipped, nativeCapabilityStatus(nativeCapabilityRows, id, fallback));
|
|
113
|
+
line('SKS Native Capabilities:');
|
|
114
|
+
line(` image generation: ${capabilityRow('image_generation', 'repair_required')}`);
|
|
115
|
+
line(` image follow-up edit: ${capabilityRow('image_followup_edit', 'degraded')}`);
|
|
116
|
+
line(` computer use: ${capabilityRow('computer_use', 'manual_required')}`);
|
|
117
|
+
line(` Chrome/web review: ${capabilityRow('chrome_web_review', 'manual_required')}`);
|
|
118
|
+
line(` app screenshot: ${capabilityRow('codex_app_screenshot', 'degraded')}`);
|
|
119
|
+
line(` app handoff: ${capabilityRow('app_handoff', 'unavailable')}`);
|
|
120
|
+
line(` image path exposure: ${capabilityRow('image_path_exposure', 'fallback')}`);
|
|
121
|
+
const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
|
|
122
|
+
if (nativeManualActions.length) {
|
|
123
|
+
line(' manual next actions:');
|
|
124
|
+
for (const action of nativeManualActions)
|
|
125
|
+
line(` - ${action}`);
|
|
126
|
+
}
|
|
127
|
+
line('SKS Skills:');
|
|
128
|
+
line(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
|
|
129
|
+
line(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
|
|
130
|
+
line('SKS Current Command Surface:');
|
|
131
|
+
line(` status: ${commandAliasCleanup.status || (commandAliasCleanup.ok ? 'clean' : 'blocked')}`);
|
|
132
|
+
line(` canonical commands: ${commandAliasCleanup.canonical_command_count ?? 0}`);
|
|
133
|
+
const managedRuntimeCleanup = commandAliasCleanup.cleanup?.managed_runtime;
|
|
134
|
+
if (managedRuntimeCleanup) {
|
|
135
|
+
line(` managed items reconciled: ${managedRuntimeCleanup.removed_managed_artifact_count ?? 0}`);
|
|
136
|
+
line(` user-authored collisions preserved: ${managedRuntimeCleanup.preserved_user_file_count ?? 0}`);
|
|
137
|
+
}
|
|
138
|
+
if (commandAliasCleanup.report_path)
|
|
139
|
+
line(` report: ${commandAliasCleanup.report_path}`);
|
|
140
|
+
line('Secret preservation:');
|
|
141
|
+
line(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
|
|
142
|
+
line(' raw secret values: never recorded');
|
|
143
|
+
line(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
|
|
144
|
+
const harnessSkipped = codexAppHarnessMatrix.skipped === true;
|
|
145
|
+
const appFeatures = codexAppHarnessMatrix.app_features || {};
|
|
146
|
+
const sksIntegrations = codexAppHarnessMatrix.sks_integrations || {};
|
|
147
|
+
line('Codex App Harness:');
|
|
148
|
+
line(` plugins: ${skippedOr(harnessSkipped, appFeatures.plugin_json ? 'ok' : 'degraded')}`);
|
|
149
|
+
line(` hook approval: ${skippedOr(harnessSkipped, appFeatures.hook_approval_state_detectable ? 'ok' : 'unknown')}`);
|
|
150
|
+
line(` skills: ${skippedOr(harnessSkipped, sksIntegrations.dollar_skills_synced ? 'ok' : 'degraded')}`);
|
|
151
|
+
line(` agent roles: ${skippedOr(harnessSkipped, sksIntegrations.agent_roles_synced ? 'ok' : 'degraded')}`);
|
|
152
|
+
line(` native agent_type: ${skippedOr(harnessSkipped, appFeatures.agent_type_supported ? 'ok' : 'fallback message-role')}`);
|
|
153
|
+
line(` init-deep memory: ${skippedOr(harnessSkipped, sksIntegrations.init_deep_available ? 'available' : 'missing')}`);
|
|
154
|
+
line(` loop mesh app profile: ${skippedOr(harnessSkipped, sksIntegrations.loop_mesh_app_profile_available ? 'available' : 'missing')}`);
|
|
155
|
+
line('Codex App UI:');
|
|
156
|
+
line(` fast selector: ${codexAppUi.fast_selector || 'unknown'}`);
|
|
157
|
+
line(` provider selector: ${codexAppUi.provider_selector || 'unknown'}`);
|
|
158
|
+
if (Array.isArray(codexAppUi.provider_blockers) && codexAppUi.provider_blockers.length) {
|
|
159
|
+
line(` provider blockers: ${codexAppUi.provider_blockers.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(codexAppUi.provider_actions) && codexAppUi.provider_actions.length) {
|
|
162
|
+
line(' provider actions:');
|
|
163
|
+
for (const action of codexAppUi.provider_actions)
|
|
164
|
+
line(` - ${action}`);
|
|
165
|
+
}
|
|
166
|
+
line(` host-owned config: ${codexAppUi.host_owned_config || 'unknown'}`);
|
|
167
|
+
if (Array.isArray(codexAppUi.actions) && codexAppUi.actions.some((action) => action.changed)) {
|
|
168
|
+
line(' repaired files:');
|
|
169
|
+
for (const action of codexAppUi.actions.filter((entry) => entry.changed))
|
|
170
|
+
line(` - ${action.file}${action.backup_path ? ` (backup ${action.backup_path})` : ''}`);
|
|
171
|
+
}
|
|
172
|
+
if (codexAppUi.next_action)
|
|
173
|
+
line(` next action: ${codexAppUi.next_action}`);
|
|
174
|
+
line('SKS Menu Bar:');
|
|
175
|
+
line(` status: ${sksMenuBar.status || (sksMenuBar.ok ? 'ok' : 'blocked')}`);
|
|
176
|
+
for (const versionLine of sksMenuBarRunningVersionConsoleLines(sksMenuBar))
|
|
177
|
+
line(versionLine);
|
|
178
|
+
const menubarPhase = result.doctor_fix_transaction?.phases?.find((phase) => phase?.id === 'sks_menubar');
|
|
179
|
+
if (menubarPhase) {
|
|
180
|
+
const menubarSummary = menubarPhase.ok
|
|
181
|
+
? (menubarPhase.repaired ? 'repaired' : 'verified')
|
|
182
|
+
: `blocked(${(menubarPhase.blockers || []).join(', ') || 'unknown'})`;
|
|
183
|
+
line(` menubar: ${menubarSummary}`);
|
|
184
|
+
}
|
|
185
|
+
if (sksMenuBar.app_path)
|
|
186
|
+
line(` app: ${sksMenuBar.app_path}`);
|
|
187
|
+
if (sksMenuBar.launch_agent_path)
|
|
188
|
+
line(` launch agent: ${sksMenuBar.launch_agent_path}`);
|
|
189
|
+
if (Array.isArray(sksMenuBar.blockers) && sksMenuBar.blockers.length)
|
|
190
|
+
line(` blockers: ${sksMenuBar.blockers.join(', ')}`);
|
|
191
|
+
if (Array.isArray(sksMenuBar.warnings) && sksMenuBar.warnings.length)
|
|
192
|
+
line(` warnings: ${sksMenuBar.warnings.join(', ')}`);
|
|
193
|
+
line(`Provider: ${providerContext.provider || 'unknown'} ${providerContext.service_tier || ''} (${providerContext.source || 'unknown'}, ${providerContext.confidence || 'low'})`);
|
|
194
|
+
const imagegenReady = imagegen.auth_readiness;
|
|
195
|
+
if (imagegenReady) {
|
|
196
|
+
const paths = imagegenReady.available_paths?.length ? imagegenReady.available_paths.join(', ') : 'none';
|
|
197
|
+
line(`Image Gen: auth=${imagegenReady.auth_mode} | headless_auto=${imagegenReady.headless_auto_available ? 'available' : 'unavailable'} | paths: ${paths}`);
|
|
198
|
+
if (!imagegenReady.headless_auto_available) {
|
|
199
|
+
for (const action of imagegenReady.next_actions || [])
|
|
200
|
+
line(` - ${action}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
line(`Image Gen repair: ${skippedOr(imagegenRepair.skipped === true, String(extras.nativeCapabilityReadiness?.imagegen?.status))}`);
|
|
204
|
+
for (const action of imagegenRepair.manual_actions || [])
|
|
205
|
+
line(` - ${action}`);
|
|
206
|
+
line(`Computer Use repair: ${skippedOr(computerUseRepair.skipped === true, computerUseRepair.recovered ? 'ok' : computerUseRepair.attempted ? 'blocked' : 'not-needed')}`);
|
|
207
|
+
for (const action of computerUseRepair.next_actions || [])
|
|
208
|
+
line(` - ${action}`);
|
|
209
|
+
line(`Browser Use repair: ${skippedOr(browserUseRepair.skipped === true, browserUseRepair.recovered ? 'ok' : browserUseRepair.attempted ? 'blocked' : 'not-needed')}`);
|
|
210
|
+
for (const action of browserUseRepair.next_actions || [])
|
|
211
|
+
line(` - ${action}`);
|
|
212
|
+
if (mcpTransportCollisionRepair) {
|
|
213
|
+
const collisionCount = (mcpTransportCollisionRepair.servers || []).filter((server) => server.status === 'collision_resolved').length;
|
|
214
|
+
line(`MCP transport collision repair: ${mcpTransportCollisionRepair.ok ? 'ok' : 'blocked'}${collisionCount ? ` (${collisionCount} resolved)` : ''}`);
|
|
215
|
+
}
|
|
216
|
+
line(`Agent bridge: ${extras.agentBridgeManifestExists ? 'manifest present' : 'not set up'}${extras.agentBridgeManifestExists ? '' : ' — run `sks agent-bridge setup` to publish the manifest and register with an MCP host'}`);
|
|
217
|
+
const codexCurrentApp = extras.codexCurrentAppCapability?.report || {};
|
|
218
|
+
const currentAppSkipped = extras.codexCurrentAppCapability?.skipped === true;
|
|
219
|
+
line('Codex current compatibility:');
|
|
220
|
+
line(` target: ${CURRENT_CODEX_RUNTIME_CONTRACT.targetTag}`);
|
|
221
|
+
line(` runtime: ${codex.version || 'unknown'}`);
|
|
222
|
+
line(` multi-agent mode: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.multi_agent_mode?.ok ? 'verified' : 'unverified')}`);
|
|
223
|
+
line(` rollout budget: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.rollout_budget?.ok ? 'verified' : 'unverified')}`);
|
|
224
|
+
line(` indexed search: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.indexed_web_search?.ok ? 'verified' : 'unverified')}`);
|
|
225
|
+
line(` current time: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.current_time_read?.ok ? 'verified' : 'unverified')}`);
|
|
226
|
+
line('Current Codex app features:');
|
|
227
|
+
line(` /app handoff: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_app_handoff ? 'ok' : 'unavailable')}`);
|
|
228
|
+
line(` plugin JSON: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_plugin_json ? 'ok' : 'unavailable')}`);
|
|
229
|
+
line(` image path exposure: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_image_path_exposure ? 'ok' : 'unavailable')}`);
|
|
230
|
+
line(` OAuth MCP pre-refresh: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_oauth_mcp_prerefresh ? 'ok' : 'unavailable')}`);
|
|
231
|
+
const pluginInventorySkipped = extras.pluginInventory?.skipped === true;
|
|
232
|
+
const pluginReport = extras.pluginInventory?.report;
|
|
233
|
+
const plugins = pluginReport?.plugins || [];
|
|
234
|
+
const remoteMcpCount = plugins.flatMap((plugin) => plugin.remote_mcp_servers || []).length;
|
|
235
|
+
const unavailableTemplates = pluginPolicy?.unavailable_app_templates?.length || 0;
|
|
236
|
+
line(`Codex plugins: ${skippedOr(pluginInventorySkipped, pluginReport ? 'ok' : 'warning')}`);
|
|
237
|
+
line(` Remote MCP servers: ${pluginInventorySkipped ? DOCTOR_CONSOLE_NOT_MEASURED : `${remoteMcpCount} candidates`}`);
|
|
238
|
+
line(` Unavailable app templates: ${pluginInventorySkipped ? DOCTOR_CONSOLE_NOT_MEASURED : unavailableTemplates}`);
|
|
239
|
+
for (const warning of pluginPolicy?.doctor_warnings || [])
|
|
240
|
+
line(` warning: ${warning}`);
|
|
241
|
+
if (codexCurrentAppDoctor.fixed?.length)
|
|
242
|
+
line(` doctor --fix repaired: ${codexCurrentAppDoctor.fixed.join(', ')}`);
|
|
243
|
+
line(`Desktop Bridge: ${desktopBridge.ok ? 'ready' : 'blocked'} (${desktopBridge.status?.readiness?.state || 'unavailable'})`);
|
|
244
|
+
for (const providerId of ['codex-lb', 'openrouter']) {
|
|
245
|
+
const provider = desktopBridge.providers?.[providerId];
|
|
246
|
+
if (!provider)
|
|
247
|
+
continue;
|
|
248
|
+
line(` ${providerId}: ${provider.enabled ? 'enabled' : 'disabled'}; credential ${provider.credential?.state || 'unknown'} (${provider.credential?.source || 'none'}); endpoint ${provider.endpoint?.configured ? 'configured' : 'missing'}`);
|
|
249
|
+
}
|
|
250
|
+
for (const warning of desktopBridge.warnings || [])
|
|
251
|
+
line(` warning: ${warning}`);
|
|
252
|
+
for (const blocker of desktopBridge.blockers || [])
|
|
253
|
+
line(` blocker: ${blocker}`);
|
|
254
|
+
if (!desktopBridge.ok)
|
|
255
|
+
for (const action of desktopBridge.recovery_actions || [])
|
|
256
|
+
line(` action: ${action}`);
|
|
257
|
+
line(`Permissions: config profile and permission profile are tracked separately (${permissionProfiles.codex_config_profile_field}, ${permissionProfiles.codex_permission_profile_field})`);
|
|
258
|
+
line('Ready:');
|
|
259
|
+
line(` cli_ready: ${ready.cli_ready ? 'yes' : 'no'}`);
|
|
260
|
+
line(` mad_ready: ${ready.mad_ready ? 'yes' : 'no'}`);
|
|
261
|
+
line(` managed_state_current: ${ready.managed_state_current ? 'yes' : 'no'}`);
|
|
262
|
+
line(` core_ready: ${ready.core_ready ? 'yes' : 'no'}`);
|
|
263
|
+
line(` center_ready: ${ready.center_ready ? 'yes' : 'no'}${ready.center_attempted ? ' (repair attempted)' : ' (not attempted)'}`);
|
|
264
|
+
line(` ready: ${ready.ready ? 'yes' : 'no'}`);
|
|
265
|
+
if (!ready.ready) {
|
|
266
|
+
line('Primary blocker:');
|
|
267
|
+
line(` ${ready.primary_blocker || 'unknown'}`);
|
|
268
|
+
}
|
|
269
|
+
if (configRepair?.repair_actions?.length) {
|
|
270
|
+
line('What I fixed:');
|
|
271
|
+
for (const action of configRepair.repair_actions)
|
|
272
|
+
line(` - ${action.name}: ${action.ok ? 'ok' : 'failed'}`);
|
|
273
|
+
}
|
|
274
|
+
if (migrationJournal?.journal_path) {
|
|
275
|
+
line(`Migration journal: ${migrationJournal.journal_path} (${migrationJournal.event_count} events, ${migrationJournal.mutations_without_rollback} without rollback)`);
|
|
276
|
+
}
|
|
277
|
+
if (sksUpdate) {
|
|
278
|
+
line(`SKS update: ${sksUpdate.status}${sksUpdate.latest ? ` latest ${sksUpdate.latest}` : ''}${sksUpdate.error ? ` (${sksUpdate.error})` : ''}`);
|
|
279
|
+
}
|
|
280
|
+
if (globalSksInstallCleanup) {
|
|
281
|
+
line(`Global SKS installs: kept ${globalSksInstallCleanup.kept?.length ?? 0}, removed ${globalSksInstallCleanup.removed?.filter((entry) => entry.ok).length ?? 0}, source repo exempt ${globalSksInstallCleanup.candidates?.filter((entry) => entry.source_repo_exempt).length ?? 0}`);
|
|
282
|
+
if (globalSksInstallCleanup.npm_cache)
|
|
283
|
+
line(`NPM cache cleanup: ${globalSksInstallCleanup.npm_cache.status}`);
|
|
284
|
+
}
|
|
285
|
+
if (!ready.ready && ready.next_actions?.length) {
|
|
286
|
+
line('What still needs you:');
|
|
287
|
+
for (const action of ready.next_actions)
|
|
288
|
+
line(` - ${action}`);
|
|
289
|
+
}
|
|
290
|
+
return lines;
|
|
291
|
+
}
|
|
@@ -258,8 +258,11 @@ export function buildCodexAppUiDiagnosticFailure(apply, error) {
|
|
|
258
258
|
blockers: [error instanceof Error ? error.message : String(error)]
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
|
+
export const DOCTOR_CONSOLE_NOT_MEASURED = 'not measured (run: sks doctor --full)';
|
|
261
262
|
export function formatCodexDoctorConsoleStatus(report) {
|
|
262
|
-
if (!report
|
|
263
|
+
if (!report)
|
|
264
|
+
return DOCTOR_CONSOLE_NOT_MEASURED;
|
|
265
|
+
if (report.available !== true)
|
|
263
266
|
return 'unavailable';
|
|
264
267
|
return report.disposition || (report.exit_code === 0 ? 'pass' : 'warn');
|
|
265
268
|
}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { flag } from '../cli/args.js';
|
|
2
3
|
import { sksMenuBarRestartDeferred } from '../core/codex-app/menubar/index.js';
|
|
4
|
+
export function doctorGlobalOnlySelection(input) {
|
|
5
|
+
if (!input.doctorFix)
|
|
6
|
+
return { global_only: false, reason: null };
|
|
7
|
+
if (flag(input.args, '--global-only'))
|
|
8
|
+
return { global_only: true, reason: 'explicit_flag' };
|
|
9
|
+
if (path.resolve(input.root) === path.resolve(input.home))
|
|
10
|
+
return { global_only: true, reason: 'home_is_root' };
|
|
11
|
+
return { global_only: false, reason: null };
|
|
12
|
+
}
|
|
3
13
|
export function doctorProfileFromArgs(args = [], doctorFix = false) {
|
|
4
14
|
const explicit = readOption(args, '--profile');
|
|
5
15
|
if (isDoctorProfile(explicit))
|
package/dist/commands/doctor.js
CHANGED
|
@@ -29,7 +29,8 @@ import { sweepSksTempDirs } from '../core/retention.js';
|
|
|
29
29
|
import { detectImagegenCapability } from '../core/imagegen/imagegen-capability.js';
|
|
30
30
|
import { CURRENT_CODEX_RUNTIME_CONTRACT } from '../core/codex-compat/codex-runtime-contract.js';
|
|
31
31
|
import { formatHarnessConflictReport, scanHarnessConflicts } from '../core/harness-conflicts.js';
|
|
32
|
-
import { doctorArgWarnings as baseDoctorArgWarnings, doctorMenuBarInstallPolicy, doctorPhaseIdsForProfile, doctorProfileFromArgs, doctorProfileRequiresDesktopBridgeReadiness } from './doctor-profile.js';
|
|
32
|
+
import { doctorArgWarnings as baseDoctorArgWarnings, doctorGlobalOnlySelection, doctorMenuBarInstallPolicy, doctorPhaseIdsForProfile, doctorProfileFromArgs, doctorProfileRequiresDesktopBridgeReadiness } from './doctor-profile.js';
|
|
33
|
+
import { renderDoctorConsoleReport } from './doctor-console.js';
|
|
33
34
|
import { buildCodexAppUiDiagnosticFailure, buildRuntimeReadiness, captureCodexConfigSnapshot, deferredNativeRepair, doctorDedupeStatus, doctorSkillStatus, fallbackCodexNativeFeatureMatrix, formatCodexDoctorConsoleStatus, installScopeFromArgs, isMigrationUserOwnedProjectConfigBlocker, mergeObservedCodexStartupWarnings, nativeCapabilityStatus, readOption, rebootstrapSksMenuBarLaunchdForDoctorFix, sksMenuBarRunningVersionConsoleLines, uniqueNativeManualActions, writeFixMigrationJournal, writeJsonReportFile } from './doctor-helpers.js';
|
|
34
35
|
export { doctorMenuBarInstallPolicy, doctorProfileFromArgs, doctorProfileRequiresDesktopBridgeReadiness } from './doctor-profile.js';
|
|
35
36
|
export { buildCodexAppUiDiagnosticFailure, buildRuntimeReadiness, formatCodexDoctorConsoleStatus, rebootstrapSksMenuBarLaunchdForDoctorFix, sksMenuBarRunningVersionConsoleLines };
|
|
@@ -134,7 +135,8 @@ export async function run(_command, args = [], deps = {}) {
|
|
|
134
135
|
return report;
|
|
135
136
|
}
|
|
136
137
|
const doctorFix = flag(args, '--fix');
|
|
137
|
-
const
|
|
138
|
+
const homeDir = path.resolve(deps.home || process.env.HOME || os.homedir());
|
|
139
|
+
const globalOnly = doctorGlobalOnlySelection({ args, doctorFix, root, home: homeDir }).global_only;
|
|
138
140
|
if (doctorFix) {
|
|
139
141
|
const conflictScan = await scanHarnessConflicts(root);
|
|
140
142
|
if (conflictScan.hard_block) {
|
|
@@ -167,9 +169,7 @@ export async function run(_command, args = [], deps = {}) {
|
|
|
167
169
|
if (!doctorFix && flag(args, '--json') && doctorProfile === 'fast')
|
|
168
170
|
return runDoctorJsonFastPath(args, root);
|
|
169
171
|
if (doctorFix) {
|
|
170
|
-
const guardRoot = globalOnly
|
|
171
|
-
? path.resolve(deps.home || process.env.HOME || os.homedir())
|
|
172
|
-
: root;
|
|
172
|
+
const guardRoot = globalOnly ? homeDir : root;
|
|
173
173
|
return withSecretPreservationGuard(guardRoot, 'doctor-fix', async () => (globalOnly
|
|
174
174
|
? runDoctorGlobalOnlyFix(args, root, deps)
|
|
175
175
|
: runDoctor(args, root, doctorFix, deps)));
|
|
@@ -248,6 +248,7 @@ export async function executeDoctorGlobalOnlyFix(args = [], root, deps = {}) {
|
|
|
248
248
|
: [])
|
|
249
249
|
].map(String).filter(Boolean))];
|
|
250
250
|
const ok = blockers.length === 0;
|
|
251
|
+
const projectRootAliasDetected = path.resolve(root) === home;
|
|
251
252
|
return {
|
|
252
253
|
schema: 'sks.doctor-status.v3',
|
|
253
254
|
elapsed_ms: Date.now() - startedAtMs,
|
|
@@ -258,7 +259,7 @@ export async function executeDoctorGlobalOnlyFix(args = [], root, deps = {}) {
|
|
|
258
259
|
install_scope: 'global',
|
|
259
260
|
root,
|
|
260
261
|
home,
|
|
261
|
-
project_root_alias_detected:
|
|
262
|
+
project_root_alias_detected: projectRootAliasDetected,
|
|
262
263
|
no_project_writes_performed: true,
|
|
263
264
|
project_phases_skipped: [
|
|
264
265
|
'project_skills_reconcile',
|
|
@@ -277,6 +278,9 @@ export async function executeDoctorGlobalOnlyFix(args = [], root, deps = {}) {
|
|
|
277
278
|
desktop_bridge: desktopBridge,
|
|
278
279
|
blockers,
|
|
279
280
|
next_actions: [
|
|
281
|
+
...(projectRootAliasDetected
|
|
282
|
+
? ['Project checks were skipped because this directory is your home folder. Run them from your project: cd <your-project> && sks doctor --fix']
|
|
283
|
+
: []),
|
|
280
284
|
...(desktopBridge.ok === false ? desktopBridge.recovery_actions || [] : []),
|
|
281
285
|
'Run `sks doctor --fix --json` from a specific project directory when project-scoped repair is required.'
|
|
282
286
|
]
|
|
@@ -1597,246 +1601,17 @@ async function runDoctor(args = [], root, doctorFix, deps = {}) {
|
|
|
1597
1601
|
process.exitCode = 1;
|
|
1598
1602
|
return;
|
|
1599
1603
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
.join(', ');
|
|
1612
|
-
console.log(`OAuth callback port 1455: warning (${listeners})`);
|
|
1613
|
-
for (const action of oauthCallbackOperatorActions)
|
|
1614
|
-
console.log(` action: ${action}`);
|
|
1615
|
-
}
|
|
1616
|
-
const actual = (codexConfig.checks || []).find((check) => check.name === 'actual_codex_cli_config_load');
|
|
1617
|
-
console.log('Project config:');
|
|
1618
|
-
console.log(` node read: ${ready.codex_config_readable_by_node ? 'ok' : 'failed'}`);
|
|
1619
|
-
console.log(` codex cli read: ${ready.codex_config_readable_by_codex_cli ? 'ok' : (actual?.status || 'failed')}`);
|
|
1620
|
-
console.log('Context7 MCP:');
|
|
1621
|
-
console.log(` transport: ${context7Repair.preferred_transport || 'remote'}`);
|
|
1622
|
-
console.log(` repair: ${context7Repair.ok ? 'ok' : 'blocked'}`);
|
|
1623
|
-
for (const action of context7Repair.actions || [])
|
|
1624
|
-
console.log(` - ${action}`);
|
|
1625
|
-
for (const warning of context7Repair.warnings || [])
|
|
1626
|
-
console.log(` warning: ${warning}`);
|
|
1627
|
-
console.log('Codex startup config:');
|
|
1628
|
-
console.log(` repair: ${codexStartupRepair.ok ? 'ok' : 'blocked'}`);
|
|
1629
|
-
for (const action of codexStartupRepair.actions || [])
|
|
1630
|
-
console.log(` - ${action}`);
|
|
1631
|
-
for (const action of codexStartupRepair.manual_actions || [])
|
|
1632
|
-
console.log(` manual: ${action}`);
|
|
1633
|
-
for (const warning of codexStartupRepair.warnings || [])
|
|
1634
|
-
console.log(` warning: ${warning}`);
|
|
1635
|
-
if (codexConfigSyntaxRepair) {
|
|
1636
|
-
console.log('Codex config syntax:');
|
|
1637
|
-
console.log(` repair: ${codexConfigSyntaxRepair.ok ? 'ok' : 'blocked'}`);
|
|
1638
|
-
for (const action of codexConfigSyntaxRepair.actions || [])
|
|
1639
|
-
console.log(` - ${action}`);
|
|
1640
|
-
for (const action of codexConfigSyntaxRepair.manual_actions || [])
|
|
1641
|
-
console.log(` manual: ${action}`);
|
|
1642
|
-
for (const warning of codexConfigSyntaxRepair.warnings || [])
|
|
1643
|
-
console.log(` warning: ${warning}`);
|
|
1644
|
-
}
|
|
1645
|
-
console.log(` codex doctor: ${formatCodexDoctorConsoleStatus(authoritativeCodexDoctor)}`);
|
|
1646
|
-
console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
|
|
1647
|
-
console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
|
|
1648
|
-
console.log('SKS Runtime Readiness:');
|
|
1649
|
-
console.log(` Codex Native: ${runtimeReadiness.codex_native}`);
|
|
1650
|
-
console.log(` Loop Mesh: ${runtimeReadiness.loop_mesh}`);
|
|
1651
|
-
console.log(` QA Visual: ${runtimeReadiness.qa_visual}`);
|
|
1652
|
-
console.log(` Research Sources: ${runtimeReadiness.research_sources}`);
|
|
1653
|
-
console.log(` Image Follow-up: ${runtimeReadiness.image_followup}`);
|
|
1654
|
-
for (const note of runtimeReadiness.notes)
|
|
1655
|
-
console.log(` ${note}`);
|
|
1656
|
-
if (runtimeReadiness.repair_actions.length) {
|
|
1657
|
-
console.log('Repair actions:');
|
|
1658
|
-
for (const action of runtimeReadiness.repair_actions)
|
|
1659
|
-
console.log(` - ${action}`);
|
|
1660
|
-
}
|
|
1661
|
-
const nativeCapabilityRows = Array.isArray(doctorNativeCapabilityRepair?.native_capabilities?.capabilities)
|
|
1662
|
-
? doctorNativeCapabilityRepair.native_capabilities.capabilities
|
|
1663
|
-
: [];
|
|
1664
|
-
console.log('SKS Native Capabilities:');
|
|
1665
|
-
console.log(` image generation: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_generation', 'repair_required')}`);
|
|
1666
|
-
console.log(` image follow-up edit: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_followup_edit', 'degraded')}`);
|
|
1667
|
-
console.log(` computer use: ${nativeCapabilityStatus(nativeCapabilityRows, 'computer_use', 'manual_required')}`);
|
|
1668
|
-
console.log(` Chrome/web review: ${nativeCapabilityStatus(nativeCapabilityRows, 'chrome_web_review', 'manual_required')}`);
|
|
1669
|
-
console.log(` app screenshot: ${nativeCapabilityStatus(nativeCapabilityRows, 'codex_app_screenshot', 'degraded')}`);
|
|
1670
|
-
console.log(` app handoff: ${nativeCapabilityStatus(nativeCapabilityRows, 'app_handoff', 'unavailable')}`);
|
|
1671
|
-
console.log(` image path exposure: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_path_exposure', 'fallback')}`);
|
|
1672
|
-
const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
|
|
1673
|
-
if (nativeManualActions.length) {
|
|
1674
|
-
console.log(' manual next actions:');
|
|
1675
|
-
for (const action of nativeManualActions)
|
|
1676
|
-
console.log(` - ${action}`);
|
|
1677
|
-
}
|
|
1678
|
-
console.log('SKS Skills:');
|
|
1679
|
-
console.log(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
|
|
1680
|
-
console.log(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
|
|
1681
|
-
console.log('SKS Current Command Surface:');
|
|
1682
|
-
console.log(` status: ${commandAliasCleanup.status || (commandAliasCleanup.ok ? 'clean' : 'blocked')}`);
|
|
1683
|
-
console.log(` canonical commands: ${commandAliasCleanup.canonical_command_count ?? 0}`);
|
|
1684
|
-
const managedRuntimeCleanup = commandAliasCleanup?.cleanup?.managed_runtime;
|
|
1685
|
-
if (managedRuntimeCleanup) {
|
|
1686
|
-
console.log(` managed items reconciled: ${managedRuntimeCleanup.removed_managed_artifact_count ?? 0}`);
|
|
1687
|
-
console.log(` user-authored collisions preserved: ${managedRuntimeCleanup.preserved_user_file_count ?? 0}`);
|
|
1688
|
-
}
|
|
1689
|
-
if (commandAliasCleanup.report_path)
|
|
1690
|
-
console.log(` report: ${commandAliasCleanup.report_path}`);
|
|
1691
|
-
console.log('Secret preservation:');
|
|
1692
|
-
console.log(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
|
|
1693
|
-
console.log(' raw secret values: never recorded');
|
|
1694
|
-
console.log(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
|
|
1695
|
-
console.log('Codex App Harness:');
|
|
1696
|
-
console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
|
|
1697
|
-
console.log(` hook approval: ${codexAppHarnessMatrix.app_features?.hook_approval_state_detectable ? 'ok' : 'unknown'}`);
|
|
1698
|
-
console.log(` skills: ${codexAppHarnessMatrix.sks_integrations?.dollar_skills_synced ? 'ok' : 'degraded'}`);
|
|
1699
|
-
console.log(` agent roles: ${codexAppHarnessMatrix.sks_integrations?.agent_roles_synced ? 'ok' : 'degraded'}`);
|
|
1700
|
-
console.log(` native agent_type: ${codexAppHarnessMatrix.app_features?.agent_type_supported ? 'ok' : 'fallback message-role'}`);
|
|
1701
|
-
console.log(` init-deep memory: ${codexAppHarnessMatrix.sks_integrations?.init_deep_available ? 'available' : 'missing'}`);
|
|
1702
|
-
console.log(` loop mesh app profile: ${codexAppHarnessMatrix.sks_integrations?.loop_mesh_app_profile_available ? 'available' : 'missing'}`);
|
|
1703
|
-
const codexAppUiStatus = codexAppUi;
|
|
1704
|
-
console.log('Codex App UI:');
|
|
1705
|
-
console.log(` fast selector: ${codexAppUi.fast_selector || 'unknown'}`);
|
|
1706
|
-
console.log(` provider selector: ${codexAppUi.provider_selector || 'unknown'}`);
|
|
1707
|
-
if (Array.isArray(codexAppUiStatus.provider_blockers) && codexAppUiStatus.provider_blockers.length) {
|
|
1708
|
-
console.log(` provider blockers: ${codexAppUiStatus.provider_blockers.join(', ')}`);
|
|
1709
|
-
}
|
|
1710
|
-
if (Array.isArray(codexAppUiStatus.provider_actions) && codexAppUiStatus.provider_actions.length) {
|
|
1711
|
-
console.log(' provider actions:');
|
|
1712
|
-
for (const action of codexAppUiStatus.provider_actions)
|
|
1713
|
-
console.log(` - ${action}`);
|
|
1714
|
-
}
|
|
1715
|
-
console.log(` host-owned config: ${codexAppUi.host_owned_config || 'unknown'}`);
|
|
1716
|
-
if (Array.isArray(codexAppUi.actions) && codexAppUi.actions.some((action) => action.changed)) {
|
|
1717
|
-
console.log(' repaired files:');
|
|
1718
|
-
for (const action of codexAppUi.actions.filter((entry) => entry.changed))
|
|
1719
|
-
console.log(` - ${action.file}${action.backup_path ? ` (backup ${action.backup_path})` : ''}`);
|
|
1720
|
-
}
|
|
1721
|
-
if (codexAppUi.next_action)
|
|
1722
|
-
console.log(` next action: ${codexAppUi.next_action}`);
|
|
1723
|
-
console.log('SKS Menu Bar:');
|
|
1724
|
-
console.log(` status: ${sksMenuBar.status || (sksMenuBar.ok ? 'ok' : 'blocked')}`);
|
|
1725
|
-
for (const line of sksMenuBarRunningVersionConsoleLines(sksMenuBar))
|
|
1726
|
-
console.log(line);
|
|
1727
|
-
const menubarPhase = doctorFixTransaction?.phases?.find((phase) => phase?.id === 'sks_menubar');
|
|
1728
|
-
if (menubarPhase) {
|
|
1729
|
-
const menubarSummary = menubarPhase.ok
|
|
1730
|
-
? (menubarPhase.repaired ? 'repaired' : 'verified')
|
|
1731
|
-
: `blocked(${(menubarPhase.blockers || []).join(', ') || 'unknown'})`;
|
|
1732
|
-
console.log(` menubar: ${menubarSummary}`);
|
|
1733
|
-
}
|
|
1734
|
-
if (sksMenuBar.app_path)
|
|
1735
|
-
console.log(` app: ${sksMenuBar.app_path}`);
|
|
1736
|
-
if (sksMenuBar.launch_agent_path)
|
|
1737
|
-
console.log(` launch agent: ${sksMenuBar.launch_agent_path}`);
|
|
1738
|
-
if (Array.isArray(sksMenuBar.blockers) && sksMenuBar.blockers.length)
|
|
1739
|
-
console.log(` blockers: ${sksMenuBar.blockers.join(', ')}`);
|
|
1740
|
-
if (Array.isArray(sksMenuBar.warnings) && sksMenuBar.warnings.length)
|
|
1741
|
-
console.log(` warnings: ${sksMenuBar.warnings.join(', ')}`);
|
|
1742
|
-
console.log(`Provider: ${providerContext.provider || 'unknown'} ${providerContext.service_tier || ''} (${providerContext.source || 'unknown'}, ${providerContext.confidence || 'low'})`);
|
|
1743
|
-
const imagegenReady = imagegen.auth_readiness;
|
|
1744
|
-
if (imagegenReady) {
|
|
1745
|
-
const paths = imagegenReady.available_paths?.length ? imagegenReady.available_paths.join(', ') : 'none';
|
|
1746
|
-
console.log(`Image Gen: auth=${imagegenReady.auth_mode} | headless_auto=${imagegenReady.headless_auto_available ? 'available' : 'unavailable'} | paths: ${paths}`);
|
|
1747
|
-
if (!imagegenReady.headless_auto_available) {
|
|
1748
|
-
for (const action of imagegenReady.next_actions || [])
|
|
1749
|
-
console.log(` - ${action}`);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
console.log(`Image Gen repair: ${nativeCapabilityReadiness.imagegen.status}`);
|
|
1753
|
-
for (const action of imagegenRepair.manual_actions || [])
|
|
1754
|
-
console.log(` - ${action}`);
|
|
1755
|
-
console.log(`Computer Use repair: ${computerUseRepair.recovered ? 'ok' : computerUseRepair.attempted ? 'blocked' : 'not-needed'}`);
|
|
1756
|
-
for (const action of computerUseRepair.next_actions || [])
|
|
1757
|
-
console.log(` - ${action}`);
|
|
1758
|
-
console.log(`Browser Use repair: ${browserUseRepair.recovered ? 'ok' : browserUseRepair.attempted ? 'blocked' : 'not-needed'}`);
|
|
1759
|
-
for (const action of browserUseRepair.next_actions || [])
|
|
1760
|
-
console.log(` - ${action}`);
|
|
1761
|
-
if (mcpTransportCollisionRepair) {
|
|
1762
|
-
const collisionCount = (mcpTransportCollisionRepair.servers || []).filter((s) => s.status === 'collision_resolved').length;
|
|
1763
|
-
console.log(`MCP transport collision repair: ${mcpTransportCollisionRepair.ok ? 'ok' : 'blocked'}${collisionCount ? ` (${collisionCount} resolved)` : ''}`);
|
|
1764
|
-
}
|
|
1765
|
-
{
|
|
1766
|
-
const manifestPath = path.join(root, '.sneakoscope', 'agent-bridge', 'manifest.json');
|
|
1767
|
-
const manifestExists = await exists(manifestPath);
|
|
1768
|
-
console.log(`Agent bridge: ${manifestExists ? 'manifest present' : 'not set up'}${manifestExists ? '' : ' — run `sks agent-bridge setup` to publish the manifest and register with an MCP host'}`);
|
|
1769
|
-
}
|
|
1770
|
-
const codexCurrentApp = codexCurrentAppCapability.report || {};
|
|
1771
|
-
console.log('Codex current compatibility:');
|
|
1772
|
-
console.log(` target: ${CURRENT_CODEX_RUNTIME_CONTRACT.targetTag}`);
|
|
1773
|
-
console.log(` runtime: ${codex.version || 'unknown'}`);
|
|
1774
|
-
console.log(` multi-agent mode: ${codexNativeFeatureMatrix.features?.multi_agent_mode?.ok ? 'verified' : 'unverified'}`);
|
|
1775
|
-
console.log(` rollout budget: ${codexNativeFeatureMatrix.features?.rollout_budget?.ok ? 'verified' : 'unverified'}`);
|
|
1776
|
-
console.log(` indexed search: ${codexNativeFeatureMatrix.features?.indexed_web_search?.ok ? 'verified' : 'unverified'}`);
|
|
1777
|
-
console.log(` current time: ${codexNativeFeatureMatrix.features?.current_time_read?.ok ? 'verified' : 'unverified'}`);
|
|
1778
|
-
console.log('Current Codex app features:');
|
|
1779
|
-
console.log(` /app handoff: ${codexCurrentApp.supports_app_handoff ? 'ok' : 'unavailable'}`);
|
|
1780
|
-
console.log(` plugin JSON: ${codexCurrentApp.supports_plugin_json ? 'ok' : 'unavailable'}`);
|
|
1781
|
-
console.log(` image path exposure: ${codexCurrentApp.supports_image_path_exposure ? 'ok' : 'unavailable'}`);
|
|
1782
|
-
console.log(` OAuth MCP pre-refresh: ${codexCurrentApp.supports_oauth_mcp_prerefresh ? 'ok' : 'unavailable'}`);
|
|
1783
|
-
const plugins = pluginInventory?.report?.plugins || [];
|
|
1784
|
-
const remoteMcpCount = plugins.flatMap((plugin) => plugin.remote_mcp_servers || []).length;
|
|
1785
|
-
const unavailableTemplates = pluginPolicy?.unavailable_app_templates?.length || 0;
|
|
1786
|
-
console.log(`Codex plugins: ${pluginInventory?.report ? 'ok' : 'warning'}`);
|
|
1787
|
-
console.log(` Remote MCP servers: ${remoteMcpCount} candidates`);
|
|
1788
|
-
console.log(` Unavailable app templates: ${unavailableTemplates}`);
|
|
1789
|
-
for (const warning of pluginPolicy?.doctor_warnings || [])
|
|
1790
|
-
console.log(` warning: ${warning}`);
|
|
1791
|
-
if (codexCurrentAppDoctor?.fixed?.length)
|
|
1792
|
-
console.log(` doctor --fix repaired: ${codexCurrentAppDoctor.fixed.join(', ')}`);
|
|
1793
|
-
console.log(`Desktop Bridge: ${desktopBridge.ok ? 'ready' : 'blocked'} (${desktopBridge.status?.readiness?.state || 'unavailable'})`);
|
|
1794
|
-
for (const providerId of ['codex-lb', 'openrouter']) {
|
|
1795
|
-
const provider = desktopBridge.providers?.[providerId];
|
|
1796
|
-
if (!provider)
|
|
1797
|
-
continue;
|
|
1798
|
-
console.log(` ${providerId}: ${provider.enabled ? 'enabled' : 'disabled'}; credential ${provider.credential?.state || 'unknown'} (${provider.credential?.source || 'none'}); endpoint ${provider.endpoint?.configured ? 'configured' : 'missing'}`);
|
|
1799
|
-
}
|
|
1800
|
-
for (const warning of desktopBridge.warnings || [])
|
|
1801
|
-
console.log(` warning: ${warning}`);
|
|
1802
|
-
for (const blocker of desktopBridge.blockers || [])
|
|
1803
|
-
console.log(` blocker: ${blocker}`);
|
|
1804
|
-
if (!desktopBridge.ok)
|
|
1805
|
-
for (const action of desktopBridge.recovery_actions || [])
|
|
1806
|
-
console.log(` action: ${action}`);
|
|
1807
|
-
console.log(`Permissions: config profile and permission profile are tracked separately (${permissionProfiles.codex_config_profile_field}, ${permissionProfiles.codex_permission_profile_field})`);
|
|
1808
|
-
console.log('Ready:');
|
|
1809
|
-
console.log(` cli_ready: ${ready.cli_ready ? 'yes' : 'no'}`);
|
|
1810
|
-
console.log(` mad_ready: ${ready.mad_ready ? 'yes' : 'no'}`);
|
|
1811
|
-
console.log(` managed_state_current: ${ready.managed_state_current ? 'yes' : 'no'}`);
|
|
1812
|
-
console.log(` core_ready: ${ready.core_ready ? 'yes' : 'no'}`);
|
|
1813
|
-
console.log(` center_ready: ${ready.center_ready ? 'yes' : 'no'}${ready.center_attempted ? ' (repair attempted)' : ' (not attempted)'}`);
|
|
1814
|
-
console.log(` ready: ${ready.ready ? 'yes' : 'no'}`);
|
|
1815
|
-
if (!ready.ready) {
|
|
1816
|
-
console.log('Primary blocker:');
|
|
1817
|
-
console.log(` ${ready.primary_blocker || 'unknown'}`);
|
|
1818
|
-
}
|
|
1819
|
-
if (configRepair?.repair_actions?.length) {
|
|
1820
|
-
console.log('What I fixed:');
|
|
1821
|
-
for (const action of configRepair.repair_actions)
|
|
1822
|
-
console.log(` - ${action.name}: ${action.ok ? 'ok' : 'failed'}`);
|
|
1823
|
-
}
|
|
1824
|
-
if (migrationJournal?.journal_path) {
|
|
1825
|
-
console.log(`Migration journal: ${migrationJournal.journal_path} (${migrationJournal.event_count} events, ${migrationJournal.mutations_without_rollback} without rollback)`);
|
|
1826
|
-
}
|
|
1827
|
-
if (sksUpdate) {
|
|
1828
|
-
console.log(`SKS update: ${sksUpdate.status}${sksUpdate.latest ? ` latest ${sksUpdate.latest}` : ''}${sksUpdate.error ? ` (${sksUpdate.error})` : ''}`);
|
|
1829
|
-
}
|
|
1830
|
-
if (globalSksInstallCleanup) {
|
|
1831
|
-
console.log(`Global SKS installs: kept ${globalSksInstallCleanup.kept?.length ?? 0}, removed ${globalSksInstallCleanup.removed?.filter((entry) => entry.ok).length ?? 0}, source repo exempt ${globalSksInstallCleanup.candidates?.filter((entry) => entry.source_repo_exempt).length ?? 0}`);
|
|
1832
|
-
if (globalSksInstallCleanup.npm_cache)
|
|
1833
|
-
console.log(`NPM cache cleanup: ${globalSksInstallCleanup.npm_cache.status}`);
|
|
1834
|
-
}
|
|
1835
|
-
if (!ready.ready && ready.next_actions?.length) {
|
|
1836
|
-
console.log('What still needs you:');
|
|
1837
|
-
for (const action of ready.next_actions)
|
|
1838
|
-
console.log(` - ${action}`);
|
|
1839
|
-
}
|
|
1604
|
+
const agentBridgeManifestExists = await exists(path.join(root, '.sneakoscope', 'agent-bridge', 'manifest.json'));
|
|
1605
|
+
const consoleLines = renderDoctorConsoleReport(result, {
|
|
1606
|
+
oauthCallbackOperatorActions,
|
|
1607
|
+
nativeCapabilityReadiness,
|
|
1608
|
+
agentBridgeManifestExists,
|
|
1609
|
+
codexCurrentAppCapability,
|
|
1610
|
+
pluginInventory,
|
|
1611
|
+
rootIsHome: path.resolve(root) === path.resolve(deps.home || process.env.HOME || os.homedir())
|
|
1612
|
+
});
|
|
1613
|
+
for (const consoleLine of consoleLines)
|
|
1614
|
+
console.log(consoleLine);
|
|
1840
1615
|
if (!result.ok)
|
|
1841
1616
|
process.exitCode = 1;
|
|
1842
1617
|
}
|
|
@@ -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 };
|
package/dist/core/fsx.js
CHANGED
|
@@ -342,17 +342,23 @@ export async function findUp(start, names) {
|
|
|
342
342
|
}
|
|
343
343
|
export async function findProjectRoot(start = process.cwd()) {
|
|
344
344
|
const resolved = path.resolve(start);
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
const home = path.resolve(os.homedir());
|
|
346
|
+
for (const markers of [['.sneakoscope', '.dcodex'], ['.git']]) {
|
|
347
|
+
let from = resolved;
|
|
348
|
+
while (true) {
|
|
349
|
+
const marker = await findUp(from, markers);
|
|
350
|
+
if (!marker)
|
|
351
|
+
break;
|
|
352
|
+
const rootDir = path.dirname(marker);
|
|
353
|
+
if (rootDir === path.parse(rootDir).root)
|
|
354
|
+
break;
|
|
355
|
+
if (rootDir !== home)
|
|
356
|
+
return rootDir;
|
|
357
|
+
const parent = path.dirname(rootDir);
|
|
358
|
+
if (parent === rootDir)
|
|
359
|
+
break;
|
|
360
|
+
from = parent;
|
|
361
|
+
}
|
|
356
362
|
}
|
|
357
363
|
return null;
|
|
358
364
|
}
|
|
@@ -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.2';
|
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.2",
|
|
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",
|