sneakoscope 0.9.9 → 0.9.11
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 +1 -1
- package/src/cli/install-helpers.mjs +107 -9
- package/src/cli/main.mjs +16 -6
- package/src/core/codex-app.mjs +59 -0
- package/src/core/fsx.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.11",
|
|
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",
|
|
@@ -3,12 +3,13 @@ import os from 'node:os';
|
|
|
3
3
|
import fsp from 'node:fs/promises';
|
|
4
4
|
import readline from 'node:readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
-
import { ensureDir, exists, globalSksRoot, packageRoot, readText, runProcess, tmpdir, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
6
|
+
import { ensureDir, exists, globalSksRoot, packageRoot, PACKAGE_VERSION, readText, runProcess, tmpdir, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
7
7
|
import { getCodexInfo } from '../core/codex-adapter.mjs';
|
|
8
8
|
import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.mjs';
|
|
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'],
|
|
@@ -31,6 +32,7 @@ export async function postinstall({ bootstrap }) {
|
|
|
31
32
|
console.log('\nSKS installed.');
|
|
32
33
|
const shim = await ensureSksCommandDuringInstall();
|
|
33
34
|
if (shim.status === 'present') console.log(`SKS command: available (${shim.command}).`);
|
|
35
|
+
else if (shim.status === 'repaired') console.log(`SKS command: stale PATH shim repaired (${shim.command}).`);
|
|
34
36
|
else if (shim.status === 'created') console.log(`SKS command: shim created at ${shim.command}.`);
|
|
35
37
|
else if (shim.status === 'created_not_on_path') console.log(`SKS command: shim created at ${shim.command}. Add ${path.dirname(shim.command)} to PATH, or run npx -y -p sneakoscope sks.`);
|
|
36
38
|
else if (shim.status === 'skipped') console.log(`SKS command: skipped (${shim.reason}).`);
|
|
@@ -46,6 +48,11 @@ export async function postinstall({ bootstrap }) {
|
|
|
46
48
|
else if (fastModeRepair.status === 'present') console.log('Codex App Fast mode: config already compatible.');
|
|
47
49
|
else if (fastModeRepair.status === 'skipped') console.log(`Codex App Fast mode: skipped (${fastModeRepair.reason}).`);
|
|
48
50
|
else if (fastModeRepair.status === 'failed') console.log(`Codex App Fast mode: auto repair failed. Run \`sks setup\`. ${fastModeRepair.error || ''}`.trim());
|
|
51
|
+
const appProcessRepair = await reconcileCodexAppUpgradeProcesses();
|
|
52
|
+
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.`);
|
|
53
|
+
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.`);
|
|
54
|
+
else if (appProcessRepair.status === 'skipped' && appProcessRepair.reason !== 'platform') console.log(`Codex App reconnect repair: skipped (${appProcessRepair.reason}).`);
|
|
55
|
+
else if (appProcessRepair.status === 'failed') console.log(`Codex App reconnect repair: skipped (${appProcessRepair.error || appProcessRepair.reason || 'process check failed'}).`);
|
|
49
56
|
const globalSkills = await ensureGlobalCodexSkillsDuringInstall();
|
|
50
57
|
if (globalSkills.status === 'installed') {
|
|
51
58
|
const removed = globalSkills.removed_stale_generated_skills || [];
|
|
@@ -1361,10 +1368,13 @@ function escapeRegExp(value) {
|
|
|
1361
1368
|
export async function ensureSksCommandDuringInstall(opts = {}) {
|
|
1362
1369
|
if (process.env.SKS_SKIP_POSTINSTALL_SHIM === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM=1' };
|
|
1363
1370
|
const pathEnv = opts.pathEnv ?? process.env.PATH ?? '';
|
|
1364
|
-
const existing = await findCommandOnPath('sks', pathEnv);
|
|
1365
|
-
if (isStableSksBin(existing)) return { status: 'present', command: existing };
|
|
1366
1371
|
const nodeBin = opts.nodeBin || process.execPath;
|
|
1367
1372
|
const target = opts.target || path.join(packageRoot(), 'bin', 'sks.mjs');
|
|
1373
|
+
const repair = await reconcileSksPathShimsDuringInstall({ ...opts, pathEnv, nodeBin, target });
|
|
1374
|
+
if (repair.status === 'repaired') return { ...repair, command: repair.command || repair.repaired?.[0]?.path || target };
|
|
1375
|
+
if (repair.status === 'failed') return repair;
|
|
1376
|
+
const existing = await findCommandOnPath('sks', pathEnv);
|
|
1377
|
+
if (isStableSksBin(existing)) return { status: 'present', command: existing };
|
|
1368
1378
|
const dirs = candidateShimDirs(pathEnv, opts.home || process.env.HOME);
|
|
1369
1379
|
const script = process.platform === 'win32'
|
|
1370
1380
|
? `@echo off\r\n"${nodeBin}" "${target}" %*\r\n`
|
|
@@ -1388,6 +1398,80 @@ export async function ensureSksCommandDuringInstall(opts = {}) {
|
|
|
1388
1398
|
return { status: 'failed', error: lastError };
|
|
1389
1399
|
}
|
|
1390
1400
|
|
|
1401
|
+
export async function selftestSksShimRepair() {
|
|
1402
|
+
const staleShimTmp = tmpdir();
|
|
1403
|
+
const staleBin = path.join(staleShimTmp, 'old-prefix', 'bin');
|
|
1404
|
+
const stalePkg = path.join(staleShimTmp, 'old-prefix', 'lib', 'node_modules', 'sneakoscope');
|
|
1405
|
+
await ensureDir(path.join(stalePkg, 'bin'));
|
|
1406
|
+
await ensureDir(staleBin);
|
|
1407
|
+
await writeTextAtomic(path.join(stalePkg, 'package.json'), JSON.stringify({ name: 'sneakoscope', version: '0.0.1' }, null, 2));
|
|
1408
|
+
await writeTextAtomic(path.join(stalePkg, 'bin', 'sks.mjs'), '#!/usr/bin/env node\nconsole.log("sneakoscope 0.0.1");\n');
|
|
1409
|
+
await fsp.chmod(path.join(stalePkg, 'bin', 'sks.mjs'), 0o755).catch(() => {});
|
|
1410
|
+
await fsp.symlink(path.join(stalePkg, 'bin', 'sks.mjs'), path.join(staleBin, 'sks'));
|
|
1411
|
+
const repair = await ensureSksCommandDuringInstall({ force: true, pathEnv: staleBin, home: path.join(staleShimTmp, 'home') });
|
|
1412
|
+
if (repair.status !== 'repaired') throw new Error(`selftest: stale global sks shim was not repaired (${repair.status})`);
|
|
1413
|
+
const run = await runProcess(path.join(staleBin, 'sks'), ['--version'], { timeoutMs: 10000, maxOutputBytes: 16 * 1024 });
|
|
1414
|
+
if (run.code !== 0 || !String(run.stdout || '').includes(PACKAGE_VERSION)) throw new Error('selftest: repaired stale sks shim does not run current package version');
|
|
1415
|
+
return { ok: true, repaired: repair.repaired || [] };
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
async function reconcileSksPathShimsDuringInstall(opts = {}) {
|
|
1419
|
+
if (process.env.SKS_SKIP_POSTINSTALL_SHIM_REPAIR === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM_REPAIR=1' };
|
|
1420
|
+
const target = opts.target || path.join(packageRoot(), 'bin', 'sks.mjs');
|
|
1421
|
+
const nodeBin = opts.nodeBin || process.execPath;
|
|
1422
|
+
const currentVersion = await installedPackageVersion(packageRoot());
|
|
1423
|
+
const commands = await findCommandsOnPath(['sks', 'sneakoscope'], opts.pathEnv ?? process.env.PATH ?? '');
|
|
1424
|
+
const repaired = [];
|
|
1425
|
+
const failed = [];
|
|
1426
|
+
for (const command of commands) {
|
|
1427
|
+
const info = await inspectSksPathShim(command.path, { target, currentVersion });
|
|
1428
|
+
if (!info.repairable) continue;
|
|
1429
|
+
const script = process.platform === 'win32'
|
|
1430
|
+
? `@echo off\r\n"${nodeBin}" "${target}" %*\r\n`
|
|
1431
|
+
: `#!/bin/sh\nexec "${nodeBin}" "${target}" "$@"\n`;
|
|
1432
|
+
try {
|
|
1433
|
+
await writeTextAtomic(command.path, script);
|
|
1434
|
+
if (process.platform !== 'win32') await fsp.chmod(command.path, 0o755).catch(() => {});
|
|
1435
|
+
repaired.push({ path: command.path, name: command.name, previous_version: info.version || null, target });
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
failed.push({ path: command.path, name: command.name, previous_version: info.version || null, error: err.message });
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (repaired.length) return { status: 'repaired', command: repaired[0].path, repaired, failed };
|
|
1441
|
+
if (failed.length) return { status: 'failed', error: failed.map((entry) => `${entry.path}: ${entry.error}`).join('; '), failed };
|
|
1442
|
+
return { status: 'present' };
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
async function inspectSksPathShim(candidate, opts = {}) {
|
|
1446
|
+
if (!candidate || isTransientNpmBinPath(candidate)) return { repairable: false, reason: 'transient_or_missing' };
|
|
1447
|
+
const target = path.resolve(opts.target || path.join(packageRoot(), 'bin', 'sks.mjs'));
|
|
1448
|
+
const resolved = await fsp.realpath(candidate).catch(() => candidate);
|
|
1449
|
+
if (path.resolve(resolved) === target) return { repairable: false, reason: 'current_target' };
|
|
1450
|
+
const packageDir = sksPackageRootForBin(resolved) || sksPackageRootForBin(candidate);
|
|
1451
|
+
if (!packageDir) return { repairable: false, reason: 'not_sneakoscope_bin' };
|
|
1452
|
+
const version = await installedPackageVersion(packageDir);
|
|
1453
|
+
const currentVersion = opts.currentVersion || await installedPackageVersion(packageRoot());
|
|
1454
|
+
if (!version || !currentVersion || compareVersions(version, currentVersion) >= 0) return { repairable: false, reason: 'not_older', version, current_version: currentVersion };
|
|
1455
|
+
return { repairable: true, version, current_version: currentVersion, package_dir: packageDir, resolved };
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function sksPackageRootForBin(file) {
|
|
1459
|
+
const normalized = String(file || '').split(path.sep).join('/');
|
|
1460
|
+
const marker = '/node_modules/sneakoscope/bin/';
|
|
1461
|
+
const idx = normalized.lastIndexOf(marker);
|
|
1462
|
+
if (idx < 0) return null;
|
|
1463
|
+
return normalized.slice(0, idx + '/node_modules/sneakoscope'.length).split('/').join(path.sep);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
async function installedPackageVersion(root) {
|
|
1467
|
+
const pkg = await readJsonMaybe(path.join(root, 'package.json'));
|
|
1468
|
+
return pkg?.version || (root === packageRoot() ? PACKAGE_VERSION : null);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
async function readJsonMaybe(file) {
|
|
1472
|
+
try { return JSON.parse(await fsp.readFile(file, 'utf8')); } catch { return null; }
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1391
1475
|
function candidateShimDirs(pathEnv, home) {
|
|
1392
1476
|
const seen = new Set();
|
|
1393
1477
|
const out = [];
|
|
@@ -1407,14 +1491,26 @@ function candidateShimDirs(pathEnv, home) {
|
|
|
1407
1491
|
}
|
|
1408
1492
|
|
|
1409
1493
|
async function findCommandOnPath(name, pathEnv) {
|
|
1494
|
+
const found = await findCommandsOnPath([name], pathEnv);
|
|
1495
|
+
return found[0]?.path || null;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
async function findCommandsOnPath(names, pathEnv) {
|
|
1410
1499
|
const suffixes = process.platform === 'win32' ? ['.cmd', '.exe', ''] : [''];
|
|
1500
|
+
const out = [];
|
|
1501
|
+
const seen = new Set();
|
|
1411
1502
|
for (const dir of String(pathEnv || '').split(path.delimiter).filter(Boolean)) {
|
|
1412
|
-
for (const
|
|
1413
|
-
const
|
|
1414
|
-
|
|
1503
|
+
for (const name of names) {
|
|
1504
|
+
for (const suffix of suffixes) {
|
|
1505
|
+
const candidate = path.join(dir, `${name}${suffix}`);
|
|
1506
|
+
const key = path.resolve(candidate);
|
|
1507
|
+
if (seen.has(key) || !await exists(candidate)) continue;
|
|
1508
|
+
seen.add(key);
|
|
1509
|
+
out.push({ name, path: candidate });
|
|
1510
|
+
}
|
|
1415
1511
|
}
|
|
1416
1512
|
}
|
|
1417
|
-
return
|
|
1513
|
+
return out;
|
|
1418
1514
|
}
|
|
1419
1515
|
|
|
1420
1516
|
async function ensureGlobalContext7DuringInstall() {
|
|
@@ -1766,6 +1862,7 @@ function codexLbPostinstallEnv(baseEnv, overrides = {}) {
|
|
|
1766
1862
|
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1767
1863
|
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1768
1864
|
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1',
|
|
1865
|
+
SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1',
|
|
1769
1866
|
...overrides
|
|
1770
1867
|
};
|
|
1771
1868
|
}
|
|
@@ -1838,7 +1935,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1838
1935
|
const codexLbPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1839
1936
|
const codexLbLoginCallsAfterPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1840
1937
|
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'];
|
|
1938
|
+
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
1939
|
const postinstallEnvBefore = Object.fromEntries(postinstallEnvKeys.map((key) => [key, process.env[key]]));
|
|
1843
1940
|
const codexLbLoginCallsBeforeBootstrap = await codexLbLoginCallCount(codexLbHome);
|
|
1844
1941
|
try {
|
|
@@ -1854,7 +1951,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
1854
1951
|
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1855
1952
|
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1856
1953
|
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1857
|
-
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1'
|
|
1954
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1',
|
|
1955
|
+
SKS_SKIP_CODEX_APP_UPGRADE_REPAIR: '1'
|
|
1858
1956
|
});
|
|
1859
1957
|
await postinstall({
|
|
1860
1958
|
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';
|
|
@@ -81,7 +81,7 @@ import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs'
|
|
|
81
81
|
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, sksAsciiLogo, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, reconcileTmuxTeamCockpit, runTmuxStatus, sanitizeTmuxSessionName, sweepCodexLbTmuxSessions, sweepTmuxTeamSurfaces, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
82
82
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
83
83
|
import { context7Command } from './context7-command.mjs';
|
|
84
|
-
import { askPostinstallQuestion, checkCodexLbResponseChain, checkContext7, checkRequiredSkills, codexLbChatgptBackupPath, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexFastModeDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, formatCodexLbRepairResultText, formatCodexLbStatusText, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, releaseCodexLbAuthHold, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall, unselectCodexLbProvider } from './install-helpers.mjs';
|
|
84
|
+
import { askPostinstallQuestion, checkCodexLbResponseChain, checkContext7, checkRequiredSkills, codexLbChatgptBackupPath, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexFastModeDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, formatCodexLbRepairResultText, formatCodexLbStatusText, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, releaseCodexLbAuthHold, repairCodexLbAuth, selftestCodexLb, selftestSksShimRepair, shouldAutoApproveInstall, unselectCodexLbProvider } from './install-helpers.mjs';
|
|
85
85
|
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, madHighCommand as runMadHighCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
86
86
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
87
87
|
import { recallPulseCommand } from './recallpulse-command.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');
|
|
@@ -2330,6 +2330,7 @@ async function selftest() {
|
|
|
2330
2330
|
if (await exists(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest: postinstall global skills shadow the first-party ${name} plugin`);
|
|
2331
2331
|
}
|
|
2332
2332
|
if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', 'getdesign-reference', 'SKILL.md')))) throw new Error('selftest: postinstall global getdesign-reference skill not installed');
|
|
2333
|
+
await selftestSksShimRepair();
|
|
2333
2334
|
const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
2334
2335
|
process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = '1';
|
|
2335
2336
|
const noBootstrapDecision = await postinstallBootstrapDecision(postinstallSetupTmp);
|
|
@@ -2341,7 +2342,7 @@ async function selftest() {
|
|
|
2341
2342
|
const postinstallNoMarkerCwd = path.join(postinstallNoMarkerTmp, 'cwd');
|
|
2342
2343
|
const postinstallNoMarkerGlobalRoot = path.join(postinstallNoMarkerTmp, 'global-root');
|
|
2343
2344
|
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 });
|
|
2345
|
+
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
2346
|
if (postinstallNoMarker.code !== 0) throw new Error(`selftest: no-marker postinstall bootstrap exited ${postinstallNoMarker.code}: ${postinstallNoMarker.stderr}`);
|
|
2346
2347
|
if (!String(postinstallNoMarker.stdout || '').includes('no project marker found; auto-running global SKS runtime bootstrap')) throw new Error('selftest: no-marker bootstrap');
|
|
2347
2348
|
if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest: no-marker postinstall did not bootstrap global runtime root');
|
|
@@ -3182,6 +3183,15 @@ async function selftest() {
|
|
|
3182
3183
|
const codexAppFixtureOpts = { codex: { bin: fakeCodex, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, cwd: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } };
|
|
3183
3184
|
const codexAppFeatureStatus = await codexAppIntegrationStatus(codexAppFixtureOpts);
|
|
3184
3185
|
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');
|
|
3186
|
+
const codexAppProcessRows = parseProcessRows([
|
|
3187
|
+
'101 1 /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled',
|
|
3188
|
+
'200 1 /Applications/Codex.app/Contents/MacOS/Codex',
|
|
3189
|
+
'201 200 /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled',
|
|
3190
|
+
'202 1 /Applications/Codex.app/Contents/Resources/codex app-server --listen stdio://',
|
|
3191
|
+
'203 1 /Users/me/.nvm/versions/node/bin/codex --model gpt-5.5'
|
|
3192
|
+
].join('\n'));
|
|
3193
|
+
const codexAppRepairTargets = findCodexAppUpgradeRepairTargets(codexAppProcessRows);
|
|
3194
|
+
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
3195
|
const codexAppOldCliStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodex, version: 'codex-cli 0.129.0' }, home: appFeatureTmp, cwd: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
3186
3196
|
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
3197
|
const missingDefaultPluginTmp = tmpdir();
|
package/src/core/codex-app.mjs
CHANGED
|
@@ -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.
|
|
8
|
+
export const PACKAGE_VERSION = '0.9.11';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|