sneakoscope 0.6.76 → 0.6.77
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 +4 -0
- package/package.json +1 -1
- package/src/cli/main.mjs +5 -2
- package/src/cli/maintenance-commands.mjs +38 -7
- package/src/core/cmux-ui.mjs +263 -10
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +1 -1
- package/src/core/team-live.mjs +37 -0
package/README.md
CHANGED
|
@@ -201,6 +201,10 @@ sks team log latest
|
|
|
201
201
|
|
|
202
202
|
Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and opens a named cmux Team workspace with split live lanes when cmux is available. `sks team dashboard` renders the cockpit panes for mission overview, agent lanes, task DAG, QA/dogfood, artifacts/evidence, and performance.
|
|
203
203
|
|
|
204
|
+
The cmux Team workspace is a live orchestration screen: the first pane follows `sks team watch <mission-id> --follow` as the mission overview, and neighboring split panes follow individual `sks team lane <mission-id> --agent <name> --follow` views. SKS colors and labels lanes by role, so scouts, planning/debate voices, executors, reviewers, and safety lanes are visually distinct while the same evidence is mirrored into `team-transcript.jsonl`, `team-live.md`, and `team-dashboard.json`.
|
|
205
|
+
|
|
206
|
+
When the Team route reaches `session_cleanup`, SKS collapses the cmux workspace back to the overview pane and marks the workspace complete. You can also run `sks team cleanup-cmux <mission-id|latest>` manually, or `sks team cleanup-cmux latest --close-workspace` when you want the whole Team workspace closed.
|
|
207
|
+
|
|
204
208
|
### QA, Goal, Research, DB, Wiki, GX
|
|
205
209
|
|
|
206
210
|
```sh
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.77",
|
|
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",
|
package/src/cli/main.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import { buildPromptContext } from '../core/prompt-context-builder.mjs';
|
|
|
40
40
|
import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
|
|
41
41
|
import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
42
42
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
43
|
-
import { CMUX_BREW_COMMAND, CMUX_BREW_UPGRADE_COMMAND, buildCmuxLaunchPlan, buildCmuxNewWorkspaceArgs, cmuxSurfaceRefFromText, cmuxWorkspaceRef, cmuxWorkspaceRefFromText, cmuxReadiness, cmuxStatusKind, defaultCmuxWorkspaceName, ensureCmuxInstalled, formatCmuxBanner, launchCmuxTeamView, launchCmuxUi, matchingCmuxWorkspaces, parseCmuxWorkspaceList, platformCmuxInstallHint, readCmuxWorkspaceRecord, runCmuxStatus, sanitizeCmuxWorkspaceName, writeCmuxWorkspaceRecord } from '../core/cmux-ui.mjs';
|
|
43
|
+
import { CMUX_BREW_COMMAND, CMUX_BREW_UPGRADE_COMMAND, buildCmuxLaunchPlan, buildCmuxNewWorkspaceArgs, cmuxSurfaceRefFromText, cmuxWorkspaceRef, cmuxWorkspaceRefFromText, cmuxReadiness, cmuxStatusKind, defaultCmuxWorkspaceName, ensureCmuxInstalled, formatCmuxBanner, launchCmuxTeamView, launchCmuxUi, matchingCmuxWorkspaces, parseCmuxWorkspaceList, platformCmuxInstallHint, readCmuxWorkspaceRecord, runCmuxStatus, sanitizeCmuxWorkspaceName, teamLaneStyle, writeCmuxWorkspaceRecord } from '../core/cmux-ui.mjs';
|
|
44
44
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
45
45
|
import { buildTeamPlan, codeStructureCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, qaLoopCommand, researchCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
46
46
|
|
|
@@ -2700,7 +2700,10 @@ async function selftest() {
|
|
|
2700
2700
|
if (!roleTeamPlan.roster.debate_team.some((agent) => /inconvenience/.test(agent.persona))) throw new Error('selftest failed: user friction persona missing from debate team');
|
|
2701
2701
|
const cmuxTeam = await launchCmuxTeamView({ root: tmp, missionId: teamId, plan: roleTeamPlan, json: true });
|
|
2702
2702
|
if (!cmuxTeam.agents?.length || !cmuxTeam.agents.some((entry) => entry.agent === 'analysis_scout_1') || !cmuxTeam.agents.every((entry) => String(entry.command || '').includes('team lane') && String(entry.command || '').includes('--agent'))) throw new Error('selftest failed: Team cmux view did not expose agent live lanes');
|
|
2703
|
-
|
|
2703
|
+
if (!cmuxTeam.overview?.command?.includes('team watch') || !cmuxTeam.lanes?.some((entry) => entry.role === 'overview') || !cmuxTeam.lanes?.some((entry) => entry.agent === 'analysis_scout_1')) throw new Error('selftest failed: Team cmux view did not expose orchestration overview plus agent lanes');
|
|
2704
|
+
if (teamLaneStyle('analysis_scout_1').role !== 'scout' || teamLaneStyle('executor_1').role !== 'execution' || teamLaneStyle('reviewer_1').role !== 'review') throw new Error('selftest failed: Team cmux role palette did not classify lane roles');
|
|
2705
|
+
if (cmuxTeam.cleanup_policy !== 'collapse-agent-lanes-to-overview' || !cmuxTeam.lanes.every((entry) => entry.style?.color && entry.title)) throw new Error('selftest failed: Team cmux view did not expose color/title metadata and cleanup policy');
|
|
2706
|
+
const cmuxTeamWorkspaceArgs = buildCmuxNewWorkspaceArgs({ root: tmp, workspace: `sks-team-${teamId}` }, cmuxTeam.overview.command);
|
|
2704
2707
|
if (!cmuxTeamWorkspaceArgs.includes('--name') || !cmuxTeamWorkspaceArgs.includes(`sks-team-${teamId}`)) throw new Error('selftest failed: Team cmux workspace is not named for visibility');
|
|
2705
2708
|
if (routeReasoning(routePrompt('$Research frontier idea'), '$Research frontier idea').effort !== 'xhigh') throw new Error('selftest failed: research reasoning not xhigh');
|
|
2706
2709
|
if (routeReasoning(routePrompt('$From-Chat-IMG 채팅 이미지 작업'), '$From-Chat-IMG 채팅 이미지 작업').effort !== 'xhigh') throw new Error('selftest failed: From-Chat-IMG reasoning not xhigh');
|
|
@@ -17,7 +17,7 @@ import { contextCapsule } from '../core/triwiki-attention.mjs';
|
|
|
17
17
|
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
|
|
18
18
|
import { ALLOWED_REASONING_EFFORTS, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, ROUTES, hasFromChatImgSignal, routePrompt, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
|
|
19
19
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
|
|
20
|
-
import { appendTeamEvent, formatRoleCounts, initTeamLive, normalizeTeamSpec, parseTeamSpecArgs, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane } from '../core/team-live.mjs';
|
|
20
|
+
import { appendTeamEvent, formatRoleCounts, initTeamLive, normalizeTeamSpec, parseTeamSpecArgs, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamWatch } from '../core/team-live.mjs';
|
|
21
21
|
import { ARTIFACT_FILES, writeValidationReport } from '../core/artifact-schemas.mjs';
|
|
22
22
|
import { writeEffortDecision } from '../core/effort-orchestrator.mjs';
|
|
23
23
|
import { createWorkOrderLedger, writeWorkOrderLedger } from '../core/work-order-ledger.mjs';
|
|
@@ -27,7 +27,7 @@ import { runPerfBench } from '../core/perf-bench.mjs';
|
|
|
27
27
|
import { GOAL_BRIDGE_ARTIFACT, GOAL_WORKFLOW_ARTIFACT, updateGoalWorkflow, writeGoalWorkflow } from '../core/goal-workflow.mjs';
|
|
28
28
|
import { scanCodeStructure, writeCodeStructureReport } from '../core/code-structure.mjs';
|
|
29
29
|
import { writeMemorySweepReport } from '../core/memory-governor.mjs';
|
|
30
|
-
import { launchCmuxTeamView } from '../core/cmux-ui.mjs';
|
|
30
|
+
import { cleanupCmuxTeamView, launchCmuxTeamView } from '../core/cmux-ui.mjs';
|
|
31
31
|
import { writeSkillForgeReport } from '../core/skill-forge.mjs';
|
|
32
32
|
import { writeMistakeMemoryReport } from '../core/mistake-memory.mjs';
|
|
33
33
|
import { scanDbSafety } from '../core/db-safety.mjs';
|
|
@@ -1171,13 +1171,13 @@ export async function gxCommand(sub, args) {
|
|
|
1171
1171
|
}
|
|
1172
1172
|
|
|
1173
1173
|
export async function team(args) {
|
|
1174
|
-
const teamSubcommands = new Set(['log', 'tail', 'watch', 'lane', 'status', 'dashboard', 'event']);
|
|
1174
|
+
const teamSubcommands = new Set(['log', 'tail', 'watch', 'lane', 'status', 'dashboard', 'event', 'cleanup-cmux']);
|
|
1175
1175
|
if (teamSubcommands.has(args[0])) return teamCommand(args[0], args.slice(1));
|
|
1176
1176
|
const opts = parseTeamCreateArgs(args);
|
|
1177
1177
|
const { prompt, agentSessions, roleCounts, roster } = opts;
|
|
1178
1178
|
if (!prompt) {
|
|
1179
1179
|
console.error('Usage: sks team "task" [executor:5 reviewer:2 user:1] [--agents N] [--json]');
|
|
1180
|
-
console.error(' sks team log|tail|watch|lane|status [mission-id|latest]');
|
|
1180
|
+
console.error(' sks team log|tail|watch|lane|status|cleanup-cmux [mission-id|latest]');
|
|
1181
1181
|
console.error(' sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."');
|
|
1182
1182
|
process.exitCode = 1;
|
|
1183
1183
|
return;
|
|
@@ -1507,15 +1507,35 @@ async function teamCommand(sub, args) {
|
|
|
1507
1507
|
process.exitCode = 1;
|
|
1508
1508
|
return;
|
|
1509
1509
|
}
|
|
1510
|
+
const phase = readFlagValue(args, '--phase', 'general');
|
|
1510
1511
|
const record = await appendTeamEvent(dir, {
|
|
1511
1512
|
agent: readFlagValue(args, '--agent', 'parent_orchestrator'),
|
|
1512
|
-
phase
|
|
1513
|
+
phase,
|
|
1513
1514
|
type: readFlagValue(args, '--type', 'status'),
|
|
1514
1515
|
artifact: readFlagValue(args, '--artifact', ''),
|
|
1515
1516
|
message
|
|
1516
1517
|
});
|
|
1518
|
+
const cmuxCleanup = /^session_cleanup$|^team_cleanup$|^cleanup$/i.test(String(phase || ''))
|
|
1519
|
+
? await cleanupCmuxTeamView({ root, missionId: id, closeWorkspace: flag(args, '--close-workspace') }).catch((err) => ({ ok: false, reason: err.message || 'cmux cleanup failed' }))
|
|
1520
|
+
: null;
|
|
1517
1521
|
if (flag(args, '--json')) return console.log(JSON.stringify(record, null, 2));
|
|
1518
1522
|
console.log(`${record.ts} [${record.phase}] ${record.agent}: ${record.message}`);
|
|
1523
|
+
if (cmuxCleanup) {
|
|
1524
|
+
if (cmuxCleanup.ok) console.log(`cmux cleanup: collapsed ${cmuxCleanup.closed_surfaces || 0} agent pane(s), kept overview ${cmuxCleanup.kept_surface || cmuxCleanup.workspace_ref}`);
|
|
1525
|
+
else console.log(`cmux cleanup: skipped (${cmuxCleanup.reason || 'not available'})`);
|
|
1526
|
+
}
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (sub === 'cleanup-cmux') {
|
|
1530
|
+
const cleanup = await cleanupCmuxTeamView({ root, missionId: id, closeWorkspace: flag(args, '--close-workspace') || flag(args, '--close') });
|
|
1531
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(cleanup, null, 2));
|
|
1532
|
+
if (!cleanup.ok) {
|
|
1533
|
+
console.error(`cmux cleanup skipped: ${cleanup.reason || 'not available'}`);
|
|
1534
|
+
process.exitCode = cleanup.skipped ? 0 : 2;
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
if (cleanup.close_workspace) console.log(`cmux cleanup: closed Team workspace ${cleanup.workspace_ref}`);
|
|
1538
|
+
else console.log(`cmux cleanup: collapsed ${cleanup.closed_surfaces}/${cleanup.requested_close_surfaces} agent pane(s), kept overview ${cleanup.kept_surface || cleanup.workspace_ref}`);
|
|
1519
1539
|
return;
|
|
1520
1540
|
}
|
|
1521
1541
|
if (sub === 'status') {
|
|
@@ -1573,15 +1593,26 @@ async function teamCommand(sub, args) {
|
|
|
1573
1593
|
if (sub === 'tail' || sub === 'watch') {
|
|
1574
1594
|
const lines = readFlagValue(args, '--lines', '20');
|
|
1575
1595
|
const printTail = async () => {
|
|
1596
|
+
if (sub === 'watch' && !flag(args, '--raw')) {
|
|
1597
|
+
if (flag(args, '--follow') && process.stdout.isTTY) console.clear();
|
|
1598
|
+
console.log(await renderTeamWatch(dir, { missionId: id, lines: Number(lines) }));
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1576
1601
|
for (const line of await readTeamTranscriptTail(dir, Number(lines))) console.log(line);
|
|
1577
1602
|
};
|
|
1578
1603
|
await printTail();
|
|
1579
1604
|
if (sub === 'watch' && flag(args, '--follow')) {
|
|
1580
|
-
let last = (
|
|
1605
|
+
let last = flag(args, '--raw')
|
|
1606
|
+
? (await readTeamTranscriptTail(dir, Number(lines))).join('\n')
|
|
1607
|
+
: await renderTeamWatch(dir, { missionId: id, lines: Number(lines) });
|
|
1581
1608
|
for (;;) {
|
|
1582
1609
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1583
|
-
const next = (
|
|
1610
|
+
const next = flag(args, '--raw')
|
|
1611
|
+
? (await readTeamTranscriptTail(dir, Number(lines))).join('\n')
|
|
1612
|
+
: await renderTeamWatch(dir, { missionId: id, lines: Number(lines) });
|
|
1584
1613
|
if (next !== last) {
|
|
1614
|
+
if (process.stdout.isTTY) console.clear();
|
|
1615
|
+
else console.log('\n--- team watch update ---\n');
|
|
1585
1616
|
console.log(next);
|
|
1586
1617
|
last = next;
|
|
1587
1618
|
}
|
package/src/core/cmux-ui.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
4
4
|
import { exists, nowIso, packageRoot, readJson, runProcess, sha256, sksRoot, which, writeJsonAtomic } from './fsx.mjs';
|
|
5
5
|
import { getCodexInfo } from './codex-adapter.mjs';
|
|
6
6
|
import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
|
|
@@ -90,6 +90,10 @@ export function cmuxWorkspaceStatePath(plan = {}) {
|
|
|
90
90
|
return path.join(path.resolve(plan.root || process.cwd()), '.sneakoscope', 'state', 'cmux-workspaces.json');
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
export function cmuxTeamStatePath(root = process.cwd()) {
|
|
94
|
+
return path.join(path.resolve(root || process.cwd()), '.sneakoscope', 'state', 'cmux-team-workspaces.json');
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
export function cmuxWorkspaceStateKey(plan = {}) {
|
|
94
98
|
const root = path.resolve(plan.root || process.cwd());
|
|
95
99
|
const workspace = sanitizeCmuxWorkspaceName(plan.workspace || defaultCmuxWorkspaceName(root));
|
|
@@ -189,6 +193,23 @@ export function cmuxBinaryCandidates() {
|
|
|
189
193
|
return Array.from(new Set(candidates));
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
function cmuxAppExecutableCandidates() {
|
|
197
|
+
if (process.platform !== 'darwin') return [];
|
|
198
|
+
const envApps = String(process.env.SKS_CMUX_APP_PATHS || '')
|
|
199
|
+
.split(path.delimiter)
|
|
200
|
+
.map((entry) => entry.trim())
|
|
201
|
+
.filter(Boolean);
|
|
202
|
+
const appBundles = [
|
|
203
|
+
...envApps,
|
|
204
|
+
'/Applications/cmux.app',
|
|
205
|
+
'/Applications/Cmux.app',
|
|
206
|
+
'/Applications/CMUX.app',
|
|
207
|
+
path.join(process.env.HOME || '', 'Applications', 'cmux.app'),
|
|
208
|
+
'/opt/homebrew/Caskroom/cmux/latest/cmux.app'
|
|
209
|
+
].filter(Boolean);
|
|
210
|
+
return Array.from(new Set(appBundles.map((app) => path.join(app, 'Contents', 'MacOS', 'cmux'))));
|
|
211
|
+
}
|
|
212
|
+
|
|
192
213
|
export async function cmuxAvailable() {
|
|
193
214
|
const bin = await findCmuxBinary();
|
|
194
215
|
if (!bin) return { ok: false, bin: null, version: null, executable_ok: false, error: 'cmux CLI not found' };
|
|
@@ -267,14 +288,59 @@ export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
|
267
288
|
].join('; ');
|
|
268
289
|
}
|
|
269
290
|
|
|
291
|
+
function echoLinesCommand(lines = []) {
|
|
292
|
+
return lines.map((line) => String(line) ? `echo ${shellEscape(line)}` : 'echo').join('; ');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export const CMUX_TEAM_LANE_STYLES = Object.freeze({
|
|
296
|
+
overview: Object.freeze({ role: 'overview', label: 'overview', color_name: 'Charcoal', color: '#3E4B5E', icon: 'layout-dashboard' }),
|
|
297
|
+
scout: Object.freeze({ role: 'scout', label: 'scout', color_name: 'Aqua', color: '#0E6B8C', icon: 'search' }),
|
|
298
|
+
planning: Object.freeze({ role: 'planning', label: 'plan', color_name: 'Amber', color: '#7D6608', icon: 'messages-square' }),
|
|
299
|
+
execution: Object.freeze({ role: 'execution', label: 'exec', color_name: 'Green', color: '#196F3D', icon: 'hammer' }),
|
|
300
|
+
review: Object.freeze({ role: 'review', label: 'review', color_name: 'Crimson', color: '#922B21', icon: 'shield-check' }),
|
|
301
|
+
safety: Object.freeze({ role: 'safety', label: 'safety', color_name: 'Magenta', color: '#AD1457', icon: 'database' })
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
export function teamLaneStyle(agentId = '') {
|
|
305
|
+
const id = String(agentId || '').toLowerCase();
|
|
306
|
+
if (!id || id === 'mission_overview' || id === 'overview') return CMUX_TEAM_LANE_STYLES.overview;
|
|
307
|
+
if (/analysis|scout/.test(id)) return CMUX_TEAM_LANE_STYLES.scout;
|
|
308
|
+
if (/debate|consensus|planner|user/.test(id)) return CMUX_TEAM_LANE_STYLES.planning;
|
|
309
|
+
if (/db|safety/.test(id)) return CMUX_TEAM_LANE_STYLES.safety;
|
|
310
|
+
if (/review|qa|validation/.test(id)) return CMUX_TEAM_LANE_STYLES.review;
|
|
311
|
+
if (/executor|implementation|worker|developer/.test(id)) return CMUX_TEAM_LANE_STYLES.execution;
|
|
312
|
+
return CMUX_TEAM_LANE_STYLES.planning;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function teamLaneTitle(agentId = '') {
|
|
316
|
+
const style = teamLaneStyle(agentId);
|
|
317
|
+
return `${style.label}: ${String(agentId || 'mission_overview')}`.slice(0, 80);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function cmuxStatusKey(agentId = '') {
|
|
321
|
+
return sanitizeCmuxWorkspaceName(`sks-${String(agentId || 'overview').toLowerCase()}`).slice(0, 40);
|
|
322
|
+
}
|
|
323
|
+
|
|
270
324
|
export function teamAgentCommand(root, missionId, agentId, phase) {
|
|
325
|
+
const style = teamLaneStyle(agentId);
|
|
271
326
|
return [
|
|
272
|
-
|
|
327
|
+
'clear',
|
|
328
|
+
echoLinesCommand([...SKS_CMUX_LOGO.split('\n'), '', `Team mission: ${missionId}`, `Agent: ${agentId}`, `Lane: ${style.label} (${style.color_name} ${style.color})`, `Phase: ${phase}`, '']),
|
|
273
329
|
`cd ${shellEscape(root)}`,
|
|
274
330
|
`node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} team lane ${shellEscape(missionId)} --agent ${shellEscape(agentId)} --phase ${shellEscape(phase)} --follow --lines 12`
|
|
275
331
|
].join('; ');
|
|
276
332
|
}
|
|
277
333
|
|
|
334
|
+
export function teamOverviewCommand(root, missionId) {
|
|
335
|
+
const style = teamLaneStyle('mission_overview');
|
|
336
|
+
return [
|
|
337
|
+
'clear',
|
|
338
|
+
echoLinesCommand([...SKS_CMUX_LOGO.split('\n'), '', `Team mission: ${missionId}`, 'View: live orchestration overview', `Lane: ${style.label} (${style.color_name} ${style.color})`, '']),
|
|
339
|
+
`cd ${shellEscape(root)}`,
|
|
340
|
+
`node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} team watch ${shellEscape(missionId)} --follow --lines 18`
|
|
341
|
+
].join('; ');
|
|
342
|
+
}
|
|
343
|
+
|
|
278
344
|
export async function buildCmuxLaunchPlan(opts = {}) {
|
|
279
345
|
const root = path.resolve(opts.root || await sksRoot());
|
|
280
346
|
const workspace = sanitizeCmuxWorkspaceName(opts.workspace || opts.session || defaultCmuxWorkspaceName(root));
|
|
@@ -491,6 +557,14 @@ async function ensureCmuxDaemonReady(cmux = {}) {
|
|
|
491
557
|
last = await cmuxSocketProbe(cmux.bin);
|
|
492
558
|
if (last.ok) return { ...cmux, ok: true, error: null };
|
|
493
559
|
}
|
|
560
|
+
if (process.env.SKS_CMUX_SOCKET_ALLOW_ALL !== '0') {
|
|
561
|
+
await restartCmuxApp({ socketMode: 'allowAll' });
|
|
562
|
+
for (let i = 0; i < 8; i++) {
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, 750));
|
|
564
|
+
last = await cmuxSocketProbe(cmux.bin);
|
|
565
|
+
if (last.ok) return { ...cmux, ok: true, error: null, socket_mode: 'allowAll' };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
494
568
|
}
|
|
495
569
|
return { ok: false, error: last.error || 'cmux socket did not become ready' };
|
|
496
570
|
}
|
|
@@ -505,7 +579,7 @@ function isRecoverableCmuxSocketError(error) {
|
|
|
505
579
|
return /socket|broken pipe|receive timeout|connection refused/i.test(String(error || ''));
|
|
506
580
|
}
|
|
507
581
|
|
|
508
|
-
async function restartCmuxApp() {
|
|
582
|
+
async function restartCmuxApp(opts = {}) {
|
|
509
583
|
if (process.platform !== 'darwin') return { ok: false, reason: 'not_macos' };
|
|
510
584
|
const quit = await runProcess('osascript', ['-e', 'tell application "cmux" to quit'], { timeoutMs: 8000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
511
585
|
if (quit.code !== 0) {
|
|
@@ -513,6 +587,21 @@ async function restartCmuxApp() {
|
|
|
513
587
|
}
|
|
514
588
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
515
589
|
await removeStaleCmuxSocket().catch(() => null);
|
|
590
|
+
if (opts.socketMode) return openCmuxAppWithSocketMode(opts.socketMode);
|
|
591
|
+
return openCmuxApp();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async function openCmuxAppWithSocketMode(socketMode) {
|
|
595
|
+
for (const exe of cmuxAppExecutableCandidates()) {
|
|
596
|
+
if (!await exists(exe)) continue;
|
|
597
|
+
const child = spawn(exe, [], {
|
|
598
|
+
detached: true,
|
|
599
|
+
stdio: 'ignore',
|
|
600
|
+
env: { ...process.env, CMUX_SOCKET_MODE: socketMode }
|
|
601
|
+
});
|
|
602
|
+
child.unref();
|
|
603
|
+
return { ok: true, mode: socketMode, executable: exe };
|
|
604
|
+
}
|
|
516
605
|
return openCmuxApp();
|
|
517
606
|
}
|
|
518
607
|
|
|
@@ -524,7 +613,7 @@ async function removeStaleCmuxSocket() {
|
|
|
524
613
|
}
|
|
525
614
|
|
|
526
615
|
export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFile = null, json = false } = {}) {
|
|
527
|
-
const launch = await buildCmuxLaunchPlan({ root, workspace: `sks-team-${missionId}
|
|
616
|
+
const launch = await buildCmuxLaunchPlan({ root, workspace: `sks-team-${missionId}`, wakeCmux: true });
|
|
528
617
|
const agents = [
|
|
529
618
|
...(plan.roster?.analysis_team || []),
|
|
530
619
|
...(plan.roster?.debate_team || []),
|
|
@@ -541,18 +630,26 @@ export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFil
|
|
|
541
630
|
}
|
|
542
631
|
const commands = uniqueAgents.slice(0, Math.max(1, plan.agent_session_count || 3)).map((agentId, index) => ({
|
|
543
632
|
agent: agentId,
|
|
544
|
-
command: teamAgentCommand(launch.root, missionId, agentId, index === 0 ? 'analysis' : 'team', promptFile)
|
|
633
|
+
command: teamAgentCommand(launch.root, missionId, agentId, index === 0 ? 'analysis' : 'team', promptFile),
|
|
634
|
+
style: teamLaneStyle(agentId),
|
|
635
|
+
title: teamLaneTitle(agentId)
|
|
545
636
|
}));
|
|
546
|
-
const
|
|
637
|
+
const overview = { agent: 'mission_overview', role: 'overview', command: teamOverviewCommand(launch.root, missionId), style: teamLaneStyle('mission_overview'), title: teamLaneTitle('mission_overview') };
|
|
638
|
+
const lanes = [overview, ...commands.map((entry) => ({ ...entry, role: entry.style.role }))];
|
|
639
|
+
const result = { ready: launch.ready, cmux: launch.cmux, workspace: launch.workspace, overview, agents: commands, lanes, cleanup_policy: 'collapse-agent-lanes-to-overview', blockers: launch.blockers };
|
|
547
640
|
if (json || !launch.ready) return result;
|
|
548
|
-
const first =
|
|
641
|
+
const first = overview.command;
|
|
549
642
|
const created = spawnSync(launch.cmux.bin, buildCmuxNewWorkspaceArgs(launch, first), { encoding: 'utf8', stdio: 'pipe' });
|
|
550
643
|
result.created = created.status === 0;
|
|
551
644
|
result.stdout = created.stdout || '';
|
|
552
645
|
result.stderr = created.stderr || '';
|
|
553
|
-
const
|
|
646
|
+
const createdText = `${created.stdout || ''}\n${created.stderr || ''}`;
|
|
647
|
+
const workspaceRef = cmuxWorkspaceRefFromText(createdText);
|
|
648
|
+
let overviewSurfaceRef = cmuxSurfaceRefFromText(createdText);
|
|
554
649
|
if (workspaceRef) {
|
|
650
|
+
overviewSurfaceRef ||= firstCmuxSurfaceRef(launch.cmux.bin, workspaceRef);
|
|
555
651
|
result.workspace_ref = workspaceRef;
|
|
652
|
+
if (overviewSurfaceRef) result.overview.surface_ref = overviewSurfaceRef;
|
|
556
653
|
await writeCmuxWorkspaceRecord(launch, { ref: workspaceRef, name: launch.workspace, description: cmuxWorkspaceDescription(launch), cwd: launch.root }).catch(() => null);
|
|
557
654
|
const selected = await runProcess(launch.cmux.bin, ['select-workspace', '--workspace', workspaceRef], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
558
655
|
result.selected = selected.code === 0;
|
|
@@ -564,7 +661,7 @@ export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFil
|
|
|
564
661
|
if (!workspaceRef) result.blockers = [...(result.blockers || []), 'cmux new-workspace did not return a workspace ref'];
|
|
565
662
|
return result;
|
|
566
663
|
}
|
|
567
|
-
for (const entry of commands
|
|
664
|
+
for (const entry of commands) {
|
|
568
665
|
const split = spawnSync(launch.cmux.bin, ['new-split', 'right', '--workspace', workspaceRef], { encoding: 'utf8', stdio: 'pipe' });
|
|
569
666
|
const splitText = `${split.stdout || ''}\n${split.stderr || ''}`;
|
|
570
667
|
const surfaceRef = cmuxSurfaceRefFromText(splitText);
|
|
@@ -574,6 +671,8 @@ export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFil
|
|
|
574
671
|
ok: split.status === 0 && Boolean(surfaceRef),
|
|
575
672
|
pane_ref: paneRef,
|
|
576
673
|
surface_ref: surfaceRef,
|
|
674
|
+
style: entry.style,
|
|
675
|
+
title: entry.title,
|
|
577
676
|
stdout: split.stdout || '',
|
|
578
677
|
stderr: split.stderr || ''
|
|
579
678
|
};
|
|
@@ -585,10 +684,35 @@ export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFil
|
|
|
585
684
|
}
|
|
586
685
|
result.splits.push(splitResult);
|
|
587
686
|
}
|
|
687
|
+
const customizationLanes = [
|
|
688
|
+
{ ...overview, surface_ref: overviewSurfaceRef },
|
|
689
|
+
...result.splits.map((entry) => ({ agent: entry.agent, surface_ref: entry.surface_ref, pane_ref: entry.pane_ref, style: entry.style, title: entry.title }))
|
|
690
|
+
];
|
|
691
|
+
result.customization = await applyCmuxTeamCustomization(launch.cmux.bin, workspaceRef, customizationLanes);
|
|
692
|
+
await writeCmuxTeamRecord(launch.root, {
|
|
693
|
+
mission_id: missionId,
|
|
694
|
+
workspace: launch.workspace,
|
|
695
|
+
workspace_ref: workspaceRef,
|
|
696
|
+
overview_surface_ref: overviewSurfaceRef || null,
|
|
697
|
+
cleanup_policy: result.cleanup_policy,
|
|
698
|
+
lanes: customizationLanes.map((entry) => ({
|
|
699
|
+
agent: entry.agent,
|
|
700
|
+
role: entry.style?.role || teamLaneStyle(entry.agent).role,
|
|
701
|
+
style: entry.style || teamLaneStyle(entry.agent),
|
|
702
|
+
title: entry.title || teamLaneTitle(entry.agent),
|
|
703
|
+
surface_ref: entry.surface_ref || null,
|
|
704
|
+
pane_ref: entry.pane_ref || null
|
|
705
|
+
}))
|
|
706
|
+
}).catch(() => null);
|
|
588
707
|
result.split_count = result.splits.filter((entry) => entry.ok && entry.send_ok).length;
|
|
589
|
-
const expectedSplits =
|
|
708
|
+
const expectedSplits = commands.length;
|
|
590
709
|
result.opened_lane_count = 1 + result.split_count;
|
|
591
710
|
result.all_lanes_opened = result.created && result.selected !== false && result.split_count === expectedSplits;
|
|
711
|
+
result.screen_read_checks = readCmuxLaneScreens(launch.cmux.bin, workspaceRef, [
|
|
712
|
+
{ agent: 'mission_overview', surface_ref: overviewSurfaceRef },
|
|
713
|
+
...result.splits.map((entry) => ({ agent: entry.agent, surface_ref: entry.surface_ref }))
|
|
714
|
+
]);
|
|
715
|
+
result.screen_read_ok = result.screen_read_checks.some((entry) => entry.ok);
|
|
592
716
|
result.ready = Boolean(result.ready && result.all_lanes_opened);
|
|
593
717
|
if (!result.all_lanes_opened) {
|
|
594
718
|
result.blockers = [
|
|
@@ -600,6 +724,135 @@ export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFil
|
|
|
600
724
|
return result;
|
|
601
725
|
}
|
|
602
726
|
|
|
727
|
+
async function applyCmuxTeamCustomization(bin, workspaceRef, lanes = []) {
|
|
728
|
+
if (!bin || !workspaceRef) return { ok: false, skipped: true, reason: 'missing cmux binary or workspace ref', operations: [] };
|
|
729
|
+
const operations = [];
|
|
730
|
+
const pushRun = async (label, args) => {
|
|
731
|
+
const run = await runProcess(bin, args, { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
732
|
+
operations.push({ label, args, ok: run.code === 0, stdout: run.stdout || '', stderr: run.stderr || '' });
|
|
733
|
+
return run.code === 0;
|
|
734
|
+
};
|
|
735
|
+
const overview = lanes.find((lane) => (lane.agent || '') === 'mission_overview') || lanes[0] || {};
|
|
736
|
+
const overviewStyle = overview.style || teamLaneStyle('mission_overview');
|
|
737
|
+
await pushRun('workspace-color', ['workspace-action', '--workspace', workspaceRef, '--action', 'set-color', '--color', overviewStyle.color]);
|
|
738
|
+
await pushRun('workspace-status', ['set-status', 'sks-team', 'Team live', '--icon', overviewStyle.icon, '--color', overviewStyle.color, '--workspace', workspaceRef]);
|
|
739
|
+
await pushRun('workspace-progress', ['set-progress', '0.15', '--label', 'Team running', '--workspace', workspaceRef]);
|
|
740
|
+
for (const lane of lanes) {
|
|
741
|
+
const style = lane.style || teamLaneStyle(lane.agent);
|
|
742
|
+
if (lane.surface_ref) await pushRun(`rename-${lane.agent}`, ['rename-tab', '--workspace', workspaceRef, '--surface', lane.surface_ref, '--title', lane.title || teamLaneTitle(lane.agent)]);
|
|
743
|
+
await pushRun(`status-${lane.agent}`, ['set-status', cmuxStatusKey(lane.agent), lane.title || teamLaneTitle(lane.agent), '--icon', style.icon, '--color', style.color, '--workspace', workspaceRef]);
|
|
744
|
+
}
|
|
745
|
+
return { ok: operations.some((entry) => entry.ok), operations };
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async function writeCmuxTeamRecord(root, record = {}) {
|
|
749
|
+
if (!record.mission_id || !record.workspace_ref) return null;
|
|
750
|
+
const statePath = cmuxTeamStatePath(root);
|
|
751
|
+
const state = await readJson(statePath, {}).catch(() => ({}));
|
|
752
|
+
const now = nowIso();
|
|
753
|
+
const nextRecord = { ...record, schema_version: 1, root: path.resolve(root || process.cwd()), updated_at: now };
|
|
754
|
+
const missions = state.missions && typeof state.missions === 'object' ? state.missions : {};
|
|
755
|
+
await writeJsonAtomic(statePath, {
|
|
756
|
+
schema_version: 1,
|
|
757
|
+
updated_at: now,
|
|
758
|
+
missions: {
|
|
759
|
+
...missions,
|
|
760
|
+
[record.mission_id]: nextRecord
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
return nextRecord;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async function readCmuxTeamRecord(root, missionId) {
|
|
767
|
+
const state = await readJson(cmuxTeamStatePath(root), {}).catch(() => ({}));
|
|
768
|
+
const missions = state.missions && typeof state.missions === 'object' ? state.missions : {};
|
|
769
|
+
if (missionId && missionId !== 'latest') return missions[missionId] || null;
|
|
770
|
+
const records = Object.values(missions).filter((entry) => entry && typeof entry === 'object');
|
|
771
|
+
records.sort((a, b) => String(b.updated_at || '').localeCompare(String(a.updated_at || '')));
|
|
772
|
+
return records[0] || null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
export async function cleanupCmuxTeamView({ root, missionId = 'latest', closeWorkspace = false } = {}) {
|
|
776
|
+
const resolvedRoot = path.resolve(root || await sksRoot());
|
|
777
|
+
const record = await readCmuxTeamRecord(resolvedRoot, missionId);
|
|
778
|
+
if (!record?.workspace_ref) return { ok: false, skipped: true, reason: 'no recorded cmux Team workspace', mission_id: missionId };
|
|
779
|
+
const cmux = await cmuxReadiness({ wake: true }).catch((err) => ({ ok: false, error: err.message || 'cmux readiness failed' }));
|
|
780
|
+
if (!cmux.ok) return { ok: false, workspace_ref: record.workspace_ref, mission_id: record.mission_id, reason: cmux.error || 'cmux not ready' };
|
|
781
|
+
const operations = [];
|
|
782
|
+
const run = async (label, args) => {
|
|
783
|
+
const out = await runProcess(cmux.bin, args, { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
784
|
+
operations.push({ label, ok: out.code === 0, stdout: out.stdout || '', stderr: out.stderr || '' });
|
|
785
|
+
return out.code === 0;
|
|
786
|
+
};
|
|
787
|
+
if (closeWorkspace) {
|
|
788
|
+
const closed = await run('close-workspace', ['close-workspace', '--workspace', record.workspace_ref]);
|
|
789
|
+
return { ok: closed, mission_id: record.mission_id, workspace_ref: record.workspace_ref, close_workspace: true, closed_workspace: closed, operations };
|
|
790
|
+
}
|
|
791
|
+
let overviewSurfaceRef = record.overview_surface_ref || record.lanes?.find((lane) => lane.agent === 'mission_overview')?.surface_ref || null;
|
|
792
|
+
let agentLanes = (record.lanes || []).filter((lane) => lane.surface_ref && lane.surface_ref !== overviewSurfaceRef && lane.agent !== 'mission_overview');
|
|
793
|
+
if (!overviewSurfaceRef) {
|
|
794
|
+
const agentRefs = new Set(agentLanes.map((lane) => lane.surface_ref));
|
|
795
|
+
overviewSurfaceRef = listCmuxWorkspaceSurfacesSync(cmux.bin, record.workspace_ref).find((surfaceRef) => !agentRefs.has(surfaceRef)) || null;
|
|
796
|
+
agentLanes = (record.lanes || []).filter((lane) => lane.surface_ref && lane.surface_ref !== overviewSurfaceRef && lane.agent !== 'mission_overview');
|
|
797
|
+
}
|
|
798
|
+
let closed = 0;
|
|
799
|
+
for (const lane of agentLanes) {
|
|
800
|
+
if (await run(`close-${lane.agent}`, ['close-surface', '--workspace', record.workspace_ref, '--surface', lane.surface_ref])) closed += 1;
|
|
801
|
+
}
|
|
802
|
+
const completeStyle = CMUX_TEAM_LANE_STYLES.execution;
|
|
803
|
+
if (overviewSurfaceRef) await run('rename-overview-complete', ['rename-tab', '--workspace', record.workspace_ref, '--surface', overviewSurfaceRef, '--title', `complete: ${record.mission_id}`.slice(0, 80)]);
|
|
804
|
+
await run('status-complete', ['set-status', 'sks-team', 'Team complete', '--icon', 'check-circle', '--color', completeStyle.color, '--workspace', record.workspace_ref]);
|
|
805
|
+
await run('progress-complete', ['set-progress', '1.0', '--label', 'Team complete', '--workspace', record.workspace_ref]);
|
|
806
|
+
await run('select-workspace', ['select-workspace', '--workspace', record.workspace_ref]);
|
|
807
|
+
await writeCmuxTeamRecord(resolvedRoot, { ...record, cleanup_completed_at: nowIso(), closed_agent_surfaces: closed }).catch(() => null);
|
|
808
|
+
return {
|
|
809
|
+
ok: true,
|
|
810
|
+
mission_id: record.mission_id,
|
|
811
|
+
workspace_ref: record.workspace_ref,
|
|
812
|
+
close_workspace: false,
|
|
813
|
+
kept_surface: overviewSurfaceRef,
|
|
814
|
+
requested_close_surfaces: agentLanes.length,
|
|
815
|
+
closed_surfaces: closed,
|
|
816
|
+
operations
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function readCmuxLaneScreens(bin, workspaceRef, lanes = []) {
|
|
821
|
+
return lanes.map((lane) => {
|
|
822
|
+
const args = ['read-screen', '--workspace', workspaceRef, '--lines', '6'];
|
|
823
|
+
if (lane.surface_ref) args.splice(3, 0, '--surface', lane.surface_ref);
|
|
824
|
+
const read = spawnSync(bin, args, { encoding: 'utf8', stdio: 'pipe' });
|
|
825
|
+
const text = `${read.stdout || ''}\n${read.stderr || ''}`.trim();
|
|
826
|
+
return {
|
|
827
|
+
agent: lane.agent,
|
|
828
|
+
surface_ref: lane.surface_ref || null,
|
|
829
|
+
ok: read.status === 0 && Boolean(text),
|
|
830
|
+
preview: text.slice(0, 1000),
|
|
831
|
+
error: read.status === 0 ? null : text || 'cmux read-screen failed'
|
|
832
|
+
};
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function firstCmuxSurfaceRef(bin, workspaceRef) {
|
|
837
|
+
return listCmuxWorkspaceSurfacesSync(bin, workspaceRef)[0] || '';
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function listCmuxWorkspaceSurfacesSync(bin, workspaceRef) {
|
|
841
|
+
if (!bin || !workspaceRef) return [];
|
|
842
|
+
const panes = spawnSync(bin, ['list-panes', '--workspace', workspaceRef], { encoding: 'utf8', stdio: 'pipe' });
|
|
843
|
+
if (panes.status !== 0) return [];
|
|
844
|
+
const paneRefs = Array.from(new Set(String(`${panes.stdout || ''}\n${panes.stderr || ''}`).match(/\bpane:\d+\b/g) || []));
|
|
845
|
+
const surfaces = [];
|
|
846
|
+
for (const paneRef of paneRefs) {
|
|
847
|
+
const run = spawnSync(bin, ['list-pane-surfaces', '--workspace', workspaceRef, '--pane', paneRef], { encoding: 'utf8', stdio: 'pipe' });
|
|
848
|
+
if (run.status !== 0) continue;
|
|
849
|
+
for (const surfaceRef of String(`${run.stdout || ''}\n${run.stderr || ''}`).match(/\bsurface:\d+\b/g) || []) {
|
|
850
|
+
if (!surfaces.includes(surfaceRef)) surfaces.push(surfaceRef);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return surfaces;
|
|
854
|
+
}
|
|
855
|
+
|
|
603
856
|
export async function runCmuxStatus(args = [], opts = {}) {
|
|
604
857
|
const once = args.includes('--once') || !args.includes('--watch');
|
|
605
858
|
do {
|
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.6.
|
|
8
|
+
export const PACKAGE_VERSION = '0.6.77';
|
|
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
|
@@ -486,7 +486,7 @@ function codexAppQuickReference(scope, commandPrefix) {
|
|
|
486
486
|
`Runtime root: ${commandPrefix} root shows whether SKS is using the nearest project root or the per-user global SKS runtime root; outside any project marker, runtime commands use the global root instead of writing .sneakoscope into the current random directory.`,
|
|
487
487
|
`Context Tracking: TriWiki SSOT. Before each route phase read only the latest coordinate+voxel overlay pack at .sneakoscope/wiki/context-pack.json; coordinate-only legacy packs are invalid. Use attention.use_first for compact high-trust recall and hydrate attention.hydrate_first from source before risky/lower-trust decisions. During every stage hydrate low-trust claims from source/hash/RGBA anchors; after changes run ${commandPrefix} wiki refresh or pack; before handoff/final run ${commandPrefix} wiki validate .sneakoscope/wiki/context-pack.json.`,
|
|
488
488
|
stackCurrentDocsPolicyText(commandPrefix),
|
|
489
|
-
`Team
|
|
489
|
+
`Team cmux view: ${commandPrefix} team "task" opens a live orchestration workspace with an overview watch pane plus color-coded split per-agent lanes; ${commandPrefix} team lane latest --agent analysis_scout_1 --follow shows one agent's status, assigned runtime tasks, recent agent events, and fallback global tail; ${commandPrefix} team cleanup-cmux latest collapses agent panes back to the overview.`,
|
|
490
490
|
`Runtime: open Codex App once, then run ${commandPrefix} bootstrap, ${commandPrefix} deps check, or ${commandPrefix} deps install cmux.`,
|
|
491
491
|
`Guard: generated harness files are immutable outside the engine source repo; check ${commandPrefix} guard check; conflicts use ${commandPrefix} conflicts prompt with human approval.`
|
|
492
492
|
].join('\n') + '\n';
|
package/src/core/team-live.mjs
CHANGED
|
@@ -376,6 +376,43 @@ export async function renderTeamAgentLane(dir, opts = {}) {
|
|
|
376
376
|
].join('\n');
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
export async function renderTeamWatch(dir, opts = {}) {
|
|
380
|
+
const lines = Math.max(1, Number(opts.lines) || 20);
|
|
381
|
+
const dashboard = await readTeamDashboard(dir);
|
|
382
|
+
const runtime = await readJson(path.join(dir, TEAM_RUNTIME_TASKS_ARTIFACT), null);
|
|
383
|
+
const missionId = opts.missionId || dashboard?.mission_id || runtime?.mission_id || path.basename(dir);
|
|
384
|
+
const agents = Object.entries(dashboard?.agents || {});
|
|
385
|
+
const visibleAgents = agents
|
|
386
|
+
.filter(([name]) => name !== 'parent_orchestrator')
|
|
387
|
+
.slice(0, Math.max(3, Number(dashboard?.agent_session_count) || 3));
|
|
388
|
+
const events = (await readTeamTranscriptTail(dir, lines)).map(parseTranscriptLine).filter(Boolean);
|
|
389
|
+
const runtimeTasks = Array.isArray(runtime?.tasks) ? runtime.tasks : Array.isArray(runtime) ? runtime : [];
|
|
390
|
+
return [
|
|
391
|
+
'# SKS Team Live Orchestration',
|
|
392
|
+
'',
|
|
393
|
+
`Mission: ${missionId}`,
|
|
394
|
+
`Updated: ${dashboard?.updated_at || 'unknown'}`,
|
|
395
|
+
`Agent session budget: ${dashboard?.agent_session_count || 'unknown'}`,
|
|
396
|
+
dashboard?.role_counts ? `Role counts: ${formatRoleCounts(dashboard.role_counts)}` : null,
|
|
397
|
+
'',
|
|
398
|
+
'## Split-Screen Map',
|
|
399
|
+
'- This overview pane follows the whole mission transcript.',
|
|
400
|
+
'- Neighbor cmux panes follow individual `sks team lane ... --agent <name>` views.',
|
|
401
|
+
'- Use `sks team event ...` to mirror scout, debate, executor, review, and verification status into the live panes.',
|
|
402
|
+
'',
|
|
403
|
+
'## Visible Agent Lanes',
|
|
404
|
+
...(visibleAgents.length
|
|
405
|
+
? visibleAgents.map(([name, status]) => `- ${name}: ${status.status || 'pending'} | ${status.phase || 'unknown'} | last_seen:${status.last_seen || 'never'}`)
|
|
406
|
+
: ['- No agent lanes registered yet.']),
|
|
407
|
+
'',
|
|
408
|
+
'## Runtime Task Snapshot',
|
|
409
|
+
...(runtimeTasks.length ? formatRuntimeTasks(runtimeTasks.slice(0, 8)) : ['- team-runtime-tasks.json not available yet.']),
|
|
410
|
+
'',
|
|
411
|
+
'## Recent Mission Events',
|
|
412
|
+
...(events.length ? events.map(formatTranscriptEvent) : ['- No transcript events yet.'])
|
|
413
|
+
].filter((line) => line !== null).join('\n');
|
|
414
|
+
}
|
|
415
|
+
|
|
379
416
|
function normalizeEvent(event = {}) {
|
|
380
417
|
return {
|
|
381
418
|
ts: event.ts || nowIso(),
|