sneakoscope 0.7.72 → 0.7.74
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 +25 -1
- package/src/cli/main.mjs +17 -33
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +64 -0
- package/src/core/routes.mjs +12 -3
- package/src/core/team-live.mjs +13 -3
- package/src/core/tmux-ui.mjs +25 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.74",
|
|
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",
|
|
@@ -361,6 +361,7 @@ export function normalizeCodexFastModeUiConfig(text = '') {
|
|
|
361
361
|
next = removeTomlTableKey(next, 'features', 'codex_hooks');
|
|
362
362
|
next = upsertTopLevelTomlString(next, 'model', 'gpt-5.5');
|
|
363
363
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
364
|
+
next = upsertTopLevelTomlBoolean(next, 'suppress_unstable_features_warning', true);
|
|
364
365
|
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
365
366
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
366
367
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
@@ -457,6 +458,21 @@ function upsertTopLevelTomlString(text, key, value) {
|
|
|
457
458
|
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
458
459
|
}
|
|
459
460
|
|
|
461
|
+
function upsertTopLevelTomlBoolean(text, key, value) {
|
|
462
|
+
const line = `${key} = ${value ? 'true' : 'false'}`;
|
|
463
|
+
const lines = String(text || '').split('\n');
|
|
464
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
465
|
+
const end = firstTable === -1 ? lines.length : firstTable;
|
|
466
|
+
for (let i = 0; i < end; i += 1) {
|
|
467
|
+
if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[i])) {
|
|
468
|
+
lines[i] = line;
|
|
469
|
+
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
lines.splice(end, 0, line);
|
|
473
|
+
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
474
|
+
}
|
|
475
|
+
|
|
460
476
|
function upsertTomlTable(text, table, block) {
|
|
461
477
|
let lines = String(text || '').trimEnd().split('\n');
|
|
462
478
|
if (lines.length === 1 && lines[0] === '') lines = [];
|
|
@@ -929,15 +945,18 @@ export async function selftestCodexLb(tmp) {
|
|
|
929
945
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
930
946
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
931
947
|
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || !codexLbConfig.includes('model_provider = "codex-lb"') || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") || !/(\"auth_mode\"\s*:\s*\"apikey\")/.test(codexLbAuth)) throw new Error('selftest: codex-lb setup');
|
|
948
|
+
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
932
949
|
await initProject(codexLbHome, { installScope: 'global', force: true, repair: true });
|
|
933
950
|
const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
934
951
|
if (!codexLbRepairSetupConfig.includes('model_provider = "codex-lb"') || !codexLbRepairSetupConfig.includes('[model_providers.codex-lb]') || !codexLbRepairSetupConfig.includes('https://lb.example.test/backend-api/codex') || codexLbRepairSetupConfig.includes('sk-test')) throw new Error('selftest: init codex-lb');
|
|
952
|
+
if (!hasCodexUnstableFeatureWarningSuppression(codexLbRepairSetupConfig)) throw new Error('selftest: init codex-lb did not suppress Codex unstable feature warning');
|
|
935
953
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `${codexLbConfig}\n[mcp_servers.supabase]\nurl = "https://mcp.supabase.com/mcp?project_ref=ref&read_only=true&features=database,docs"\n`);
|
|
936
954
|
const ptmp = path.join(tmp, 'codex-lb-project-config'), prevHome = process.env.HOME;
|
|
937
955
|
try { process.env.HOME = codexLbHome; await initProject(ptmp, { installScope: 'global' }); }
|
|
938
956
|
finally { if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; }
|
|
939
957
|
const pcfg = await safeReadText(path.join(ptmp, '.codex', 'config.toml'));
|
|
940
958
|
if (!pcfg.includes('model_provider = "codex-lb"') || !pcfg.includes('[model_providers.codex-lb]') || !pcfg.includes('[mcp_servers.supabase]') || !pcfg.includes('read_only=true')) throw new Error('selftest: project codex-lb');
|
|
959
|
+
if (!hasCodexUnstableFeatureWarningSuppression(pcfg)) throw new Error('selftest: project codex-lb config did not suppress Codex unstable feature warning');
|
|
941
960
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
942
961
|
const codexLbRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
943
962
|
if (codexLbRepair.code !== 0) throw new Error(`selftest: codex-lb repair exited ${codexLbRepair.code}: ${codexLbRepair.stderr}`);
|
|
@@ -1014,7 +1033,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1014
1033
|
const codexLbDoctorJson = JSON.parse(codexLbDoctorRepair.stdout);
|
|
1015
1034
|
const codexLbDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1016
1035
|
const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1017
|
-
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbDoctorAuth.includes('sk-test') || !codexLbDoctorConfig.includes('model_provider = "codex-lb"') || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex')) throw new Error('selftest: doctor codex-lb');
|
|
1036
|
+
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbDoctorAuth.includes('sk-test') || !codexLbDoctorConfig.includes('model_provider = "codex-lb"') || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex') || !hasCodexUnstableFeatureWarningSuppression(codexLbDoctorConfig)) throw new Error('selftest: doctor codex-lb');
|
|
1018
1037
|
const codexLbContext7Bin = path.join(tmp, 'codex-lb-context7-bin');
|
|
1019
1038
|
await ensureDir(codexLbContext7Bin);
|
|
1020
1039
|
await writeTextAtomic(path.join(codexLbContext7Bin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 99.0.0"; exit 0; fi\nif [ "$CODEX_LB_API_KEY" ]; then echo "context7 leaked CODEX_LB_API_KEY" >&2; exit 77; fi\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then echo ""; exit 0; fi\nif [ "$1" = "mcp" ] && [ "$2" = "add" ]; then echo "context7 added"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
@@ -1092,6 +1111,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1092
1111
|
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1093
1112
|
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
|
|
1094
1113
|
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
|
|
1114
|
+
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
1095
1115
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
1096
1116
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest: tmux launch command does not source codex-lb env file');
|
|
1097
1117
|
if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest: tmux launch command without args did not force GPT-5.5');
|
|
@@ -1118,3 +1138,7 @@ function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
|
1118
1138
|
}
|
|
1119
1139
|
return lines.slice(start + 1, end).some((line) => /^\s*codex_hooks\s*=/.test(line));
|
|
1120
1140
|
}
|
|
1141
|
+
|
|
1142
|
+
function hasCodexUnstableFeatureWarningSuppression(text = '') {
|
|
1143
|
+
return /(^|\n)\s*suppress_unstable_features_warning\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(String(text || ''));
|
|
1144
|
+
}
|
package/src/cli/main.mjs
CHANGED
|
@@ -4,7 +4,7 @@ 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
6
|
import { projectRoot, readJson, writeJsonAtomic, writeTextAtomic, appendJsonlBounded, nowIso, exists, ensureDir, tmpdir, packageRoot, dirSize, formatBytes, which, runProcess, PACKAGE_VERSION, sksRoot, globalSksRoot, findProjectRoot, readStdin } from '../core/fsx.mjs';
|
|
7
|
-
import { initProject, installSkills, normalizeInstallScope, sksCommandPrefix } from '../core/init.mjs';
|
|
7
|
+
import { assertCodexWarningSuppressed as assertCodexWarn, hasDeprecatedCodexHooksFeatureFlag, hasTopLevelCodexModeLock, initProject, installSkills, missingGeneratedCodexAppFeatureFlags, normalizeInstallScope, sksCommandPrefix } from '../core/init.mjs';
|
|
8
8
|
import { buildCodexExecArgs, getCodexInfo, runCodexExec } from '../core/codex-adapter.mjs';
|
|
9
9
|
import { createMission, loadMission, findLatestMission, missionDir, setCurrent, stateFile } from '../core/mission.mjs';
|
|
10
10
|
import { buildQuestionSchema, writeQuestions } from '../core/questions.mjs';
|
|
@@ -78,7 +78,7 @@ import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs'
|
|
|
78
78
|
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, sksAsciiLogo, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, reconcileTmuxTeamCockpit, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
79
79
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
80
80
|
import { context7Command } from './context7-command.mjs';
|
|
81
|
-
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
81
|
+
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexFastModeDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
82
82
|
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';
|
|
83
83
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
84
84
|
|
|
@@ -1659,6 +1659,7 @@ async function doctor(args) {
|
|
|
1659
1659
|
let conflictScan = await scanHarnessConflicts(root);
|
|
1660
1660
|
let repairApplied = false;
|
|
1661
1661
|
let globalSkillsRepair = null;
|
|
1662
|
+
let globalCodexConfigRepair = null;
|
|
1662
1663
|
let projectRepair = null;
|
|
1663
1664
|
let codexLbRepair = null;
|
|
1664
1665
|
const globalCommand = await globalSksCommand();
|
|
@@ -1666,6 +1667,7 @@ async function doctor(args) {
|
|
|
1666
1667
|
const existingManifest = await readJson(path.join(root, '.sneakoscope', 'manifest.json'), null);
|
|
1667
1668
|
const fixScope = requestedScope || normalizeInstallScope(existingManifest?.installation?.scope || 'global');
|
|
1668
1669
|
projectRepair = await initProject(root, { installScope: fixScope, globalCommand, localOnly: flag(args, '--local-only') || Boolean(existingManifest?.git?.local_only), force: true, repair: true });
|
|
1670
|
+
if (!flag(args, '--local-only')) globalCodexConfigRepair = await ensureGlobalCodexFastModeDuringInstall();
|
|
1669
1671
|
if (!flag(args, '--local-only')) globalSkillsRepair = await ensureGlobalCodexSkillsDuringInstall({ force: true });
|
|
1670
1672
|
codexLbRepair = await repairCodexLbAuth();
|
|
1671
1673
|
repairApplied = true;
|
|
@@ -1695,7 +1697,7 @@ async function doctor(args) {
|
|
|
1695
1697
|
const result = {
|
|
1696
1698
|
node: { ok: nodeOk, version: process.version }, root, codex, rust,
|
|
1697
1699
|
install,
|
|
1698
|
-
repair: { applied: repairApplied, project: projectRepair, global_skills: globalSkillsRepair, codex_lb: codexLbRepair, blocked_by_other_harness: flag(args, '--fix') && conflictScan.hard_block },
|
|
1700
|
+
repair: { applied: repairApplied, project: projectRepair, global_codex_config: globalCodexConfigRepair, global_skills: globalSkillsRepair, codex_lb: codexLbRepair, blocked_by_other_harness: flag(args, '--fix') && conflictScan.hard_block },
|
|
1699
1701
|
harness_conflicts: {
|
|
1700
1702
|
ok: conflictScan.ok,
|
|
1701
1703
|
hard_block: conflictScan.hard_block,
|
|
@@ -1729,6 +1731,7 @@ async function doctor(args) {
|
|
|
1729
1731
|
console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
|
|
1730
1732
|
console.log(`Conflicts: ${result.harness_conflicts.hard_block ? 'blocked' : 'ok'} ${result.harness_conflicts.conflicts.length} finding(s)`);
|
|
1731
1733
|
if (repairApplied) console.log('Repair: regenerated SKS managed files from the installed package template');
|
|
1734
|
+
if (globalCodexConfigRepair) console.log(`Global Codex config: ${globalCodexConfigRepair.status} ${globalCodexConfigRepair.config_path || ''}`.trimEnd());
|
|
1732
1735
|
if (globalSkillsRepair) {
|
|
1733
1736
|
const removed = globalSkillsRepair.removed_stale_generated_skills || [];
|
|
1734
1737
|
const cleanup = removed.length ? ` removed stale generated skill shadow(s): ${removed.join(', ')}` : '';
|
|
@@ -1925,34 +1928,6 @@ async function safeReadText(file, fallback = '') {
|
|
|
1925
1928
|
try { return await fsp.readFile(file, 'utf8'); } catch { return fallback; }
|
|
1926
1929
|
}
|
|
1927
1930
|
|
|
1928
|
-
function hasTopLevelCodexModeLock(text = '') {
|
|
1929
|
-
const lines = String(text || '').split('\n');
|
|
1930
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
1931
|
-
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
1932
|
-
const model = top.match(/^model\s*=\s*"([^"]+)"/m)?.[1];
|
|
1933
|
-
return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top);
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
1937
|
-
const lines = String(text || '').split('\n');
|
|
1938
|
-
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
1939
|
-
if (start === -1) return false;
|
|
1940
|
-
let end = lines.length;
|
|
1941
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1942
|
-
if (/^\s*\[.+\]\s*$/.test(lines[i])) {
|
|
1943
|
-
end = i;
|
|
1944
|
-
break;
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
return lines.slice(start + 1, end).some((line) => /^\s*codex_hooks\s*=/.test(line));
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
const REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS = ['hooks', 'multi_agent', 'fast_mode', 'fast_mode_ui', 'codex_git_commit', 'computer_use', 'apps', 'plugins'];
|
|
1951
|
-
|
|
1952
|
-
function missingGeneratedCodexAppFeatureFlags(text = '') {
|
|
1953
|
-
return REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS.filter((name) => !String(text || '').includes(`${name} = true`));
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
1931
|
async function resolveMissionId(root, arg) { return (!arg || arg === 'latest') ? findLatestMission(root) : arg; }
|
|
1957
1932
|
function readMaxCycles(args, fallback) {
|
|
1958
1933
|
const i = args.indexOf('--max-cycles');
|
|
@@ -2118,6 +2093,8 @@ async function selftest() {
|
|
|
2118
2093
|
if (!doctorRepairJson.repair?.applied || doctorRepairJson.install?.scope !== 'project' || !doctorRepairJson.install?.ok || !doctorRepairJson.install?.source_project) throw new Error('selftest: doctor scope');
|
|
2119
2094
|
const repairedManifest = await readJson(path.join(repairTmp, '.sneakoscope', 'manifest.json'));
|
|
2120
2095
|
if (repairedManifest.installation?.scope !== 'project' || repairedManifest.installation?.hook_command_prefix !== 'node ./bin/sks.mjs') throw new Error('selftest: manifest scope');
|
|
2096
|
+
const repairedCodexConfig = await safeReadText(path.join(repairTmp, '.codex', 'config.toml'));
|
|
2097
|
+
assertCodexWarn(repairedCodexConfig, 'doctor project config');
|
|
2121
2098
|
const repairedTeamSkill = await safeReadText(path.join(repairTmp, '.agents', 'skills', 'team', 'SKILL.md'));
|
|
2122
2099
|
if (!repairedTeamSkill.includes('SKS Team orchestration') || repairedTeamSkill.includes('tampered')) throw new Error('selftest: doctor repair did not regenerate team skill');
|
|
2123
2100
|
if (await exists(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest: doctor repair did not remove deprecated agent-team alias skill');
|
|
@@ -2153,6 +2130,9 @@ async function selftest() {
|
|
|
2153
2130
|
});
|
|
2154
2131
|
if (doctorGlobalRepair.code !== 0) throw new Error(`selftest: doctor --fix global skill repair exited ${doctorGlobalRepair.code}: ${doctorGlobalRepair.stderr}`);
|
|
2155
2132
|
const doctorGlobalRepairJson = JSON.parse(doctorGlobalRepair.stdout || '{}');
|
|
2133
|
+
const doctorGlobalCodexConfig = await safeReadText(path.join(doctorGlobalHome, '.codex', 'config.toml'));
|
|
2134
|
+
if (!doctorGlobalRepairJson.repair?.global_codex_config) throw new Error('selftest: doctor global config repair missing');
|
|
2135
|
+
assertCodexWarn(doctorGlobalCodexConfig, 'doctor global config');
|
|
2156
2136
|
for (const name of stalePluginSkillNames) {
|
|
2157
2137
|
if (await exists(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest: doctor --fix did not remove global generated ${name} plugin shadow skill`);
|
|
2158
2138
|
}
|
|
@@ -2185,6 +2165,7 @@ async function selftest() {
|
|
|
2185
2165
|
if (!(await exists(path.join(postinstallSetupTmp, '.codex', 'hooks.json')))) throw new Error('selftest: postinstall did not create project hooks during automatic bootstrap');
|
|
2186
2166
|
const postinstallSetupConfig = await safeReadText(path.join(postinstallSetupTmp, '.codex', 'config.toml'));
|
|
2187
2167
|
if (missingGeneratedCodexAppFeatureFlags(postinstallSetupConfig).length || hasDeprecatedCodexHooksFeatureFlag(postinstallSetupConfig)) throw new Error('selftest: postinstall flags');
|
|
2168
|
+
assertCodexWarn(postinstallSetupConfig, 'postinstall project config');
|
|
2188
2169
|
if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest: postinstall did not report automatic global Codex App skills');
|
|
2189
2170
|
if (!String(postinstallSetup.stdout || '').includes('Removed stale generated skill shadow(s):')) throw new Error('selftest: postinstall did not report stale first-party plugin shadow cleanup');
|
|
2190
2171
|
const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
|
|
@@ -2221,6 +2202,7 @@ async function selftest() {
|
|
|
2221
2202
|
if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest: no-marker postinstall did not bootstrap global runtime root');
|
|
2222
2203
|
const postinstallNoMarkerConfig = await safeReadText(path.join(postinstallNoMarkerGlobalRoot, '.codex', 'config.toml'));
|
|
2223
2204
|
if (missingGeneratedCodexAppFeatureFlags(postinstallNoMarkerConfig).length || hasDeprecatedCodexHooksFeatureFlag(postinstallNoMarkerConfig)) throw new Error('selftest: no-marker flags');
|
|
2205
|
+
assertCodexWarn(postinstallNoMarkerConfig, 'postinstall global runtime config');
|
|
2224
2206
|
if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest: no-marker postinstall polluted install cwd');
|
|
2225
2207
|
if (await exists(path.join(postinstallNoMarkerGlobalRoot, '.gitignore'))) throw new Error('selftest: global runtime bootstrap without project git wrote shared .gitignore');
|
|
2226
2208
|
const bootstrapJsonTmp = tmpdir();
|
|
@@ -3001,6 +2983,7 @@ async function selftest() {
|
|
|
3001
2983
|
const codexConfigText = await safeReadText(path.join(tmp, '.codex', 'config.toml'));
|
|
3002
2984
|
const missingCodexConfigFlags = missingGeneratedCodexAppFeatureFlags(codexConfigText);
|
|
3003
2985
|
if (missingCodexConfigFlags.length || hasDeprecatedCodexHooksFeatureFlag(codexConfigText)) throw new Error(`selftest: generated Codex App feature flags missing or deprecated: ${missingCodexConfigFlags.join(', ')}`);
|
|
2986
|
+
assertCodexWarn(codexConfigText, 'generated Codex App config');
|
|
3004
2987
|
if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest: Context7 MCP not configured');
|
|
3005
2988
|
if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest: GPT-5.5 reasoning profiles not configured');
|
|
3006
2989
|
if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest: generated sks-mad-high profile is not full access');
|
|
@@ -3012,6 +2995,7 @@ async function selftest() {
|
|
|
3012
2995
|
await initProject(preservedConfigTmp, {});
|
|
3013
2996
|
const preservedConfig = await safeReadText(path.join(preservedConfigTmp, '.codex', 'config.toml'));
|
|
3014
2997
|
if (!/^model = "gpt-5\.5"/m.test(preservedConfig) || !preservedConfig.includes('service_tier = "fast"') || !preservedConfig.includes('fast_mode = true') || !preservedConfig.includes('fast_mode_ui = true') || !preservedConfig.includes('[user.fast_mode]') || !preservedConfig.includes('visible = true') || !preservedConfig.includes('enabled = true') || !preservedConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(preservedConfig)) throw new Error('selftest: Codex config merge dropped or failed to enable Fast mode defaults and GPT-5.5');
|
|
2998
|
+
assertCodexWarn(preservedConfig, 'merged Codex config');
|
|
3015
2999
|
if (preservedConfig.includes('fast_default_opt_out = true') || !preservedConfig.includes('keep = true')) throw new Error('selftest: Codex config merge did not remove stale Fast opt-out notice while preserving other notice keys');
|
|
3016
3000
|
const missingPreservedFlags = missingGeneratedCodexAppFeatureFlags(preservedConfig);
|
|
3017
3001
|
if (missingPreservedFlags.length || hasDeprecatedCodexHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('custom_preview = true') || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error(`selftest: Codex config merge did not add required app feature flags, preserve existing feature flags, or remove deprecated codex_hooks: ${missingPreservedFlags.join(', ')}`);
|
|
@@ -3247,7 +3231,7 @@ async function selftest() {
|
|
|
3247
3231
|
if (teamPlan.agent_session_count !== 5) throw new Error('selftest: team default sessions not 5');
|
|
3248
3232
|
if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 5) throw new Error('selftest: team default role counts invalid');
|
|
3249
3233
|
const teamPlanFeatureFlags = teamPlan.codex_config_required?.features || {};
|
|
3250
|
-
const missingTeamPlanFeatureFlags =
|
|
3234
|
+
const missingTeamPlanFeatureFlags = missingGeneratedCodexAppFeatureFlags(teamPlanFeatureFlags);
|
|
3251
3235
|
if (missingTeamPlanFeatureFlags.length || teamPlanFeatureFlags.codex_hooks === true) throw new Error(`selftest: team plan Codex config missing required app flags or still uses deprecated codex_hooks: ${missingTeamPlanFeatureFlags.join(', ')}`);
|
|
3252
3236
|
if (!teamPlan.review_gate?.passed || teamPlan.review_gate.required_reviewer_lanes !== 5) throw new Error('selftest: team review policy gate did not pass default plan');
|
|
3253
3237
|
if (teamPlan.codex_config_required?.service_tier !== 'fast' || teamPlan.reasoning?.service_tier !== 'fast') throw new Error('selftest: team plan did not require Fast service tier');
|
|
@@ -3478,7 +3462,7 @@ async function selftest() {
|
|
|
3478
3462
|
if (!(await readTeamTranscriptTail(teamDir, 1)).join('\n').includes('selftest mapped options')) throw new Error('selftest: team transcript tail missing event');
|
|
3479
3463
|
const teamLane = await renderTeamAgentLane(teamDir, { missionId: teamId, agent: 'analysis_scout_1', lines: 4 });
|
|
3480
3464
|
if (!teamLane.includes('selftest mapped repo slice')) throw new Error('selftest: team agent lane missing event context');
|
|
3481
|
-
if (!teamLane.includes('##
|
|
3465
|
+
if (!teamLane.includes('## Codex Chat') || !teamLane.includes('+-- me [status]') || !teamLane.includes('selftest mapped repo slice') || teamLane.includes('## Global Tail')) throw new Error('selftest: chat lane');
|
|
3482
3466
|
const teamLaneCli = await runProcess(process.execPath, [hookBin, 'team', 'lane', teamId, '--agent', 'analysis_scout_1', '--lines', '4'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
3483
3467
|
if (teamLaneCli.code !== 0 || !String(teamLaneCli.stdout || '').includes('SKS Team Agent Lane') || !String(teamLaneCli.stdout || '').includes('analysis_scout_1')) throw new Error('selftest: sks team lane CLI did not render an agent lane');
|
|
3484
3468
|
await writeTextAtomic(path.join(teamDir, 'team-analysis.md'), '- claim: analysis scout mapped route registry | source: src/core/routes.mjs | risk: high | confidence: supported\n');
|
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.7.
|
|
8
|
+
export const PACKAGE_VERSION = '0.7.74';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/init.mjs
CHANGED
|
@@ -15,6 +15,54 @@ const SKS_GENERATED_GIT_PATTERNS = ['.sneakoscope/', '.codex/', '.agents/', 'AGE
|
|
|
15
15
|
const SKS_SKILL_MANIFEST_FILE = '.sks-generated.json';
|
|
16
16
|
const GENERATED_PRUNE_POLICY = 'remove_previous_sks_generated_paths_absent_from_current_manifest';
|
|
17
17
|
|
|
18
|
+
export const REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS = [
|
|
19
|
+
'hooks',
|
|
20
|
+
'multi_agent',
|
|
21
|
+
'fast_mode',
|
|
22
|
+
'fast_mode_ui',
|
|
23
|
+
'codex_git_commit',
|
|
24
|
+
'computer_use',
|
|
25
|
+
'apps',
|
|
26
|
+
'plugins'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function hasTopLevelCodexModeLock(text = '') {
|
|
30
|
+
const lines = String(text || '').split('\n');
|
|
31
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
32
|
+
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
33
|
+
const model = top.match(/^model\s*=\s*"([^"]+)"/m)?.[1];
|
|
34
|
+
return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
38
|
+
const lines = String(text || '').split('\n');
|
|
39
|
+
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
40
|
+
if (start === -1) return false;
|
|
41
|
+
let end = lines.length;
|
|
42
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
43
|
+
if (/^\s*\[.+\]\s*$/.test(lines[i])) {
|
|
44
|
+
end = i;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return lines.slice(start + 1, end).some((line) => /^\s*codex_hooks\s*=/.test(line));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function missingGeneratedCodexAppFeatureFlags(text = '') {
|
|
52
|
+
if (text && typeof text === 'object') return REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS.filter((name) => text[name] !== true);
|
|
53
|
+
return REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS.filter((name) => !String(text || '').includes(`${name} = true`));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasCodexUnstableFeatureWarningSuppression(text = '') {
|
|
57
|
+
return /(^|\n)\s*suppress_unstable_features_warning\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(String(text || ''));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function assertCodexWarningSuppressed(text = '', label = 'Codex config') {
|
|
61
|
+
if (!hasCodexUnstableFeatureWarningSuppression(text)) {
|
|
62
|
+
throw new Error(`selftest: ${label} missing suppress_unstable_features_warning`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
18
66
|
function reflectionInstructionText(commandPrefix = 'sks') {
|
|
19
67
|
return `Post-route reflection: full routes load \`reflection\` after work/tests and before final; DFix/Answer/Help/Wiki/SKS discovery are exempt. Write reflection.md; record only real misses/gaps, or no_issue_acknowledged. For lessons, append TriWiki claim rows to ${REFLECTION_MEMORY_PATH}. Run "${commandPrefix} wiki refresh" or pack, validate, then pass reflection-gate.json.`;
|
|
20
68
|
}
|
|
@@ -444,6 +492,7 @@ function mergeManagedCodexConfigToml(existingContent = '') {
|
|
|
444
492
|
next = removeTomlTableKey(next, 'features', 'codex_hooks');
|
|
445
493
|
next = upsertTopLevelTomlString(next, 'model', 'gpt-5.5');
|
|
446
494
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
495
|
+
next = upsertTopLevelTomlBoolean(next, 'suppress_unstable_features_warning', true);
|
|
447
496
|
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
448
497
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
449
498
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
@@ -546,6 +595,21 @@ function upsertTopLevelTomlString(text, key, value) {
|
|
|
546
595
|
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
547
596
|
}
|
|
548
597
|
|
|
598
|
+
function upsertTopLevelTomlBoolean(text, key, value) {
|
|
599
|
+
const line = `${key} = ${value ? 'true' : 'false'}`;
|
|
600
|
+
const lines = String(text || '').split('\n');
|
|
601
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
602
|
+
const end = firstTable === -1 ? lines.length : firstTable;
|
|
603
|
+
for (let i = 0; i < end; i += 1) {
|
|
604
|
+
if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[i])) {
|
|
605
|
+
lines[i] = line;
|
|
606
|
+
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
lines.splice(end, 0, line);
|
|
610
|
+
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
611
|
+
}
|
|
612
|
+
|
|
549
613
|
function removeTomlTableKey(text, table, key) {
|
|
550
614
|
const lines = String(text || '').trimEnd().split('\n');
|
|
551
615
|
if (lines.length === 1 && lines[0] === '') return '';
|
package/src/core/routes.mjs
CHANGED
|
@@ -149,13 +149,22 @@ export function dollarSkillName(commandOrId) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
export function stripVisibleDecisionAnswerBlocks(value = '') {
|
|
152
|
-
return String(value || '')
|
|
152
|
+
return stripNonAuthoritativeLiveChatBlocks(String(value || ''))
|
|
153
153
|
.replace(/\s*\[(?=[^\]]*\b[A-Z][A-Z0-9_]{2,}\s*:)[^\]]{0,6000}\]\s*/g, ' ')
|
|
154
154
|
.replace(/[ \t]{2,}/g, ' ')
|
|
155
155
|
.replace(/\n{3,}/g, '\n\n')
|
|
156
156
|
.trim();
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
export function stripNonAuthoritativeLiveChatBlocks(value = '') {
|
|
160
|
+
return String(value || '')
|
|
161
|
+
.replace(/(?:^|\n)\s*[›>]\s*\[## Live Chat[\s\S]*?\]\s*(?=(?:이|이거|그리고|근데|계속|고쳐|수정|해결|Pane|pane|please|fix|also|and|$))/g, '\n')
|
|
162
|
+
.replace(/(?:^|\n)\s*\[## Live Chat[\s\S]*?\]\s*(?=(?:이|이거|그리고|근데|계속|고쳐|수정|해결|Pane|pane|please|fix|also|and|$))/g, '\n')
|
|
163
|
+
.replace(/^\s*-?\s*\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\s+\S+\s+\[[^\]]+\]:.*$/gm, '')
|
|
164
|
+
.replace(/^\s*## Live Chat\s*$/gm, '')
|
|
165
|
+
.trim();
|
|
166
|
+
}
|
|
167
|
+
|
|
159
168
|
export function triwikiContextTracking(commandPrefix = 'sks') {
|
|
160
169
|
const prefix = String(commandPrefix || 'sks');
|
|
161
170
|
return {
|
|
@@ -677,8 +686,8 @@ export function looksLikeImageUxReviewRequest(prompt = '') {
|
|
|
677
686
|
}
|
|
678
687
|
|
|
679
688
|
export function routePrompt(prompt) {
|
|
680
|
-
const
|
|
681
|
-
const
|
|
689
|
+
const text = stripVisibleDecisionAnswerBlocks(prompt);
|
|
690
|
+
const command = dollarCommand(text);
|
|
682
691
|
if (command) {
|
|
683
692
|
if (command === 'MAD-SKS') {
|
|
684
693
|
const afterModifier = stripMadSksSignal(text);
|
package/src/core/team-live.mjs
CHANGED
|
@@ -576,7 +576,7 @@ export async function renderTeamAgentLane(dir, opts = {}) {
|
|
|
576
576
|
`## Assigned Runtime Tasks`,
|
|
577
577
|
...(runtime ? formatRuntimeTasks(assignedTasks) : ['- team-runtime-tasks.json not available yet.']),
|
|
578
578
|
'',
|
|
579
|
-
`##
|
|
579
|
+
`## Codex Chat`,
|
|
580
580
|
...(chatEvents.length ? chatEvents.map((event) => formatChatTranscriptEvent(event, aliases[0])) : ['- waiting for live agent messages...']),
|
|
581
581
|
opts.includeGlobalTail ? '' : null,
|
|
582
582
|
opts.includeGlobalTail ? `## Global Tail` : null,
|
|
@@ -710,14 +710,24 @@ function uniqueTranscriptEvents(events = []) {
|
|
|
710
710
|
}
|
|
711
711
|
|
|
712
712
|
function formatChatTranscriptEvent(event = {}, laneAgent = '') {
|
|
713
|
-
if (event.raw) return
|
|
713
|
+
if (event.raw) return codexChatBlock({ speaker: 'system', message: event.raw });
|
|
714
714
|
const from = event.agent || 'unknown';
|
|
715
715
|
const to = event.to ? ` -> ${event.to}` : '';
|
|
716
716
|
const kind = event.type && event.type !== 'message' ? ` [${event.type}]` : '';
|
|
717
717
|
const ts = event.ts ? `${event.ts} ` : '';
|
|
718
718
|
const artifact = event.artifact ? ` (${event.artifact})` : '';
|
|
719
719
|
const marker = String(from) === String(laneAgent) ? 'me' : from;
|
|
720
|
-
return
|
|
720
|
+
return codexChatBlock({
|
|
721
|
+
speaker: `${marker}${to}${kind}`,
|
|
722
|
+
meta: ts.trim(),
|
|
723
|
+
message: `${String(event.message || '').slice(0, 500)}${artifact}`
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function codexChatBlock({ speaker = 'agent', meta = '', message = '' } = {}) {
|
|
728
|
+
const header = [speaker, meta].filter(Boolean).join(' | ');
|
|
729
|
+
const body = String(message || '').split(/\r?\n/).map((line) => `| ${line}`).join('\n');
|
|
730
|
+
return [`+-- ${header}`, body || '|', '+--'].join('\n');
|
|
721
731
|
}
|
|
722
732
|
|
|
723
733
|
function eventAddressedTo(event = {}, agent = '') {
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -223,6 +223,15 @@ function colorizedLaneBannerCommand(lines = [], color = '') {
|
|
|
223
223
|
return `printf '\\033[1;${code}m%s\\033[0m\\n' ${shellEscape(text)}`;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
function selfClosingTeamPaneCommand(command = '') {
|
|
227
|
+
return [
|
|
228
|
+
`SKS_TEAM_PANE_SELF_CLOSE=1 ${command}`,
|
|
229
|
+
'status=$?',
|
|
230
|
+
'if [ "${SKS_TEAM_PANE_SELF_CLOSE:-1}" = "1" ] && [ -n "${TMUX_PANE:-}" ]; then tmux kill-pane -t "$TMUX_PANE" >/dev/null 2>&1 || true; fi',
|
|
231
|
+
'exit "$status"'
|
|
232
|
+
].join('; ');
|
|
233
|
+
}
|
|
234
|
+
|
|
226
235
|
function compactTeamPaneBanner({ missionId, agentId, phase, style, overview = false } = {}) {
|
|
227
236
|
const role = overview ? 'overview' : `${style.label} (${style.color_name})`;
|
|
228
237
|
return [
|
|
@@ -263,24 +272,26 @@ function teamLaneTitle(agentId = '') {
|
|
|
263
272
|
export function teamAgentCommand(root, missionId, agentId, phase) {
|
|
264
273
|
const style = teamLaneStyle(agentId);
|
|
265
274
|
const title = teamLaneTitle(agentId);
|
|
275
|
+
const laneCommand = `node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} team lane ${shellEscape(missionId)} --agent ${shellEscape(agentId)} --phase ${shellEscape(phase)} --follow --lines 12`;
|
|
266
276
|
return [
|
|
267
277
|
terminalTitleCommand(title),
|
|
268
278
|
'clear',
|
|
269
279
|
colorizedLaneBannerCommand(compactTeamPaneBanner({ missionId, agentId, phase, style }), style.color),
|
|
270
280
|
`cd ${shellEscape(root)}`,
|
|
271
|
-
|
|
281
|
+
selfClosingTeamPaneCommand(laneCommand)
|
|
272
282
|
].join('; ');
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
export function teamOverviewCommand(root, missionId) {
|
|
276
286
|
const style = teamLaneStyle('mission_overview');
|
|
277
287
|
const title = teamLaneTitle('mission_overview');
|
|
288
|
+
const watchCommand = `node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} team watch ${shellEscape(missionId)} --follow --lines 18`;
|
|
278
289
|
return [
|
|
279
290
|
terminalTitleCommand(title),
|
|
280
291
|
'clear',
|
|
281
292
|
colorizedLaneBannerCommand(compactTeamPaneBanner({ missionId, agentId: 'mission_overview', style, overview: true }), style.color),
|
|
282
293
|
`cd ${shellEscape(root)}`,
|
|
283
|
-
|
|
294
|
+
selfClosingTeamPaneCommand(watchCommand)
|
|
284
295
|
].join('; ');
|
|
285
296
|
}
|
|
286
297
|
|
|
@@ -1026,7 +1037,7 @@ export async function cleanupTmuxTeamView({ root, missionId = 'latest', closeSes
|
|
|
1026
1037
|
};
|
|
1027
1038
|
}
|
|
1028
1039
|
const dynamicCleanup = await reconcileTmuxTeamCockpit({ root: resolvedRoot, missionId: record.mission_id || missionId, close: true }).catch((err) => ({ ok: false, skipped: true, reason: err.message || 'dynamic tmux cleanup failed' }));
|
|
1029
|
-
const recordedCleanup = dynamicCleanup?.ok
|
|
1040
|
+
const recordedCleanup = dynamicCleanup?.ok && dynamicCleanup.closed_lane_count > 0
|
|
1030
1041
|
? null
|
|
1031
1042
|
: await cleanupRecordedTmuxTeamPanes(resolvedRoot, record.mission_id || missionId, record).catch((err) => ({ ok: false, skipped: true, reason: err.message || 'recorded tmux cleanup failed' }));
|
|
1032
1043
|
const legacyCleanup = (dynamicCleanup?.closed_lane_count || recordedCleanup?.closed_lane_count)
|
|
@@ -1130,10 +1141,16 @@ async function cleanupRecordedTmuxTeamPanes(root, missionId, record = {}) {
|
|
|
1130
1141
|
const tmuxBin = await findTmuxBin() || 'tmux';
|
|
1131
1142
|
const paneList = await listTmuxWindowPanes(tmuxBin, target);
|
|
1132
1143
|
if (!paneList.ok) return { ok: false, skipped: true, reason: paneList.stderr, closed_lane_count: 0 };
|
|
1144
|
+
const recordedPaneIds = new Set([
|
|
1145
|
+
...(Array.isArray(record.panes) ? record.panes : []),
|
|
1146
|
+
...(Array.isArray(cockpit.panes) ? cockpit.panes : [])
|
|
1147
|
+
].map((pane) => pane?.pane_id).filter(Boolean));
|
|
1133
1148
|
const managed = paneList.panes.filter((pane) => pane.managed && pane.mission_id === id);
|
|
1149
|
+
const recorded = paneList.panes.filter((pane) => recordedPaneIds.has(pane.pane_id));
|
|
1150
|
+
const targets = managed.length ? managed : recorded;
|
|
1134
1151
|
const closed = [];
|
|
1135
1152
|
const failed = [];
|
|
1136
|
-
for (const pane of
|
|
1153
|
+
for (const pane of targets) {
|
|
1137
1154
|
const kill = await tmuxRun(tmuxBin, ['kill-pane', '-t', pane.pane_id], { timeoutMs: 5000 });
|
|
1138
1155
|
if (kill.code === 0) closed.push({ pane_id: pane.pane_id, agent: pane.agent, role: pane.role });
|
|
1139
1156
|
else failed.push({ pane_id: pane.pane_id, agent: pane.agent, stderr: kill.stderr || kill.stdout || 'tmux kill-pane failed' });
|
|
@@ -1148,9 +1165,12 @@ async function cleanupRecordedTmuxTeamPanes(root, missionId, record = {}) {
|
|
|
1148
1165
|
session: cockpit.session || record.session,
|
|
1149
1166
|
window_id: cockpit.window_id || record.window_id || null,
|
|
1150
1167
|
closed_lane_count: closed.length,
|
|
1168
|
+
fallback_used: !managed.length && recorded.length > 0,
|
|
1151
1169
|
closed,
|
|
1152
1170
|
failed,
|
|
1153
|
-
reason: closed.length
|
|
1171
|
+
reason: closed.length
|
|
1172
|
+
? (managed.length ? 'closed recorded managed panes' : 'closed panes by recorded SKS pane ids')
|
|
1173
|
+
: 'no recorded managed panes found'
|
|
1154
1174
|
};
|
|
1155
1175
|
}
|
|
1156
1176
|
|