sneakoscope 0.9.9 → 0.9.10

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.9.9",
4
+ "version": "0.9.10",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -9,6 +9,7 @@ import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConfli
9
9
  import { initProject, installSkills } from '../core/init.mjs';
10
10
  import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext7ConfigText, RECOMMENDED_SKILLS } from '../core/routes.mjs';
11
11
  import { codexLaunchCommand, platformTmuxInstallHint, tmuxReadiness } from '../core/tmux-ui.mjs';
12
+ import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.mjs';
12
13
 
13
14
  const DEFAULT_CODEX_APP_PLUGINS = [
14
15
  ['browser', 'openai-bundled'],
@@ -46,6 +47,11 @@ export async function postinstall({ bootstrap }) {
46
47
  else if (fastModeRepair.status === 'present') console.log('Codex App Fast mode: config already compatible.');
47
48
  else if (fastModeRepair.status === 'skipped') console.log(`Codex App Fast mode: skipped (${fastModeRepair.reason}).`);
48
49
  else if (fastModeRepair.status === 'failed') console.log(`Codex App Fast mode: auto repair failed. Run \`sks setup\`. ${fastModeRepair.error || ''}`.trim());
50
+ const appProcessRepair = await reconcileCodexAppUpgradeProcesses();
51
+ if (appProcessRepair.status === 'repaired') console.log(`Codex App reconnect repair: stopped ${appProcessRepair.killed.length} stale orphan app-server process(es). Restart Codex App to reconnect cleanly.`);
52
+ else if (appProcessRepair.status === 'partial') console.log(`Codex App reconnect repair: stopped ${appProcessRepair.killed.length} stale orphan app-server process(es); ${appProcessRepair.failed.length} could not be stopped. Restart Codex App if reconnecting continues.`);
53
+ else if (appProcessRepair.status === 'skipped' && appProcessRepair.reason !== 'platform') console.log(`Codex App reconnect repair: skipped (${appProcessRepair.reason}).`);
54
+ else if (appProcessRepair.status === 'failed') console.log(`Codex App reconnect repair: skipped (${appProcessRepair.error || appProcessRepair.reason || 'process check failed'}).`);
49
55
  const globalSkills = await ensureGlobalCodexSkillsDuringInstall();
50
56
  if (globalSkills.status === 'installed') {
51
57
  const removed = globalSkills.removed_stale_generated_skills || [];
@@ -1766,6 +1772,7 @@ function codexLbPostinstallEnv(baseEnv, overrides = {}) {
1766
1772
  SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
1767
1773
  SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
1768
1774
  SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1',
1775
+ SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1',
1769
1776
  ...overrides
1770
1777
  };
1771
1778
  }
@@ -1838,7 +1845,7 @@ export async function selftestCodexLb(tmp) {
1838
1845
  const codexLbPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
1839
1846
  const codexLbLoginCallsAfterPostinstall = await codexLbLoginCallCount(codexLbHome);
1840
1847
  if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"browser"') || codexLbPostinstallAuth.includes('sk-test') || codexLbLoginCallsAfterPostinstall !== codexLbLoginCallsBeforePostinstall) throw new Error('selftest: postinstall auth');
1841
- const postinstallEnvKeys = ['HOME', 'PATH', 'INIT_CWD', 'SKS_GLOBAL_ROOT', 'SKS_POSTINSTALL_BOOTSTRAP', 'SKS_POSTINSTALL_NO_BOOTSTRAP', 'SKS_SKIP_POSTINSTALL_SHIM', 'SKS_SKIP_POSTINSTALL_CONTEXT7', 'SKS_SKIP_POSTINSTALL_GETDESIGN', 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS', 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH', 'SKS_SKIP_CODEX_LB_LAUNCH_ENV', 'SKS_CODEX_LB_SYNC_CODEX_LOGIN'];
1848
+ const postinstallEnvKeys = ['HOME', 'PATH', 'INIT_CWD', 'SKS_GLOBAL_ROOT', 'SKS_POSTINSTALL_BOOTSTRAP', 'SKS_POSTINSTALL_NO_BOOTSTRAP', 'SKS_SKIP_POSTINSTALL_SHIM', 'SKS_SKIP_POSTINSTALL_CONTEXT7', 'SKS_SKIP_POSTINSTALL_GETDESIGN', 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS', 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH', 'SKS_SKIP_CODEX_LB_LAUNCH_ENV', 'SKS_SKIP_CODEX_APP_UPGRADE_REPAIR', 'SKS_CODEX_LB_SYNC_CODEX_LOGIN'];
1842
1849
  const postinstallEnvBefore = Object.fromEntries(postinstallEnvKeys.map((key) => [key, process.env[key]]));
1843
1850
  const codexLbLoginCallsBeforeBootstrap = await codexLbLoginCallCount(codexLbHome);
1844
1851
  try {
@@ -1854,7 +1861,8 @@ export async function selftestCodexLb(tmp) {
1854
1861
  SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
1855
1862
  SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
1856
1863
  SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
1857
- SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1'
1864
+ SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1',
1865
+ SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1'
1858
1866
  });
1859
1867
  await postinstall({
1860
1868
  bootstrap: async () => {
package/src/cli/main.mjs CHANGED
@@ -73,7 +73,7 @@ import { MISTAKE_RECALL_ARTIFACT, contractConsumesMistakeRecall } from '../core/
73
73
  import { buildPromptContext } from '../core/prompt-context-builder.mjs';
74
74
  import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
75
75
  import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
76
- import { CODEX_APP_DOCS_URL, codexAccessTokenStatus, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
76
+ import { CODEX_APP_DOCS_URL, codexAccessTokenStatus, codexAppIntegrationStatus, findCodexAppUpgradeRepairTargets, formatCodexAppStatus, parseProcessRows } from '../core/codex-app.mjs';
77
77
  import { buildAllFeaturesSelftest, buildFeatureRegistry, validateFeatureRegistry } from '../core/feature-registry.mjs';
78
78
  import { codexAppRemoteControlCommand } from './codex-app-command.mjs';
79
79
  import { allFeaturesCommand, featuresCommand, hooksCommand, hooksExplainReport } from './feature-commands.mjs';
@@ -2289,11 +2289,11 @@ async function selftest() {
2289
2289
  await ensureDir(path.join(conflictTmp, '.omx'));
2290
2290
  const conflictScan = await scanHarnessConflicts(conflictTmp, { home: path.join(conflictTmp, 'home') });
2291
2291
  if (!conflictScan.hard_block || !formatHarnessConflictReport(conflictScan).includes('GPT-5.5')) throw new Error('selftest: OMX conflict did not block with cleanup prompt');
2292
- const postinstallConflict = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2292
+ const postinstallConflict = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2293
2293
  if (postinstallConflict.code !== 0) throw new Error('selftest: postinstall conflict notice should not make npm install fail');
2294
2294
  const postinstallConflictOutput = String(`${postinstallConflict.stdout}\n${postinstallConflict.stderr}`);
2295
2295
  if (!postinstallConflictOutput.includes('SKS setup is blocked') || postinstallConflictOutput.includes('Cleanup prompt:')) throw new Error('selftest: postinstall conflict notice did not stay informational');
2296
- const postinstallConflictPrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, input: 'y\n', env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2296
+ const postinstallConflictPrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, input: 'y\n', env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2297
2297
  if (postinstallConflictPrompt.code !== 0 || !String(postinstallConflictPrompt.stdout || '').includes('Goal: completely remove the conflicting Codex harnesses')) throw new Error('selftest: conflict prompt');
2298
2298
  const postinstallSetupTmp = tmpdir();
2299
2299
  await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
@@ -2303,7 +2303,7 @@ async function selftest() {
2303
2303
  await ensureDir(path.join(postinstallSetupHome, '.agents', 'skills', name));
2304
2304
  await writeTextAtomic(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'), stalePluginSkillContent(name));
2305
2305
  }
2306
- const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
2306
+ const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
2307
2307
  if (postinstallSetup.code !== 0) throw new Error(`selftest: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
2308
2308
  if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest: postinstall installed deprecated agent-team fallback skill');
2309
2309
  if (!String(postinstallSetup.stdout || '').includes('SKS bootstrap: auto-running sks setup --bootstrap --install-scope global --force') || !String(postinstallSetup.stdout || '').includes('SKS Ready')) throw new Error('selftest: postinstall bootstrap');
@@ -2341,7 +2341,7 @@ async function selftest() {
2341
2341
  const postinstallNoMarkerCwd = path.join(postinstallNoMarkerTmp, 'cwd');
2342
2342
  const postinstallNoMarkerGlobalRoot = path.join(postinstallNoMarkerTmp, 'global-root');
2343
2343
  await ensureDir(postinstallNoMarkerCwd);
2344
- const postinstallNoMarker = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallNoMarkerCwd, env: { INIT_CWD: postinstallNoMarkerCwd, HOME: postinstallNoMarkerHome, SKS_GLOBAL_ROOT: postinstallNoMarkerGlobalRoot, SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
2344
+ const postinstallNoMarker = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallNoMarkerCwd, env: { INIT_CWD: postinstallNoMarkerCwd, HOME: postinstallNoMarkerHome, SKS_GLOBAL_ROOT: postinstallNoMarkerGlobalRoot, SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
2345
2345
  if (postinstallNoMarker.code !== 0) throw new Error(`selftest: no-marker postinstall bootstrap exited ${postinstallNoMarker.code}: ${postinstallNoMarker.stderr}`);
2346
2346
  if (!String(postinstallNoMarker.stdout || '').includes('no project marker found; auto-running global SKS runtime bootstrap')) throw new Error('selftest: no-marker bootstrap');
2347
2347
  if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest: no-marker postinstall did not bootstrap global runtime root');
@@ -3182,6 +3182,15 @@ async function selftest() {
3182
3182
  const codexAppFixtureOpts = { codex: { bin: fakeCodex, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, cwd: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } };
3183
3183
  const codexAppFeatureStatus = await codexAppIntegrationStatus(codexAppFixtureOpts);
3184
3184
  if (!codexAppFeatureStatus.ok || !codexAppFeatureStatus.features?.required_flags_ok || !codexAppFeatureStatus.features?.codex_git_commit || !codexAppFeatureStatus.features?.remote_control || !codexAppFeatureStatus.features?.git_actions?.ok || !codexAppFeatureStatus.features?.fast_mode_config?.ok) throw new Error('selftest: codex-app check did not accept required app feature flags, git actions, remote_control, and unlocked Fast UI config');
3185
+ const codexAppProcessRows = parseProcessRows([
3186
+ '101 1 /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled',
3187
+ '200 1 /Applications/Codex.app/Contents/MacOS/Codex',
3188
+ '201 200 /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled',
3189
+ '202 1 /Applications/Codex.app/Contents/Resources/codex app-server --listen stdio://',
3190
+ '203 1 /Users/me/.nvm/versions/node/bin/codex --model gpt-5.5'
3191
+ ].join('\n'));
3192
+ const codexAppRepairTargets = findCodexAppUpgradeRepairTargets(codexAppProcessRows);
3193
+ if (codexAppRepairTargets.length !== 1 || codexAppRepairTargets[0].pid !== 101) throw new Error('selftest: Codex App upgrade repair target selection is not limited to orphan desktop app-server processes');
3185
3194
  const codexAppOldCliStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodex, version: 'codex-cli 0.129.0' }, home: appFeatureTmp, cwd: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
3186
3195
  if (codexAppOldCliStatus.ok || codexAppOldCliStatus.features?.git_actions?.ok || !codexAppOldCliStatus.guidance.some((line) => line.includes('git commit/push actions are blocked'))) throw new Error('selftest: codex-app check did not block commit/push actions on old Codex CLI remote-control');
3187
3196
  const missingDefaultPluginTmp = tmpdir();
@@ -238,6 +238,65 @@ export function codexSupportsRemoteControl(versionText) {
238
238
  return Boolean(current && compareVersions(current, CODEX_REMOTE_CONTROL_MIN_VERSION) >= 0);
239
239
  }
240
240
 
241
+ export function parseProcessRows(text = '') {
242
+ return String(text || '')
243
+ .split(/\r?\n/)
244
+ .map((line) => line.trim())
245
+ .filter(Boolean)
246
+ .map((line) => {
247
+ const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
248
+ if (!match) return null;
249
+ return {
250
+ pid: Number.parseInt(match[1], 10),
251
+ ppid: Number.parseInt(match[2], 10),
252
+ command: match[3]
253
+ };
254
+ })
255
+ .filter((row) => Number.isFinite(row?.pid) && Number.isFinite(row?.ppid) && row.command);
256
+ }
257
+
258
+ export function findCodexAppUpgradeRepairTargets(rows = []) {
259
+ return rows.filter((row) => (
260
+ row?.ppid === 1
261
+ && /\/Codex\.app\/Contents\/Resources\/codex\s+app-server\s+--analytics-default-enabled(?:\s|$)/.test(String(row.command || ''))
262
+ ));
263
+ }
264
+
265
+ export async function reconcileCodexAppUpgradeProcesses(opts = {}) {
266
+ const platform = opts.platform || process.platform;
267
+ const env = opts.env || process.env;
268
+ if (platform !== 'darwin') return { status: 'skipped', reason: 'platform', killed: [] };
269
+ if (env.SKS_SKIP_CODEX_APP_UPGRADE_REPAIR === '1') return { status: 'skipped', reason: 'SKS_SKIP_CODEX_APP_UPGRADE_REPAIR=1', killed: [] };
270
+ const run = opts.runProcess || runProcess;
271
+ const ps = await run('ps', ['-axo', 'pid=', '-o', 'ppid=', '-o', 'command='], {
272
+ timeoutMs: opts.timeoutMs || 5000,
273
+ maxOutputBytes: opts.maxOutputBytes || 256 * 1024
274
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
275
+ if (ps.code !== 0) return { status: 'failed', reason: 'ps_failed', error: ps.stderr || ps.stdout || 'ps exited non-zero', killed: [] };
276
+ const rows = parseProcessRows(ps.stdout);
277
+ const targets = findCodexAppUpgradeRepairTargets(rows);
278
+ const killed = [];
279
+ const failed = [];
280
+ for (const target of targets) {
281
+ if (opts.dryRun) {
282
+ killed.push({ pid: target.pid, command: target.command, dry_run: true });
283
+ continue;
284
+ }
285
+ const kill = await run('kill', ['-TERM', String(target.pid)], {
286
+ timeoutMs: opts.timeoutMs || 5000,
287
+ maxOutputBytes: 8 * 1024
288
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
289
+ if (kill.code === 0) killed.push({ pid: target.pid, command: target.command });
290
+ else failed.push({ pid: target.pid, command: target.command, error: kill.stderr || kill.stdout || 'kill exited non-zero' });
291
+ }
292
+ return {
293
+ status: failed.length ? 'partial' : killed.length ? 'repaired' : 'clean',
294
+ killed,
295
+ failed,
296
+ checked: rows.length
297
+ };
298
+ }
299
+
241
300
  export function formatCodexRemoteControlStatus(status) {
242
301
  const lines = [
243
302
  'Codex remote-control',
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.9.9';
8
+ export const PACKAGE_VERSION = '0.9.10';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11