sneakoscope 0.7.60 → 0.7.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +5 -2
- package/src/cli/install-helpers.mjs +10 -5
- package/src/cli/main.mjs +110 -25
- package/src/cli/maintenance-commands.mjs +55 -9
- package/src/core/codex-app.mjs +23 -8
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +21 -0
- package/src/core/init.mjs +11 -6
- package/src/core/mission.mjs +2 -2
- package/src/core/pipeline.mjs +16 -4
- package/src/core/routes.mjs +28 -6
- package/src/core/team-live.mjs +24 -3
- package/src/core/tmux-ui.mjs +382 -115
package/README.md
CHANGED
|
@@ -239,9 +239,9 @@ sks team log latest
|
|
|
239
239
|
|
|
240
240
|
By default, Team missions keep at least five QA/reviewer lanes active. Use explicit role counts only when you need to raise or otherwise pin the lane mix for a specific mission.
|
|
241
241
|
|
|
242
|
-
Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and
|
|
242
|
+
Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and reconciles split live lanes inside the current SKS-owned tmux session when available. Outside an SKS tmux session, `sks team open-tmux --separate-session` keeps the named `sks-team-*` fallback view. Use `--no-open-tmux` for artifact-only mission creation. The default terminal output stays compact: mission id, agent count, role count, tmux status, watch command, and artifact directory. `sks team dashboard` renders the cockpit panes for mission overview, agent lanes, task DAG, QA/dogfood, artifacts/evidence, and performance.
|
|
243
243
|
|
|
244
|
-
The tmux Team launch is a live orchestration screen in one tmux window: the
|
|
244
|
+
The tmux Team launch is a live orchestration screen in one tmux window: the main Codex pane stays alive, a managed overview pane follows `sks team watch <mission-id> --follow`, and neighboring managed split panes follow individual `sks team lane <mission-id> --agent <name> --follow` views. Pane headers show only mission, lane, phase, follow command, and cleanup command. SKS tags Team panes with tmux user options, closes only those managed panes when agent lanes complete or cleanup is requested, and recalculates the tiled layout after split/close operations. The separate `sks-team-*` session remains available as a fallback. SKS gives lanes role-specific colors, labels, and terminal titles, so scouts, planning/debate voices, executors, reviewers, and safety lanes are visually distinct while detailed evidence is mirrored into `team-transcript.jsonl`, `team-live.md`, and `team-dashboard.json`.
|
|
245
245
|
|
|
246
246
|
Team roster and runtime artifacts now include per-agent Fast reasoning metadata. Simple bounded Team lanes can use low reasoning, tool-heavy runtime/CLI/tmux work uses medium, and knowledge, current-docs, safety, DB, release, commit, or research-heavy lanes use high or xhigh as appropriate instead of opening every scout at high.
|
|
247
247
|
|
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.63",
|
|
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",
|
|
@@ -89,5 +89,8 @@
|
|
|
89
89
|
"mcp-safety",
|
|
90
90
|
"db-guardian"
|
|
91
91
|
],
|
|
92
|
-
"license": "MIT"
|
|
92
|
+
"license": "MIT",
|
|
93
|
+
"dependencies": {
|
|
94
|
+
"figlet": "^1.11.0"
|
|
95
|
+
}
|
|
93
96
|
}
|
|
@@ -307,12 +307,17 @@ export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
|
|
|
307
307
|
export function normalizeCodexFastModeUiConfig(text = '') {
|
|
308
308
|
let next = removeLegacyTopLevelCodexModeLocks(text);
|
|
309
309
|
next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
|
|
310
|
-
next = removeTomlTableKey(next, 'features', '
|
|
310
|
+
next = removeTomlTableKey(next, 'features', 'codex_hooks');
|
|
311
311
|
next = upsertTopLevelTomlString(next, 'model', 'gpt-5.5');
|
|
312
312
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
313
|
-
next = upsertTomlTableKey(next, 'features', '
|
|
313
|
+
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
314
|
+
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
314
315
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
315
316
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
317
|
+
next = upsertTomlTableKey(next, 'features', 'codex_git_commit = true');
|
|
318
|
+
next = upsertTomlTableKey(next, 'features', 'computer_use = true');
|
|
319
|
+
next = upsertTomlTableKey(next, 'features', 'apps = true');
|
|
320
|
+
next = upsertTomlTableKey(next, 'features', 'plugins = true');
|
|
316
321
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
|
|
317
322
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
|
|
318
323
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
|
|
@@ -993,7 +998,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
993
998
|
if (codexLbNotConfigured.code !== 0 || String(codexLbNotConfigured.stdout || '').includes('codex-lb auth:')) throw new Error('selftest failed: postinstall should stay quiet when codex-lb is not configured');
|
|
994
999
|
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
995
1000
|
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest failed: codex-lb status did not advertise repair command');
|
|
996
|
-
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('
|
|
1001
|
+
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 failed: 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');
|
|
997
1002
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
998
1003
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
|
|
999
1004
|
if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest failed: tmux launch command without args did not force GPT-5.5');
|
|
@@ -1007,7 +1012,7 @@ function hasTopLevelCodexModeLock(text = '') {
|
|
|
1007
1012
|
return /(^|\n)\s*model\s*=\s*"codex-lb"\s*(\n|$)/.test(text) || /(^|\n)\s*model_provider\s*=\s*"openai"\s*(\n|$)/.test(text);
|
|
1008
1013
|
}
|
|
1009
1014
|
|
|
1010
|
-
function
|
|
1015
|
+
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
1011
1016
|
const lines = String(text || '').split('\n');
|
|
1012
1017
|
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
1013
1018
|
if (start === -1) return false;
|
|
@@ -1018,5 +1023,5 @@ function hasLegacyHooksFeatureFlag(text = '') {
|
|
|
1018
1023
|
break;
|
|
1019
1024
|
}
|
|
1020
1025
|
}
|
|
1021
|
-
return lines.slice(start + 1, end).some((line) => /^\s*
|
|
1026
|
+
return lines.slice(start + 1, end).some((line) => /^\s*codex_hooks\s*=/.test(line));
|
|
1022
1027
|
}
|
package/src/cli/main.mjs
CHANGED
|
@@ -75,7 +75,7 @@ import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
|
75
75
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
76
76
|
import { codexAppRemoteControlCommand } from './codex-app-command.mjs';
|
|
77
77
|
import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
78
|
-
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
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
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';
|
|
@@ -156,8 +156,7 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
156
156
|
function help(args = []) {
|
|
157
157
|
const topic = args[0];
|
|
158
158
|
if (topic) return usage([topic]);
|
|
159
|
-
console.log(
|
|
160
|
-
Sneakoscope Codex
|
|
159
|
+
console.log(`${sksAsciiLogo()}
|
|
161
160
|
|
|
162
161
|
Usage:
|
|
163
162
|
sks
|
|
@@ -205,9 +204,11 @@ Usage:
|
|
|
205
204
|
sks goal pause|resume|clear <mission-id|latest>
|
|
206
205
|
sks goal status <mission-id|latest>
|
|
207
206
|
sks team "task" [executor:5 reviewer:6 user:1] [--json]
|
|
208
|
-
sks team log|tail|watch|lane|status|dashboard [mission-id|latest]
|
|
207
|
+
sks team log|tail|watch|lane|status|dashboard|open-tmux|attach-tmux [mission-id|latest]
|
|
209
208
|
sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."
|
|
210
209
|
sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."
|
|
210
|
+
sks team open-tmux [mission-id|latest] [--no-attach|--separate-session]
|
|
211
|
+
sks team attach-tmux [mission-id|latest]
|
|
211
212
|
sks team cleanup-tmux [mission-id|latest]
|
|
212
213
|
sks research prepare "topic" [--depth frontier]
|
|
213
214
|
sks research run <mission-id|latest> [--mock] [--max-cycles N]
|
|
@@ -272,7 +273,7 @@ async function wizard(args = []) {
|
|
|
272
273
|
if (!shouldShowWizard() && !flag(args, '--force')) return help();
|
|
273
274
|
const rl = readline.createInterface({ input, output });
|
|
274
275
|
try {
|
|
275
|
-
console.log(
|
|
276
|
+
console.log(`${sksAsciiLogo()}\nSetup UI\n`);
|
|
276
277
|
const currentPackage = await effectivePackageVersion();
|
|
277
278
|
console.log(`Current package: ${currentPackage}`);
|
|
278
279
|
const latest = await npmPackageVersion('sneakoscope');
|
|
@@ -340,7 +341,7 @@ async function updateCheck(args = []) {
|
|
|
340
341
|
error: latest.error || null
|
|
341
342
|
};
|
|
342
343
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
343
|
-
console.log(
|
|
344
|
+
console.log(`${sksAsciiLogo()}\nUpdate Check`);
|
|
344
345
|
console.log(`Current: ${result.current}`);
|
|
345
346
|
console.log(`Latest: ${result.latest || 'unknown'}`);
|
|
346
347
|
console.log(`Update: ${result.update_available ? 'available' : 'not needed'}`);
|
|
@@ -352,7 +353,7 @@ const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: direct answers -> $Answe
|
|
|
352
353
|
|
|
353
354
|
function commands(args = []) {
|
|
354
355
|
if (flag(args, '--json')) return console.log(JSON.stringify({ aliases: ['sks', 'sneakoscope'], dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES, commands: COMMAND_CATALOG }, null, 2));
|
|
355
|
-
console.log(
|
|
356
|
+
console.log(`${sksAsciiLogo()}\nCommands\n`);
|
|
356
357
|
console.log('Aliases: sks, sneakoscope\n');
|
|
357
358
|
const width = Math.max(...COMMAND_CATALOG.map((c) => c.usage.length));
|
|
358
359
|
for (const c of COMMAND_CATALOG) console.log(`${c.usage.padEnd(width)} ${c.description}`);
|
|
@@ -375,7 +376,7 @@ async function rootCommand(args = []) {
|
|
|
375
376
|
using_global_root: !project
|
|
376
377
|
};
|
|
377
378
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
378
|
-
console.log(
|
|
379
|
+
console.log(`${sksAsciiLogo()}\nRoot\n`);
|
|
379
380
|
console.log(`Mode: ${result.mode}`);
|
|
380
381
|
console.log(`Active root: ${active}`);
|
|
381
382
|
console.log(`Project: ${project || 'none'}`);
|
|
@@ -385,7 +386,7 @@ async function rootCommand(args = []) {
|
|
|
385
386
|
|
|
386
387
|
function dollarCommands(args = []) {
|
|
387
388
|
if (flag(args, '--json')) return console.log(JSON.stringify({ dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES }, null, 2));
|
|
388
|
-
console.log(
|
|
389
|
+
console.log(`${sksAsciiLogo()}\n$ Commands\n`);
|
|
389
390
|
console.log('Use these inside Codex App or another agent prompt. Shells treat $ as variable syntax, so these are prompt commands, not terminal commands.\n');
|
|
390
391
|
console.log(formatDollarCommandsDetailed());
|
|
391
392
|
console.log(`\nCanonical Codex App picker skills: ${DOLLAR_COMMAND_ALIASES.map((x) => x.app_skill).join(', ')}`);
|
|
@@ -1406,7 +1407,8 @@ async function codexAppHelp(args = []) {
|
|
|
1406
1407
|
const status = await codexAppIntegrationStatus();
|
|
1407
1408
|
const skills = await codexAppSkillReadiness();
|
|
1408
1409
|
console.log([
|
|
1409
|
-
|
|
1410
|
+
sksAsciiLogo(), '',
|
|
1411
|
+
'Codex App', '',
|
|
1410
1412
|
formatCodexAppStatus(status), '',
|
|
1411
1413
|
`Skills: project=${skills.project.ok ? 'ok' : `missing ${skills.project.missing.length}`} global=${skills.global.ok ? 'ok' : `missing ${skills.global.missing.length}`}`, '',
|
|
1412
1414
|
'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks codex-app remote-control --status', ' sks tmux check', '',
|
|
@@ -1417,7 +1419,9 @@ async function codexAppHelp(args = []) {
|
|
|
1417
1419
|
}
|
|
1418
1420
|
|
|
1419
1421
|
function aliases() {
|
|
1420
|
-
console.log(
|
|
1422
|
+
console.log(`${sksAsciiLogo()}
|
|
1423
|
+
|
|
1424
|
+
Aliases
|
|
1421
1425
|
|
|
1422
1426
|
Binary aliases:
|
|
1423
1427
|
sks
|
|
@@ -1443,14 +1447,14 @@ Examples:
|
|
|
1443
1447
|
function usage(args = []) {
|
|
1444
1448
|
const topic = String(args[0] || 'overview').toLowerCase();
|
|
1445
1449
|
const blocks = {
|
|
1446
|
-
overview: ['
|
|
1450
|
+
overview: [sksAsciiLogo(), '', 'Usage', '', 'Discover:', ' sks commands', ' sks quickstart', ' sks root', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks tmux check', ' sks dollar-commands', '', `Topics: ${USAGE_TOPICS}`],
|
|
1447
1451
|
install: ['Install', '', '1. Global install:', ' npm i -g sneakoscope', '', '2. Bootstrap and check dependencies:', ' sks bootstrap', ' sks deps check', '', '3. Confirm Codex App commands:', ' sks codex-app check', ' sks dollar-commands', '', '4. Optional codex-lb key setup for CLI sks runs:', ' sks codex-lb setup --host <domain> --api-key <key>', ' sks codex-lb repair', ' sks', '', 'Fallback:', ' npx -y -p sneakoscope sks root', '', 'Project:', ' npm i -D sneakoscope', ' npx sks setup --install-scope project'],
|
|
1448
1452
|
bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and tmux.'],
|
|
1449
1453
|
root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
|
|
1450
1454
|
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew after Y/n approval for missing installs or Homebrew-managed upgrades. If PATH resolves an npm-managed tmux, SKS prompts for npm i -g tmux@latest instead. Unknown non-Homebrew tmux paths are reported as conflicts.'],
|
|
1451
1455
|
tmux: ['tmux', '', ' sks', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'Running bare `sks` opens or reuses the default tmux Codex CLI session in fast-high mode: --model gpt-5.5 -c model_reasoning_effort="high". SKS always forces gpt-5.5; SKS_CODEX_MODEL and SKS_CODEX_FAST_HIGH=0 cannot downgrade or remove that model pin. Use SKS_CODEX_REASONING only for reasoning effort. Before launch, SKS checks npm @openai/codex@latest and prompts Y/n when the installed Codex CLI is missing or outdated. Use `sks tmux open` when you need explicit session/workspace flags, and `sks help` for CLI help.'],
|
|
1452
1456
|
openclaw: ['OpenClaw', '', ' sks openclaw install', ' sks openclaw path', ' sks openclaw print SKILL.md', '', 'Installs an OpenClaw skill package under ~/.openclaw/skills/sneakoscope-codex so OpenClaw agents can attach skills: [sneakoscope-codex] with the shell tool and call local SKS commands from a project root.'],
|
|
1453
|
-
team: ['Team', '', ' sks team "task" executor:5 reviewer:6 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team auto-seals a route contract, opens scout-first tmux lanes when available, then runs scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1457
|
+
team: ['Team', '', ' sks team "task" executor:5 reviewer:6 user:1', ' sks team open-tmux latest', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team auto-seals a route contract, opens scout-first tmux lanes when available, then runs scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1454
1458
|
'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
|
|
1455
1459
|
ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT infers delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/solution/aha mappings before source research, design-system work, HTML/PDF export, render QA, fact-ledger validation, and bounded review-loop validation. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in ppt-parallel-report.json. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; Codex App $imagegen/gpt-image-2 and Context7 are conditional only when the sealed PPT contract needs raster assets, slide visual critique, or current external docs. Missing required $imagegen/gpt-image-2 output blocks instead of being simulated.'],
|
|
1456
1460
|
'image-ux-review': ['Image UX Review', '', ' $Image-UX-Review localhost 화면을 이미지 생성 리뷰 루프로 검수해줘', ' $UX-Review 이 스크린샷을 gpt-image-2 콜아웃 리뷰로 분석하고 고쳐줘', ' sks image-ux-review status latest --json', '', '$Image-UX-Review captures or receives source UI screenshots, runs Codex App $imagegen/gpt-image-2 to create generated annotated review images with numbered callouts, then extracts those generated images into image-ux-issue-ledger.json. Text-only screenshot critique cannot pass image-ux-review-gate.json; missing generated review images remain an explicit blocker.'],
|
|
@@ -1589,7 +1593,7 @@ async function setup(args) {
|
|
|
1589
1593
|
next: ['sks context7 check', 'sks selftest --mock', 'sks doctor', 'sks commands']
|
|
1590
1594
|
};
|
|
1591
1595
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
1592
|
-
console.log(
|
|
1596
|
+
console.log(`${sksAsciiLogo()}\nSetup\n`);
|
|
1593
1597
|
console.log(`Project: ${root}`);
|
|
1594
1598
|
console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
|
|
1595
1599
|
console.log(`CLI tools: Codex ${formatCodexCliToolStatus(cliTools.codex)}; tmux ${tmuxStatusKind(cliTools.tmux)} ${cliTools.tmux.version || cliTools.tmux.error || ''}`.trimEnd());
|
|
@@ -1713,7 +1717,7 @@ async function doctor(args) {
|
|
|
1713
1717
|
result.ready = !result.harness_conflicts.hard_block && nodeOk && Boolean(codex.bin) && install.ok && result.sneakoscope.ok && result.context7.ok && appRuntime.ok && result.runtime.tmux.ok && result.harness_guard.ok && result.versioning.ok && result.db_guard.ok && result.codex_app.ok && result.skills.ok && result.global_skills.ok;
|
|
1714
1718
|
if (result.harness_conflicts.hard_block) process.exitCode = 1;
|
|
1715
1719
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
1716
|
-
console.log(
|
|
1720
|
+
console.log(`${sksAsciiLogo()}\nDoctor\n`);
|
|
1717
1721
|
console.log(`Node: ${nodeOk ? 'ok' : 'fail'} ${process.version}`);
|
|
1718
1722
|
console.log(`Project: ${root}`);
|
|
1719
1723
|
console.log(`Codex: ${codex.bin ? 'ok' : 'missing'} ${codex.version || ''}`);
|
|
@@ -1779,7 +1783,7 @@ async function init(args) {
|
|
|
1779
1783
|
const localOnly = flag(args, '--local-only');
|
|
1780
1784
|
const globalCommand = await globalSksCommand();
|
|
1781
1785
|
const res = await initProject(root, { force: flag(args, '--force'), installScope, globalCommand, localOnly });
|
|
1782
|
-
console.log(`Initialized
|
|
1786
|
+
console.log(`Initialized SKS in ${root}`);
|
|
1783
1787
|
console.log(`Install scope: ${installScope} (${sksCommandPrefix(installScope, { globalCommand })})`);
|
|
1784
1788
|
if (localOnly) console.log('Git mode: local-only (.git/info/exclude)');
|
|
1785
1789
|
else console.log('Git mode: shared .gitignore');
|
|
@@ -1920,7 +1924,7 @@ function hasTopLevelCodexModeLock(text = '') {
|
|
|
1920
1924
|
return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top);
|
|
1921
1925
|
}
|
|
1922
1926
|
|
|
1923
|
-
function
|
|
1927
|
+
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
1924
1928
|
const lines = String(text || '').split('\n');
|
|
1925
1929
|
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
1926
1930
|
if (start === -1) return false;
|
|
@@ -1931,7 +1935,13 @@ function hasLegacyHooksFeatureFlag(text = '') {
|
|
|
1931
1935
|
break;
|
|
1932
1936
|
}
|
|
1933
1937
|
}
|
|
1934
|
-
return lines.slice(start + 1, end).some((line) => /^\s*
|
|
1938
|
+
return lines.slice(start + 1, end).some((line) => /^\s*codex_hooks\s*=/.test(line));
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
const REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS = ['hooks', 'multi_agent', 'fast_mode', 'fast_mode_ui', 'codex_git_commit', 'computer_use', 'apps', 'plugins'];
|
|
1942
|
+
|
|
1943
|
+
function missingGeneratedCodexAppFeatureFlags(text = '') {
|
|
1944
|
+
return REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS.filter((name) => !String(text || '').includes(`${name} = true`));
|
|
1935
1945
|
}
|
|
1936
1946
|
|
|
1937
1947
|
async function resolveMissionId(root, arg) { return (!arg || arg === 'latest') ? findLatestMission(root) : arg; }
|
|
@@ -2148,6 +2158,8 @@ async function selftest() {
|
|
|
2148
2158
|
if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
|
|
2149
2159
|
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 failed: postinstall did not auto-run global forced bootstrap');
|
|
2150
2160
|
if (!(await exists(path.join(postinstallSetupTmp, '.codex', 'hooks.json')))) throw new Error('selftest failed: postinstall did not create project hooks during automatic bootstrap');
|
|
2161
|
+
const postinstallSetupConfig = await safeReadText(path.join(postinstallSetupTmp, '.codex', 'config.toml'));
|
|
2162
|
+
if (missingGeneratedCodexAppFeatureFlags(postinstallSetupConfig).length || hasDeprecatedCodexHooksFeatureFlag(postinstallSetupConfig)) throw new Error('selftest failed: postinstall automatic bootstrap did not preserve required Codex App feature flags or migrate deprecated codex_hooks');
|
|
2151
2163
|
if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest failed: postinstall did not report automatic global Codex App skills');
|
|
2152
2164
|
if (!String(postinstallSetup.stdout || '').includes('Removed stale generated skill shadow(s):')) throw new Error('selftest failed: postinstall did not report stale first-party plugin shadow cleanup');
|
|
2153
2165
|
const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
|
|
@@ -2182,6 +2194,8 @@ async function selftest() {
|
|
|
2182
2194
|
if (postinstallNoMarker.code !== 0) throw new Error(`selftest failed: no-marker postinstall bootstrap exited ${postinstallNoMarker.code}: ${postinstallNoMarker.stderr}`);
|
|
2183
2195
|
if (!String(postinstallNoMarker.stdout || '').includes('no project marker found; auto-running global SKS runtime bootstrap')) throw new Error('selftest failed: no-marker postinstall did not report global runtime bootstrap');
|
|
2184
2196
|
if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest failed: no-marker postinstall did not bootstrap global runtime root');
|
|
2197
|
+
const postinstallNoMarkerConfig = await safeReadText(path.join(postinstallNoMarkerGlobalRoot, '.codex', 'config.toml'));
|
|
2198
|
+
if (missingGeneratedCodexAppFeatureFlags(postinstallNoMarkerConfig).length || hasDeprecatedCodexHooksFeatureFlag(postinstallNoMarkerConfig)) throw new Error('selftest failed: no-marker postinstall bootstrap did not preserve required Codex App feature flags or migrate deprecated codex_hooks');
|
|
2185
2199
|
if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest failed: no-marker postinstall polluted install cwd');
|
|
2186
2200
|
if (await exists(path.join(postinstallNoMarkerGlobalRoot, '.gitignore'))) throw new Error('selftest failed: global runtime bootstrap without project git wrote shared .gitignore');
|
|
2187
2201
|
const bootstrapJsonTmp = tmpdir();
|
|
@@ -2474,6 +2488,13 @@ async function selftest() {
|
|
|
2474
2488
|
const camelHookGuardResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'pre-tool'], { cwd: tmp, input: camelHookGuardPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2475
2489
|
const camelHookGuardJson = JSON.parse(camelHookGuardResult.stdout);
|
|
2476
2490
|
if (camelHookGuardJson.decision !== 'block') throw new Error('selftest failed: hook did not block camelCase Codex tool payload');
|
|
2491
|
+
await setCurrent(tmp, { mode: 'QALOOP', phase: 'QALOOP_RUNNING_NO_QUESTIONS', route: 'QALoop', implementation_allowed: true });
|
|
2492
|
+
const codexGitPermissionResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'permission-request'], { cwd: tmp, input: JSON.stringify({ cwd: tmp, command: 'git push origin dev', action: 'Codex App Git Actions Push' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2493
|
+
const codexGitPermissionJson = JSON.parse(codexGitPermissionResult.stdout);
|
|
2494
|
+
if (codexGitPermissionJson.hookSpecificOutput?.decision?.behavior === 'deny') throw new Error('selftest failed: Codex App git push permission was denied during no-question mode');
|
|
2495
|
+
const codexGitForcePermissionResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'permission-request'], { cwd: tmp, input: JSON.stringify({ cwd: tmp, command: 'git push --force origin dev', action: 'Codex App Git Actions Push' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2496
|
+
const codexGitForcePermissionJson = JSON.parse(codexGitForcePermissionResult.stdout);
|
|
2497
|
+
if (codexGitForcePermissionJson.hookSpecificOutput?.decision?.behavior !== 'deny') throw new Error('selftest failed: force-push permission should stay denied during no-question mode');
|
|
2477
2498
|
if (new Set(DOLLAR_COMMANDS.map((c) => c.command)).size !== DOLLAR_COMMANDS.length) throw new Error('selftest failed: duplicate dollar commands');
|
|
2478
2499
|
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$QA-LOOP' && alias.app_skill === '$qa-loop')) throw new Error('selftest failed: $QA-LOOP picker skill missing');
|
|
2479
2500
|
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Team' && alias.app_skill === '$from-chat-img')) throw new Error('selftest failed: $From-Chat-IMG picker skill missing');
|
|
@@ -2929,8 +2950,8 @@ async function selftest() {
|
|
|
2929
2950
|
if (wikiContext.includes('MANDATORY ambiguity-removal gate activated') || wikiContext.includes('Mission:')) throw new Error('selftest failed: Wiki route created ambiguity mission state');
|
|
2930
2951
|
if (!wikiJson.systemMessage?.includes('wiki refresh')) throw new Error('selftest failed: Wiki route missing system message');
|
|
2931
2952
|
const codexConfigText = await safeReadText(path.join(tmp, '.codex', 'config.toml'));
|
|
2932
|
-
|
|
2933
|
-
if (
|
|
2953
|
+
const missingCodexConfigFlags = missingGeneratedCodexAppFeatureFlags(codexConfigText);
|
|
2954
|
+
if (missingCodexConfigFlags.length || hasDeprecatedCodexHooksFeatureFlag(codexConfigText)) throw new Error(`selftest failed: generated Codex App feature flags missing or deprecated: ${missingCodexConfigFlags.join(', ')}`);
|
|
2934
2955
|
if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest failed: Context7 MCP not configured');
|
|
2935
2956
|
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 failed: GPT-5.5 reasoning profiles not configured');
|
|
2936
2957
|
if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest failed: generated sks-mad-high profile is not full access');
|
|
@@ -2938,13 +2959,29 @@ async function selftest() {
|
|
|
2938
2959
|
if (!codexConfigText.includes('[agents.team_consensus]')) throw new Error('selftest failed: team_consensus agent not configured');
|
|
2939
2960
|
const preservedConfigTmp = tmpdir();
|
|
2940
2961
|
await ensureDir(path.join(preservedConfigTmp, '.codex'));
|
|
2941
|
-
await writeTextAtomic(path.join(preservedConfigTmp, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "high"\nservice_tier = "fast"\n\n[notice]\nfast_default_opt_out = true\nkeep = true\n\n[features]\
|
|
2962
|
+
await writeTextAtomic(path.join(preservedConfigTmp, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "high"\nservice_tier = "fast"\n\n[notice]\nfast_default_opt_out = true\nkeep = true\n\n[features]\ncodex_hooks = true\nfast_mode_ui = false\ncodex_git_commit = false\ncomputer_use = false\napps = false\nplugins = false\ncustom_preview = true\n\n[user.fast_mode]\nvisible = true\n');
|
|
2942
2963
|
await initProject(preservedConfigTmp, {});
|
|
2943
2964
|
const preservedConfig = await safeReadText(path.join(preservedConfigTmp, '.codex', 'config.toml'));
|
|
2944
2965
|
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 failed: Codex config merge dropped or failed to enable Fast mode defaults and GPT-5.5');
|
|
2945
2966
|
if (preservedConfig.includes('fast_default_opt_out = true') || !preservedConfig.includes('keep = true')) throw new Error('selftest failed: Codex config merge did not remove stale Fast opt-out notice while preserving other notice keys');
|
|
2946
|
-
|
|
2967
|
+
const missingPreservedFlags = missingGeneratedCodexAppFeatureFlags(preservedConfig);
|
|
2968
|
+
if (missingPreservedFlags.length || hasDeprecatedCodexHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('custom_preview = true') || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error(`selftest failed: Codex config merge did not add required app feature flags, preserve existing feature flags, or remove deprecated codex_hooks: ${missingPreservedFlags.join(', ')}`);
|
|
2947
2969
|
if (hasTopLevelCodexModeLock(preservedConfig)) throw new Error('selftest failed: Codex config merge left top-level legacy model/reasoning locks that hide Fast mode UI');
|
|
2970
|
+
const appFeatureTmp = tmpdir();
|
|
2971
|
+
const fakeCodexApp = path.join(appFeatureTmp, 'Codex.app');
|
|
2972
|
+
const fakeCodexBinDir = path.join(appFeatureTmp, 'bin');
|
|
2973
|
+
await ensureDir(fakeCodexApp);
|
|
2974
|
+
await ensureDir(fakeCodexBinDir);
|
|
2975
|
+
const fakeCodex = path.join(fakeCodexBinDir, 'codex');
|
|
2976
|
+
await writeTextAtomic(fakeCodex, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\ncodex_git_commit under development true\ncomputer_use stable true\nfast_mode stable true\nhooks stable true\nimage_generation stable true\nplugins stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
2977
|
+
await fsp.chmod(fakeCodex, 0o755);
|
|
2978
|
+
const codexAppFeatureStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodex, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
2979
|
+
if (!codexAppFeatureStatus.ok || !codexAppFeatureStatus.features?.required_flags_ok || !codexAppFeatureStatus.features?.codex_git_commit) throw new Error('selftest failed: codex-app check did not accept required app feature flags including under-development codex_git_commit');
|
|
2980
|
+
const fakeCodexMissing = path.join(fakeCodexBinDir, 'codex-missing-git-commit');
|
|
2981
|
+
await writeTextAtomic(fakeCodexMissing, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\ncodex_git_commit under development false\ncomputer_use stable true\nfast_mode stable true\nhooks stable true\nimage_generation stable true\nplugins stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
2982
|
+
await fsp.chmod(fakeCodexMissing, 0o755);
|
|
2983
|
+
const codexAppMissingFeatureStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodexMissing, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
2984
|
+
if (codexAppMissingFeatureStatus.ok || codexAppMissingFeatureStatus.features?.required_flags_ok || codexAppMissingFeatureStatus.features?.codex_git_commit) throw new Error('selftest failed: codex-app check did not block disabled codex_git_commit feature flag');
|
|
2948
2985
|
const autoReviewHome = path.join(tmp, 'auto-review-home');
|
|
2949
2986
|
const autoReviewEnv = { HOME: autoReviewHome };
|
|
2950
2987
|
const autoReviewEnabled = await enableAutoReview({ env: autoReviewEnv, high: true });
|
|
@@ -3160,7 +3197,9 @@ async function selftest() {
|
|
|
3160
3197
|
await writeJsonAtomic(path.join(teamDir, 'team-plan.json'), teamPlan);
|
|
3161
3198
|
if (teamPlan.agent_session_count !== 5) throw new Error('selftest failed: team default sessions not 5');
|
|
3162
3199
|
if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 5) throw new Error('selftest failed: team default role counts invalid');
|
|
3163
|
-
|
|
3200
|
+
const teamPlanFeatureFlags = teamPlan.codex_config_required?.features || {};
|
|
3201
|
+
const missingTeamPlanFeatureFlags = REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS.filter((name) => teamPlanFeatureFlags[name] !== true);
|
|
3202
|
+
if (missingTeamPlanFeatureFlags.length || teamPlanFeatureFlags.codex_hooks === true) throw new Error(`selftest failed: team plan Codex config missing required app flags or still uses deprecated codex_hooks: ${missingTeamPlanFeatureFlags.join(', ')}`);
|
|
3164
3203
|
if (!teamPlan.review_gate?.passed || teamPlan.review_gate.required_reviewer_lanes !== 5) throw new Error('selftest failed: team review policy gate did not pass default plan');
|
|
3165
3204
|
if (teamPlan.codex_config_required?.service_tier !== 'fast' || teamPlan.reasoning?.service_tier !== 'fast') throw new Error('selftest failed: team plan did not require Fast service tier');
|
|
3166
3205
|
if (!teamPlan.goal_continuation?.enabled || teamPlan.goal_continuation?.mode !== 'ambient_codex_native_goal_overlay') throw new Error('selftest failed: Team plan did not include ambient Goal continuation');
|
|
@@ -3199,6 +3238,7 @@ async function selftest() {
|
|
|
3199
3238
|
if (!fromChatTeamPlan.invariants.some((item) => item.includes(FROM_CHAT_IMG_CHECKLIST_ARTIFACT)) || !fromChatTeamPlan.invariants.some((item) => item.includes(FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT)) || !fromChatTeamPlan.invariants.some((item) => item.includes(FROM_CHAT_IMG_QA_LOOP_ARTIFACT))) throw new Error('selftest failed: From-Chat-IMG team plan missing checklist/temp TriWiki/QA invariants');
|
|
3200
3239
|
const teamWorkflow = teamWorkflowMarkdown(teamPlan);
|
|
3201
3240
|
if (!teamWorkflow.includes('SSOT: triwiki') || !teamWorkflow.includes('Analysis Scouts') || !teamWorkflow.includes('sks wiki validate')) throw new Error('selftest failed: team workflow missing scout-first TriWiki context tracking');
|
|
3241
|
+
if (!teamWorkflow.includes('sks team open-tmux')) throw new Error('selftest failed: team workflow missing existing-mission tmux open command');
|
|
3202
3242
|
if (!teamWorkflow.includes(TEAM_GRAPH_ARTIFACT) || !teamWorkflow.includes(TEAM_INBOX_DIR)) throw new Error('selftest failed: team workflow missing runtime graph/inbox guidance');
|
|
3203
3243
|
if (!teamWorkflow.includes('before every stage') || !teamWorkflow.includes('after findings/artifact changes')) throw new Error('selftest failed: team workflow missing per-stage TriWiki policy');
|
|
3204
3244
|
const customTeamPlan = buildTeamPlan(teamId, '병렬 구현 팀 테스트', { agentSessions: 5 });
|
|
@@ -3232,7 +3272,7 @@ async function selftest() {
|
|
|
3232
3272
|
await ensureDir(fakeTmuxDir);
|
|
3233
3273
|
const fakeTmuxLog = path.join(fakeTmuxDir, 'tmux.log');
|
|
3234
3274
|
const fakeTmuxBin = path.join(fakeTmuxDir, 'tmux');
|
|
3235
|
-
await writeTextAtomic(fakeTmuxBin, `#!/usr/bin/env node\nconst fs = require('node:fs');\nconst log = process.env.SKS_FAKE_TMUX_LOG;\nif (log) fs.appendFileSync(log, process.argv.slice(2).join(' ') + '\\n');\nconst cmd = process.argv[2];\nif (cmd === 'has-session') process.exit(0);\nif (cmd === 'kill-session') process.exit(0);\nif (cmd === 'new-session') { console.log('%1'); process.exit(0); }\nif (cmd === 'split-window') { console.log('%2'); process.exit(0); }\nprocess.exit(0);\n`);
|
|
3275
|
+
await writeTextAtomic(fakeTmuxBin, `#!/usr/bin/env node\nconst fs = require('node:fs');\nconst log = process.env.SKS_FAKE_TMUX_LOG;\nif (log) fs.appendFileSync(log, process.argv.slice(2).join(' ') + '\\n');\nconst cmd = process.argv[2];\nif (cmd === 'has-session') process.exit(0);\nif (cmd === 'kill-session') process.exit(0);\nif (cmd === 'kill-pane') process.exit(0);\nif (cmd === 'new-session') { console.log('%1'); process.exit(0); }\nif (cmd === 'split-window') { console.log(process.env.SKS_FAKE_TMUX_SPLIT_ID || '%2'); process.exit(0); }\nif (cmd === 'list-windows') { console.log('@1'); process.exit(0); }\nif (cmd === 'display-message') { console.log(process.env.SKS_FAKE_TMUX_DISPLAY || 'sks-existing-selftest\\t@1\\t%1'); process.exit(0); }\nif (cmd === 'list-panes') { console.log(process.env.SKS_FAKE_TMUX_LIST || ''); process.exit(0); }\nif (cmd === 'set-option' || cmd === 'select-layout' || cmd === 'resize-window' || cmd === 'set-window-option' || cmd === 'set-hook') process.exit(0);\nprocess.exit(0);\n`);
|
|
3236
3276
|
await fsp.chmod(fakeTmuxBin, 0o755);
|
|
3237
3277
|
const previousFakeTmuxLog = process.env.SKS_FAKE_TMUX_LOG;
|
|
3238
3278
|
process.env.SKS_FAKE_TMUX_LOG = fakeTmuxLog;
|
|
@@ -3242,6 +3282,50 @@ async function selftest() {
|
|
|
3242
3282
|
], { recreate: true });
|
|
3243
3283
|
const fakeTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
3244
3284
|
if (!recreatedTmux.ok || !fakeTmuxLogText.includes('kill-session -t sks-existing-selftest') || !fakeTmuxLogText.includes('new-session') || !fakeTmuxLogText.includes('split-window')) throw new Error('selftest failed: tmux recreate did not replace stale existing session with split panes');
|
|
3285
|
+
if (!recreatedTmux.dynamic_resize?.enabled || !fakeTmuxLogText.includes('list-windows -t sks-existing-selftest -F #{window_id}') || !fakeTmuxLogText.includes('set-window-option -t @1 window-size latest') || !fakeTmuxLogText.includes('set-hook -t sks-existing-selftest client-resized') || !fakeTmuxLogText.includes('resize-window -t @1 -A')) throw new Error('selftest failed: tmux dynamic resize hooks were not installed for split panes');
|
|
3286
|
+
if (recreatedTmux.layout !== 'tiled' || Number(recreatedTmux.initial_size?.width || 0) < 120 || Number(recreatedTmux.initial_size?.height || 0) < 36) throw new Error('selftest failed: tmux dynamic resize metadata missing normalized initial size/layout');
|
|
3287
|
+
await ensureDir(path.join(tmp, '.sneakoscope', 'state'));
|
|
3288
|
+
await writeJsonAtomic(path.join(tmp, '.sneakoscope', 'state', 'tmux-sessions.json'), {
|
|
3289
|
+
schema_version: 1,
|
|
3290
|
+
sessions: {
|
|
3291
|
+
'sks-existing-selftest': {
|
|
3292
|
+
session: 'sks-existing-selftest',
|
|
3293
|
+
root: tmp,
|
|
3294
|
+
panes: [{ pane_id: '%1', role: 'codex', title: 'Codex CLI' }]
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
await writeTextAtomic(fakeTmuxLog, '');
|
|
3299
|
+
process.env.SKS_FAKE_TMUX_DISPLAY = 'sks-existing-selftest\t@1\t%1';
|
|
3300
|
+
process.env.SKS_FAKE_TMUX_LIST = '';
|
|
3301
|
+
process.env.SKS_FAKE_TMUX_SPLIT_ID = '%80';
|
|
3302
|
+
const cockpitOpen = await reconcileTmuxTeamCockpit({
|
|
3303
|
+
root: tmp,
|
|
3304
|
+
missionId: teamId,
|
|
3305
|
+
plan: roleTeamPlan,
|
|
3306
|
+
dashboard: { agents: { analysis_scout_1: { status: 'assigned' } } },
|
|
3307
|
+
control: { status: 'running' },
|
|
3308
|
+
tmux: { bin: fakeTmuxBin },
|
|
3309
|
+
env: { ...process.env, TMUX: '/tmp/tmux-selftest/default,1,0' }
|
|
3310
|
+
});
|
|
3311
|
+
const cockpitOpenLog = await safeReadText(fakeTmuxLog);
|
|
3312
|
+
if (!cockpitOpen.ok || cockpitOpen.opened_lane_count !== 2 || !cockpitOpenLog.includes('display-message -p') || !cockpitOpenLog.includes('split-window -t @1') || !cockpitOpenLog.includes('set-option -pt %80 @sks_team_managed 1') || !cockpitOpenLog.includes('select-layout -t @1 tiled')) throw new Error('selftest failed: dynamic Team cockpit did not split managed panes in the current SKS tmux session');
|
|
3313
|
+
await writeTextAtomic(fakeTmuxLog, '');
|
|
3314
|
+
process.env.SKS_FAKE_TMUX_LIST = `%81\tscout: analysis_scout_1\tnode\t1\t${teamId}\tanalysis_scout_1\tscout\n%82\tscout: analysis_scout_2\tnode\t1\t${teamId}\tanalysis_scout_2\tscout\n%83\tuser pane\tzsh\t\t\t\t`;
|
|
3315
|
+
const cockpitClose = await reconcileTmuxTeamCockpit({
|
|
3316
|
+
root: tmp,
|
|
3317
|
+
missionId: teamId,
|
|
3318
|
+
plan: roleTeamPlan,
|
|
3319
|
+
dashboard: { agents: { analysis_scout_1: { status: 'completed' }, analysis_scout_2: { status: 'assigned' } } },
|
|
3320
|
+
control: { status: 'running' },
|
|
3321
|
+
tmux: { bin: fakeTmuxBin },
|
|
3322
|
+
env: { ...process.env, TMUX: '/tmp/tmux-selftest/default,1,0' }
|
|
3323
|
+
});
|
|
3324
|
+
const cockpitCloseLog = await safeReadText(fakeTmuxLog);
|
|
3325
|
+
if (!cockpitClose.ok || cockpitClose.closed_lane_count !== 1 || !cockpitCloseLog.includes('kill-pane -t %81') || cockpitCloseLog.includes('kill-pane -t %83')) throw new Error('selftest failed: dynamic Team cockpit did not close only stale managed panes');
|
|
3326
|
+
delete process.env.SKS_FAKE_TMUX_DISPLAY;
|
|
3327
|
+
delete process.env.SKS_FAKE_TMUX_LIST;
|
|
3328
|
+
delete process.env.SKS_FAKE_TMUX_SPLIT_ID;
|
|
3245
3329
|
await writeTextAtomic(fakeTmuxLog, '');
|
|
3246
3330
|
const madCockpit = await launchMadTmuxUi(['--workspace', 'sks-mad-selftest-ui', '--no-attach'], { root: tmp, tmux: { ok: true, bin: fakeTmuxBin, version: '3.4' }, codex: { bin: process.execPath }, app: { ok: true, guidance: [] }, missionId: 'M-MAD-SELFTEST' });
|
|
3247
3331
|
const madTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
@@ -3333,6 +3417,7 @@ async function selftest() {
|
|
|
3333
3417
|
if (!teamDashboard?.latest_messages?.some((entry) => entry.agent === 'team_consensus')) throw new Error('selftest failed: team live dashboard missing agent event');
|
|
3334
3418
|
const teamLive = await readTeamLive(teamDir);
|
|
3335
3419
|
if (!teamLive.includes('Analysis scouts') || !teamLive.includes('selftest mapped repo slice')) throw new Error('selftest failed: team live transcript missing analysis scout section/event');
|
|
3420
|
+
if (!teamLive.includes('sks team open-tmux')) throw new Error('selftest failed: team live transcript missing existing-mission tmux open command');
|
|
3336
3421
|
if (!teamLive.includes('selftest mapped options')) throw new Error('selftest failed: team live transcript missing event');
|
|
3337
3422
|
if (!teamLive.includes('Context tracking SSOT: TriWiki')) throw new Error('selftest failed: team live transcript missing TriWiki context tracking');
|
|
3338
3423
|
if (!(await readTeamTranscriptTail(teamDir, 1)).join('\n').includes('selftest mapped options')) throw new Error('selftest failed: team transcript tail missing event');
|
|
@@ -3669,6 +3754,6 @@ async function selftest() {
|
|
|
3669
3754
|
const gc = await enforceRetention(tmp, { dryRun: true });
|
|
3670
3755
|
if (!gc.report.exists) throw new Error('selftest failed: storage report');
|
|
3671
3756
|
if (!gc.actions.some((action) => action.action === 'remove_from_chat_img_temp_triwiki')) throw new Error('selftest failed: From-Chat-IMG temporary TriWiki retention action missing');
|
|
3672
|
-
console.log(
|
|
3757
|
+
console.log(`${sksAsciiLogo()}\nselftest passed.`);
|
|
3673
3758
|
console.log(`temp: ${tmp}`);
|
|
3674
3759
|
}
|
|
@@ -30,7 +30,7 @@ import { PIPELINE_PLAN_ARTIFACT, validatePipelinePlan, writePipelinePlan } from
|
|
|
30
30
|
import { GOAL_BRIDGE_ARTIFACT, GOAL_WORKFLOW_ARTIFACT, updateGoalWorkflow, writeGoalWorkflow } from '../core/goal-workflow.mjs';
|
|
31
31
|
import { scanCodeStructure, writeCodeStructureReport } from '../core/code-structure.mjs';
|
|
32
32
|
import { writeMemorySweepReport } from '../core/memory-governor.mjs';
|
|
33
|
-
import { cleanupTmuxTeamView, defaultTmuxSessionName, launchMadTmuxUi, launchTmuxTeamView, sanitizeTmuxSessionName } from '../core/tmux-ui.mjs';
|
|
33
|
+
import { cleanupTmuxTeamView, defaultTmuxSessionName, launchMadTmuxUi, launchTmuxTeamView, reconcileTmuxTeamCockpit, sanitizeTmuxSessionName } from '../core/tmux-ui.mjs';
|
|
34
34
|
import { loadSkillDreamState, recordSkillDreamEvent, runSkillDream, writeSkillForgeReport } from '../core/skill-forge.mjs';
|
|
35
35
|
import { writeMistakeMemoryReport } from '../core/mistake-memory.mjs';
|
|
36
36
|
import { checkDbOperation, checkSqlFile, classifyCommand, classifySql, loadDbSafetyPolicy, safeSupabaseMcpConfig, scanDbSafety } from '../core/db-safety.mjs';
|
|
@@ -516,7 +516,7 @@ async function goalCreate(args) {
|
|
|
516
516
|
if (!prompt) throw new Error('Missing goal task prompt.');
|
|
517
517
|
const { id, dir, mission } = await createMission(root, { mode: 'goal', prompt });
|
|
518
518
|
const workflow = await writeGoalWorkflow(dir, mission, { action: 'create', prompt });
|
|
519
|
-
await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: 'GOAL_READY', questions_allowed: true, implementation_allowed: true, native_goal: workflow.native_goal, stop_gate: 'none' });
|
|
519
|
+
await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: 'GOAL_READY', questions_allowed: true, implementation_allowed: true, native_goal: workflow.native_goal, stop_gate: 'none' }, { replace: true });
|
|
520
520
|
console.log(`Goal mission created: ${id}`);
|
|
521
521
|
console.log(`Artifact: ${path.relative(root, path.join(dir, GOAL_WORKFLOW_ARTIFACT))}`);
|
|
522
522
|
console.log(`Bridge: ${path.relative(root, path.join(dir, GOAL_BRIDGE_ARTIFACT))}`);
|
|
@@ -529,7 +529,7 @@ async function goalControl(action, args) {
|
|
|
529
529
|
if (!id) throw new Error(`Usage: sks goal ${action} <mission-id|latest>`);
|
|
530
530
|
const { dir } = await loadMission(root, id);
|
|
531
531
|
const workflow = await updateGoalWorkflow(dir, action);
|
|
532
|
-
await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: `GOAL_${String(action).toUpperCase()}`, native_goal: workflow.native_goal, questions_allowed: true, implementation_allowed: action !== 'pause' && action !== 'clear', stop_gate: 'none' });
|
|
532
|
+
await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: `GOAL_${String(action).toUpperCase()}`, native_goal: workflow.native_goal, questions_allowed: true, implementation_allowed: action !== 'pause' && action !== 'clear', stop_gate: 'none' }, { replace: true });
|
|
533
533
|
console.log(`Goal ${action}: ${id}`);
|
|
534
534
|
console.log(`Native Codex control: ${workflow.native_goal.slash_command}`);
|
|
535
535
|
}
|
|
@@ -1626,7 +1626,7 @@ export async function gxCommand(sub, args) {
|
|
|
1626
1626
|
}
|
|
1627
1627
|
|
|
1628
1628
|
export async function team(args) {
|
|
1629
|
-
const teamSubcommands = new Set(['log', 'tail', 'watch', 'lane', 'status', 'dashboard', 'event', 'message', 'cleanup-tmux']);
|
|
1629
|
+
const teamSubcommands = new Set(['log', 'tail', 'watch', 'lane', 'status', 'dashboard', 'event', 'message', 'open-tmux', 'attach-tmux', 'cleanup-tmux']);
|
|
1630
1630
|
if (teamSubcommands.has(args[0])) return teamCommand(args[0], args.slice(1));
|
|
1631
1631
|
const jsonOutput = flag(args, '--json');
|
|
1632
1632
|
const openTmux = !jsonOutput && !flag(args, '--no-open-tmux') && !flag(args, '--no-tmux');
|
|
@@ -1635,7 +1635,7 @@ export async function team(args) {
|
|
|
1635
1635
|
const { prompt, agentSessions, roleCounts, roster } = opts;
|
|
1636
1636
|
if (!prompt) {
|
|
1637
1637
|
console.error('Usage: sks team "task" [executor:5 reviewer:6 user:1] [--agents N] [--no-open-tmux] [--json]');
|
|
1638
|
-
console.error(' sks team log|tail|watch|lane|status|message|cleanup-tmux [mission-id|latest]');
|
|
1638
|
+
console.error(' sks team log|tail|watch|lane|status|message|open-tmux|attach-tmux|cleanup-tmux [mission-id|latest]');
|
|
1639
1639
|
console.error(' sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."');
|
|
1640
1640
|
console.error(' sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."');
|
|
1641
1641
|
process.exitCode = 1;
|
|
@@ -1778,7 +1778,7 @@ export function buildTeamPlan(id, prompt, opts = {}) {
|
|
|
1778
1778
|
reasoning: teamReasoningPolicy(prompt, roster),
|
|
1779
1779
|
codex_config_required: {
|
|
1780
1780
|
service_tier: 'fast',
|
|
1781
|
-
features: { multi_agent: true,
|
|
1781
|
+
features: { multi_agent: true, hooks: true, fast_mode: true, fast_mode_ui: true, codex_git_commit: true, computer_use: true, apps: true, plugins: true },
|
|
1782
1782
|
agents: { max_threads: 6, max_depth: 1 },
|
|
1783
1783
|
custom_agents_dir: '.codex/agents'
|
|
1784
1784
|
},
|
|
@@ -1887,6 +1887,7 @@ export function buildTeamPlan(id, prompt, opts = {}) {
|
|
|
1887
1887
|
'sks team status <mission-id>',
|
|
1888
1888
|
'sks team log <mission-id>',
|
|
1889
1889
|
'sks team tail <mission-id>',
|
|
1890
|
+
'sks team open-tmux <mission-id>',
|
|
1890
1891
|
'sks team watch <mission-id>',
|
|
1891
1892
|
'sks team lane <mission-id> --agent <name> --follow',
|
|
1892
1893
|
'sks team event <mission-id> --agent <name> --phase <phase> --message "..."',
|
|
@@ -1967,7 +1968,7 @@ ${plan.roster.validation_team.map((agent) => `- ${agent.id}: ${agent.persona} [r
|
|
|
1967
1968
|
- Keep team-live.md readable for the user inside Codex App.
|
|
1968
1969
|
- Mirror every useful subagent status, debate result, handoff, review finding, and integration decision to team-transcript.jsonl.
|
|
1969
1970
|
- Use \`sks team event ${plan.mission_id} --agent <name> --phase <phase> --message "..."\` when recording a live event from the parent thread.
|
|
1970
|
-
- The user can inspect the flow with \`sks team log ${plan.mission_id}\`, \`sks team tail ${plan.mission_id}\`, \`sks team watch ${plan.mission_id}\`, or \`sks team lane ${plan.mission_id} --agent analysis_scout_1 --follow\`.
|
|
1971
|
+
- The user can inspect the flow with \`sks team open-tmux ${plan.mission_id}\`, \`sks team log ${plan.mission_id}\`, \`sks team tail ${plan.mission_id}\`, \`sks team watch ${plan.mission_id}\`, or \`sks team lane ${plan.mission_id} --agent analysis_scout_1 --follow\`.
|
|
1971
1972
|
|
|
1972
1973
|
## Phases
|
|
1973
1974
|
|
|
@@ -1989,6 +1990,37 @@ async function teamCommand(sub, args) {
|
|
|
1989
1990
|
return;
|
|
1990
1991
|
}
|
|
1991
1992
|
const { dir } = await loadMission(root, id);
|
|
1993
|
+
if (sub === 'open-tmux' || sub === 'attach-tmux') {
|
|
1994
|
+
const plan = await readJson(path.join(dir, 'team-plan.json'), null);
|
|
1995
|
+
if (!plan) {
|
|
1996
|
+
console.error(`Team plan missing for ${id}; cannot open tmux Team view.`);
|
|
1997
|
+
process.exitCode = 2;
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
const tmux = await launchTmuxTeamView({
|
|
2001
|
+
root,
|
|
2002
|
+
missionId: id,
|
|
2003
|
+
plan,
|
|
2004
|
+
promptFile: path.join(dir, 'team-workflow.md'),
|
|
2005
|
+
json: flag(args, '--json'),
|
|
2006
|
+
attach: sub === 'attach-tmux' || !flag(args, '--no-attach'),
|
|
2007
|
+
args
|
|
2008
|
+
});
|
|
2009
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(tmux, null, 2));
|
|
2010
|
+
if (!tmux.ready) {
|
|
2011
|
+
const reasons = [tmux.opened?.stderr, ...(tmux.blockers || [])].filter(Boolean);
|
|
2012
|
+
console.error(`tmux Team view blocked for ${id}: ${reasons.join('; ') || 'tmux creation failed'}`);
|
|
2013
|
+
if (tmux.attach_command) console.error(`Attach after repair: ${tmux.attach_command}`);
|
|
2014
|
+
process.exitCode = 2;
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
console.log(`tmux: opened ${tmux.opened_lane_count || tmux.lanes?.length || 0} Team lane(s) in ${tmux.session}`);
|
|
2018
|
+
if (tmux.split_ui?.mode) console.log(`tmux UI: ${tmux.split_ui.mode} (${tmux.split_ui.layout})`);
|
|
2019
|
+
if (tmux.split_ui?.current_session) console.log('tmux cockpit: reconciled inside the current SKS tmux window');
|
|
2020
|
+
console.log(`Attach: ${tmux.attach_command}`);
|
|
2021
|
+
console.log(`Watch: sks team watch ${id}`);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
1992
2024
|
if (sub === 'event') {
|
|
1993
2025
|
const message = readFlagValue(args, '--message', '');
|
|
1994
2026
|
if (!message) {
|
|
@@ -1997,6 +2029,7 @@ async function teamCommand(sub, args) {
|
|
|
1997
2029
|
return;
|
|
1998
2030
|
}
|
|
1999
2031
|
const phase = readFlagValue(args, '--phase', 'general');
|
|
2032
|
+
const plan = await readJson(path.join(dir, 'team-plan.json'), null).catch(() => null);
|
|
2000
2033
|
const record = await appendTeamEvent(dir, {
|
|
2001
2034
|
agent: readFlagValue(args, '--agent', 'parent_orchestrator'),
|
|
2002
2035
|
phase,
|
|
@@ -2004,16 +2037,29 @@ async function teamCommand(sub, args) {
|
|
|
2004
2037
|
artifact: readFlagValue(args, '--artifact', ''),
|
|
2005
2038
|
message
|
|
2006
2039
|
});
|
|
2040
|
+
const cockpit = plan
|
|
2041
|
+
? await reconcileTmuxTeamCockpit({
|
|
2042
|
+
root,
|
|
2043
|
+
missionId: id,
|
|
2044
|
+
plan,
|
|
2045
|
+
promptFile: path.join(dir, 'team-workflow.md'),
|
|
2046
|
+
close: /^session_cleanup$|^team_cleanup$|^cleanup$/i.test(String(phase || '')),
|
|
2047
|
+
plannedFallback: false
|
|
2048
|
+
}).catch((err) => ({ ok: false, skipped: true, reason: err.message || 'tmux cockpit reconcile failed' }))
|
|
2049
|
+
: null;
|
|
2007
2050
|
const tmuxCleanup = /^session_cleanup$|^team_cleanup$|^cleanup$/i.test(String(phase || ''))
|
|
2008
2051
|
? await requestTeamSessionCleanup(dir, {
|
|
2009
2052
|
missionId: id,
|
|
2010
2053
|
agent: readFlagValue(args, '--agent', 'parent_orchestrator'),
|
|
2011
2054
|
reason: message,
|
|
2012
|
-
finalMessage: 'Team cleanup event received.
|
|
2055
|
+
finalMessage: 'Team cleanup event received. Managed tmux Team panes may close; follow loops may stop.'
|
|
2013
2056
|
}).then(() => cleanupTmuxTeamView({ root, missionId: id, closeSession: flag(args, '--close-session') || flag(args, '--close') })).catch((err) => ({ ok: false, reason: err.message || 'tmux cleanup failed' }))
|
|
2014
2057
|
: null;
|
|
2015
2058
|
if (flag(args, '--json')) return console.log(JSON.stringify(record, null, 2));
|
|
2016
2059
|
console.log(`${record.ts} [${record.phase}] ${record.agent}: ${record.message}`);
|
|
2060
|
+
if (cockpit?.ok && (cockpit.opened_lane_count || cockpit.closed_lane_count)) {
|
|
2061
|
+
console.log(`tmux cockpit: +${cockpit.opened_lane_count || 0} -${cockpit.closed_lane_count || 0} managed pane(s) in ${cockpit.session}`);
|
|
2062
|
+
}
|
|
2017
2063
|
if (tmuxCleanup) {
|
|
2018
2064
|
if (tmuxCleanup.ok) console.log(`tmux cleanup: marked complete (${tmuxCleanup.reason || 'record updated'})`);
|
|
2019
2065
|
else console.log(`tmux cleanup: skipped (${tmuxCleanup.reason || 'not available'})`);
|
|
@@ -2045,7 +2091,7 @@ async function teamCommand(sub, args) {
|
|
|
2045
2091
|
missionId: id,
|
|
2046
2092
|
agent: readFlagValue(args, '--agent', 'parent_orchestrator'),
|
|
2047
2093
|
reason: readFlagValue(args, '--reason', 'Team session ended; clean up live follow panes.'),
|
|
2048
|
-
finalMessage: 'Team session ended. Lane/watch follow loops will stop after showing this cleanup summary; tmux panes
|
|
2094
|
+
finalMessage: 'Team session ended. Lane/watch follow loops will stop after showing this cleanup summary; managed tmux Team panes may close.'
|
|
2049
2095
|
});
|
|
2050
2096
|
await appendTeamEvent(dir, {
|
|
2051
2097
|
agent: readFlagValue(args, '--agent', 'parent_orchestrator'),
|