sneakoscope 2.0.8 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +8 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +33 -8
  8. package/dist/cli/command-registry.js +1 -0
  9. package/dist/commands/doctor.js +18 -1
  10. package/dist/commands/zellij-slot-pane.js +26 -0
  11. package/dist/commands/zellij.js +144 -1
  12. package/dist/core/agents/agent-orchestrator.js +202 -9
  13. package/dist/core/agents/agent-role-config.js +92 -0
  14. package/dist/core/agents/native-cli-session-swarm.js +230 -48
  15. package/dist/core/commands/mad-sks-command.js +17 -26
  16. package/dist/core/commands/naruto-command.js +155 -37
  17. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  18. package/dist/core/fsx.js +1 -1
  19. package/dist/core/hooks-runtime.js +4 -0
  20. package/dist/core/init.js +1 -0
  21. package/dist/core/naruto/naruto-active-pool.js +141 -0
  22. package/dist/core/naruto/naruto-concurrency-governor.js +17 -2
  23. package/dist/core/naruto/naruto-real-worker-child.js +35 -0
  24. package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
  25. package/dist/core/naruto/naruto-work-graph.js +2 -1
  26. package/dist/core/release/release-gate-cache-v2.js +58 -4
  27. package/dist/core/release/release-gate-dag.js +36 -25
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-dashboard-renderer.js +22 -6
  30. package/dist/core/zellij/zellij-launcher.js +3 -3
  31. package/dist/core/zellij/zellij-layout-builder.js +1 -1
  32. package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
  33. package/dist/core/zellij/zellij-right-column-manager.js +304 -0
  34. package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
  35. package/dist/core/zellij/zellij-ui-mode.js +16 -0
  36. package/dist/core/zellij/zellij-worker-pane-manager.js +152 -17
  37. package/dist/scripts/agent-role-config-repair-check.js +33 -0
  38. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
  39. package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
  40. package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
  41. package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
  42. package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
  43. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
  44. package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
  45. package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
  46. package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
  47. package/dist/scripts/naruto-extreme-parallelism-real-check.js +42 -0
  48. package/dist/scripts/naruto-real-active-pool-check.js +39 -0
  49. package/dist/scripts/naruto-real-active-pool-runtime-check.js +53 -0
  50. package/dist/scripts/naruto-work-graph-check.js +1 -1
  51. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +48 -0
  52. package/dist/scripts/product-design-auto-install-check.js +3 -3
  53. package/dist/scripts/product-design-plugin-routing-check.js +3 -3
  54. package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
  55. package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
  56. package/dist/scripts/release-check-dynamic-execute.js +27 -1
  57. package/dist/scripts/release-check-dynamic.js +38 -11
  58. package/dist/scripts/release-check-stamp.js +7 -2
  59. package/dist/scripts/release-dag-full-coverage-check.js +35 -0
  60. package/dist/scripts/release-dynamic-performance-check.js +31 -1
  61. package/dist/scripts/release-gate-existence-audit.js +29 -33
  62. package/dist/scripts/release-parallel-speed-budget-check.js +67 -13
  63. package/dist/scripts/release-readiness-report.js +14 -3
  64. package/dist/scripts/zellij-dashboard-pane-check.js +6 -4
  65. package/dist/scripts/zellij-developer-controls-check.js +20 -0
  66. package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
  67. package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
  68. package/dist/scripts/zellij-right-column-geometry-proof.js +162 -0
  69. package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
  70. package/dist/scripts/zellij-right-column-manager-check.js +22 -0
  71. package/dist/scripts/zellij-slot-only-ui-check.js +22 -0
  72. package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
  73. package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
  74. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
  75. package/package.json +23 -5
  76. package/schemas/zellij/zellij-right-column-state.schema.json +41 -0
@@ -0,0 +1,304 @@
1
+ import path from 'node:path';
2
+ import { appendJsonl, ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ import { openZellijDashboardPane } from './zellij-dashboard-pane.js';
4
+ import { zellijUiModeCreatesDashboard } from './zellij-ui-mode.js';
5
+ export const ZELLIJ_RIGHT_COLUMN_STATE_SCHEMA = 'sks.zellij-right-column-state.v1';
6
+ export async function ensureRightColumn(input) {
7
+ const paths = resolveRightColumnPaths(input.root, input.missionId, input.projectRoot);
8
+ await ensureDir(paths.missionDir);
9
+ const uiMode = input.uiMode || 'compact-slots';
10
+ const createDashboard = input.createDashboard ?? zellijUiModeCreatesDashboard(uiMode);
11
+ const existing = await readRightColumnState(input.root, input.missionId, input.projectRoot);
12
+ if (existing?.status === 'active') {
13
+ return writeRightColumnState(paths.statePath, {
14
+ ...existing,
15
+ updated_at: nowIso(),
16
+ ui_mode: existing.ui_mode || uiMode
17
+ });
18
+ }
19
+ const creating = await writeRightColumnState(paths.statePath, {
20
+ schema: ZELLIJ_RIGHT_COLUMN_STATE_SCHEMA,
21
+ generated_at: nowIso(),
22
+ updated_at: nowIso(),
23
+ mission_id: input.missionId,
24
+ session_name: input.sessionName,
25
+ status: 'creating',
26
+ dashboard_pane_id: null,
27
+ right_anchor_pane_id: null,
28
+ ui_mode: uiMode,
29
+ visible_worker_panes: [],
30
+ headless_workers: [],
31
+ blockers: []
32
+ });
33
+ await appendRightColumnEvent(paths.eventsPath, 'right_column_creating', creating, {});
34
+ if (!createDashboard) {
35
+ const active = await writeRightColumnState(paths.statePath, {
36
+ ...creating,
37
+ updated_at: nowIso(),
38
+ status: 'active',
39
+ dashboard_pane_id: null,
40
+ right_anchor_pane_id: null,
41
+ ui_mode: uiMode,
42
+ blockers: []
43
+ });
44
+ await appendRightColumnEvent(paths.eventsPath, 'right_column_created', active, {
45
+ ok: true,
46
+ dashboard_created: false,
47
+ ui_mode: uiMode
48
+ });
49
+ return active;
50
+ }
51
+ const dashboard = await openZellijDashboardPane({
52
+ root: paths.projectRoot,
53
+ missionId: input.missionId,
54
+ sessionName: input.sessionName,
55
+ cwd: input.cwd || paths.projectRoot,
56
+ snapshot: {
57
+ ...input.dashboardSnapshot,
58
+ mission_id: input.missionId,
59
+ gate_progress: input.dashboardSnapshot.gate_progress || 'right-column:first-worker'
60
+ }
61
+ }).catch((err) => ({
62
+ ok: false,
63
+ pane_id: null,
64
+ blockers: [`zellij_dashboard_exception:${err?.message || String(err)}`]
65
+ }));
66
+ const blockers = Array.isArray(dashboard.blockers) ? dashboard.blockers : [];
67
+ const active = await writeRightColumnState(paths.statePath, {
68
+ ...creating,
69
+ updated_at: nowIso(),
70
+ status: blockers.length ? 'creating' : 'active',
71
+ dashboard_pane_id: dashboard.pane_id ? String(dashboard.pane_id) : null,
72
+ right_anchor_pane_id: dashboard.pane_id ? String(dashboard.pane_id) : null,
73
+ ui_mode: uiMode,
74
+ blockers
75
+ });
76
+ await appendRightColumnEvent(paths.eventsPath, 'right_column_created', active, { ok: active.status === 'active' });
77
+ await appendRightColumnEvent(paths.eventsPath, 'dashboard_pane_created', active, { pane_id: active.dashboard_pane_id, blockers });
78
+ return active;
79
+ }
80
+ export async function prepareWorkerInRightColumn(input) {
81
+ return withRightColumnLock(input.root, input.missionId, async () => prepareWorkerInRightColumnUnlocked(input));
82
+ }
83
+ async function prepareWorkerInRightColumnUnlocked(input) {
84
+ const paths = resolveRightColumnPaths(input.root, input.missionId, input.projectRoot);
85
+ const state = await ensureRightColumn({
86
+ root: input.root,
87
+ ...(input.projectRoot ? { projectRoot: input.projectRoot } : {}),
88
+ missionId: input.missionId,
89
+ sessionName: input.sessionName,
90
+ cwd: input.cwd,
91
+ dashboardSnapshot: input.dashboardSnapshot,
92
+ uiMode: input.uiMode || 'compact-slots'
93
+ });
94
+ const activeVisible = state.visible_worker_panes.filter((pane) => pane.status === 'launching' || pane.status === 'running');
95
+ const cap = Math.max(1, Math.floor(Number(input.visiblePaneCap || 1)));
96
+ if (activeVisible.length >= cap) {
97
+ const next = await recordHeadlessWorkerInRightColumnUnlocked({
98
+ root: input.root,
99
+ ...(input.projectRoot ? { projectRoot: input.projectRoot } : {}),
100
+ missionId: input.missionId,
101
+ sessionName: input.sessionName,
102
+ slotId: input.worker.slotId,
103
+ generationIndex: input.worker.generationIndex,
104
+ reason: `visible_pane_cap:${cap}`
105
+ });
106
+ return { state: next, placement: 'headless', focusPaneId: null, yOrder: null };
107
+ }
108
+ const lastVisible = activeVisible[activeVisible.length - 1];
109
+ const focusPaneId = lastVisible?.pane_id || state.right_anchor_pane_id || state.dashboard_pane_id || null;
110
+ const yOrder = Math.max(1, ...state.visible_worker_panes.map((pane) => Number(pane.y_order || 0) + 1));
111
+ const next = await writeRightColumnState(paths.statePath, {
112
+ ...state,
113
+ updated_at: nowIso(),
114
+ right_anchor_pane_id: focusPaneId,
115
+ visible_worker_panes: [
116
+ ...state.visible_worker_panes,
117
+ {
118
+ pane_id: null,
119
+ slot_id: input.worker.slotId,
120
+ generation_index: input.worker.generationIndex,
121
+ y_order: yOrder,
122
+ status: 'launching'
123
+ }
124
+ ]
125
+ });
126
+ await appendRightColumnEvent(paths.eventsPath, 'scheduler_slot_reserved', next, {
127
+ slot_id: input.worker.slotId,
128
+ generation_index: input.worker.generationIndex,
129
+ y_order: yOrder
130
+ });
131
+ return { state: next, placement: 'zellij-pane', focusPaneId, yOrder };
132
+ }
133
+ export async function recordWorkerPaneInRightColumn(input) {
134
+ const paths = resolveRightColumnPaths(input.root, input.missionId, input.projectRoot);
135
+ const state = await readRightColumnState(input.root, input.missionId, input.projectRoot);
136
+ if (!state)
137
+ return null;
138
+ const yOrder = Math.max(1, Math.floor(Number(input.yOrder || 0))) || Math.max(1, ...state.visible_worker_panes.map((pane) => pane.y_order + 1));
139
+ const slotId = input.record.slot_id;
140
+ const generationIndex = input.record.generation_index;
141
+ const rows = state.visible_worker_panes.filter((pane) => !(pane.slot_id === slotId && pane.generation_index === generationIndex && pane.pane_id == null));
142
+ rows.push({
143
+ pane_id: input.record.pane_id,
144
+ slot_id: slotId,
145
+ generation_index: generationIndex,
146
+ y_order: yOrder,
147
+ status: input.record.status === 'failed' ? 'failed' : 'running'
148
+ });
149
+ const next = await writeRightColumnState(paths.statePath, {
150
+ ...state,
151
+ updated_at: nowIso(),
152
+ right_anchor_pane_id: input.record.pane_id || state.right_anchor_pane_id,
153
+ visible_worker_panes: rows.sort((a, b) => a.y_order - b.y_order),
154
+ blockers: [...new Set([...state.blockers, ...(input.record.blockers || [])])]
155
+ });
156
+ await appendRightColumnEvent(paths.eventsPath, 'worker_pane_created', next, {
157
+ slot_id: slotId,
158
+ generation_index: generationIndex,
159
+ pane_id: input.record.pane_id,
160
+ y_order: yOrder,
161
+ direction_applied: input.record.direction_applied,
162
+ blockers: input.record.blockers
163
+ });
164
+ return next;
165
+ }
166
+ export async function recordHeadlessWorkerInRightColumn(input) {
167
+ return withRightColumnLock(input.root, input.missionId, async () => recordHeadlessWorkerInRightColumnUnlocked(input));
168
+ }
169
+ async function recordHeadlessWorkerInRightColumnUnlocked(input) {
170
+ const paths = resolveRightColumnPaths(input.root, input.missionId, input.projectRoot);
171
+ const state = await readRightColumnState(input.root, input.missionId, input.projectRoot) || {
172
+ schema: ZELLIJ_RIGHT_COLUMN_STATE_SCHEMA,
173
+ generated_at: nowIso(),
174
+ updated_at: nowIso(),
175
+ mission_id: input.missionId,
176
+ session_name: input.sessionName,
177
+ status: 'absent',
178
+ dashboard_pane_id: null,
179
+ right_anchor_pane_id: null,
180
+ ui_mode: 'compact-slots',
181
+ visible_worker_panes: [],
182
+ headless_workers: [],
183
+ blockers: []
184
+ };
185
+ const headless = [
186
+ ...state.headless_workers.filter((row) => !(row.slot_id === input.slotId && row.generation_index === input.generationIndex)),
187
+ { slot_id: input.slotId, generation_index: input.generationIndex, reason: input.reason, status: 'running', closed_at: null }
188
+ ];
189
+ const next = await writeRightColumnState(paths.statePath, { ...state, updated_at: nowIso(), headless_workers: headless });
190
+ await appendRightColumnEvent(paths.eventsPath, 'worker_headless_overflow', next, {
191
+ slot_id: input.slotId,
192
+ generation_index: input.generationIndex,
193
+ reason: input.reason
194
+ });
195
+ return next;
196
+ }
197
+ export async function closeWorkerInRightColumn(input) {
198
+ const paths = resolveRightColumnPaths(input.root, input.missionId, input.projectRoot);
199
+ const state = await readRightColumnState(input.root, input.missionId, input.projectRoot);
200
+ if (!state)
201
+ return null;
202
+ const panes = state.visible_worker_panes.map((pane) => {
203
+ const same = pane.slot_id === input.slotId && pane.generation_index === input.generationIndex;
204
+ return same ? { ...pane, pane_id: input.paneId || pane.pane_id, status: input.status } : pane;
205
+ });
206
+ const headless = state.headless_workers.map((row) => {
207
+ const same = row.slot_id === input.slotId && row.generation_index === input.generationIndex;
208
+ return same ? { ...row, status: input.status === 'draining' ? 'closed' : input.status, closed_at: nowIso() } : row;
209
+ });
210
+ const visibleStillActive = panes.filter((pane) => pane.status === 'launching' || pane.status === 'running');
211
+ const headlessStillActive = headless.filter((row) => !row.status || row.status === 'running');
212
+ const next = await writeRightColumnState(paths.statePath, {
213
+ ...state,
214
+ updated_at: nowIso(),
215
+ status: visibleStillActive.length || headlessStillActive.length ? 'active' : 'draining',
216
+ right_anchor_pane_id: visibleStillActive[visibleStillActive.length - 1]?.pane_id || state.dashboard_pane_id,
217
+ visible_worker_panes: panes,
218
+ headless_workers: headless
219
+ });
220
+ await appendRightColumnEvent(paths.eventsPath, 'worker_pane_drained', next, {
221
+ slot_id: input.slotId,
222
+ generation_index: input.generationIndex,
223
+ pane_id: input.paneId,
224
+ status: input.status
225
+ });
226
+ return next;
227
+ }
228
+ export async function openWorkerInRightColumn(input) {
229
+ const prepared = await prepareWorkerInRightColumn({
230
+ root: input.root,
231
+ missionId: input.state.mission_id,
232
+ sessionName: input.state.session_name,
233
+ cwd: input.worker.cwd || input.root,
234
+ worker: { slotId: input.worker.slotId, generationIndex: input.worker.generationIndex },
235
+ visiblePaneCap: input.visiblePaneCap,
236
+ dashboardSnapshot: { mission_id: input.state.mission_id }
237
+ });
238
+ return { state: prepared.state, pane: null, placement: prepared.placement };
239
+ }
240
+ export async function readRightColumnState(root, missionId, projectRoot) {
241
+ const paths = resolveRightColumnPaths(root, missionId, projectRoot);
242
+ return readJson(paths.statePath, null);
243
+ }
244
+ export function resolveRightColumnPaths(root, missionId, projectRoot) {
245
+ const resolvedRoot = path.resolve(root);
246
+ const resolvedProjectRoot = projectRoot ? path.resolve(projectRoot) : inferProjectRoot(resolvedRoot, missionId);
247
+ const missionDir = inferMissionDir(resolvedRoot, missionId) || path.join(resolvedProjectRoot, '.sneakoscope', 'missions', missionId);
248
+ return {
249
+ projectRoot: resolvedProjectRoot,
250
+ missionDir,
251
+ statePath: path.join(missionDir, 'zellij-right-column-state.json'),
252
+ eventsPath: path.join(missionDir, 'zellij-right-column-events.jsonl')
253
+ };
254
+ }
255
+ async function writeRightColumnState(file, state) {
256
+ await writeJsonAtomic(file, state);
257
+ return state;
258
+ }
259
+ async function appendRightColumnEvent(file, eventType, state, payload) {
260
+ await appendJsonl(file, {
261
+ schema: 'sks.zellij-right-column-event.v1',
262
+ ts: nowIso(),
263
+ event_type: eventType,
264
+ mission_id: state.mission_id,
265
+ session_name: state.session_name,
266
+ ...payload
267
+ });
268
+ }
269
+ function inferMissionDir(root, missionId) {
270
+ if (path.basename(root) === 'agents' && path.basename(path.dirname(root)) === missionId)
271
+ return path.dirname(root);
272
+ if (path.basename(root) === missionId && path.basename(path.dirname(root)) === 'missions')
273
+ return root;
274
+ return null;
275
+ }
276
+ function inferProjectRoot(root, missionId) {
277
+ if (path.basename(root) === 'agents' && path.basename(path.dirname(root)) === missionId) {
278
+ return path.dirname(path.dirname(path.dirname(path.dirname(root))));
279
+ }
280
+ if (path.basename(root) === missionId && path.basename(path.dirname(root)) === 'missions') {
281
+ return path.dirname(path.dirname(path.dirname(root)));
282
+ }
283
+ return root;
284
+ }
285
+ const rightColumnLocks = new Map();
286
+ async function withRightColumnLock(root, missionId, fn) {
287
+ const key = `${path.resolve(root)}:${missionId}`;
288
+ const previous = rightColumnLocks.get(key) || Promise.resolve();
289
+ let release;
290
+ const current = new Promise((resolve) => {
291
+ release = resolve;
292
+ });
293
+ rightColumnLocks.set(key, previous.then(() => current, () => current));
294
+ await previous.catch(() => undefined);
295
+ try {
296
+ return await fn();
297
+ }
298
+ finally {
299
+ release();
300
+ if (rightColumnLocks.get(key) === current)
301
+ rightColumnLocks.delete(key);
302
+ }
303
+ }
304
+ //# sourceMappingURL=zellij-right-column-manager.js.map
@@ -0,0 +1,82 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function renderZellijSlotPane(input) {
4
+ const mode = input.mode || 'compact-slots';
5
+ const maxLines = mode === 'compact-slots' ? 5 : mode === 'dashboard-plus-slots' ? 8 : 20;
6
+ const task = trimInline(input.currentFile || input.currentTask || '-', 56);
7
+ const heartbeat = input.heartbeatAgeMs == null
8
+ ? 'unknown'
9
+ : input.heartbeatAgeMs < 1000
10
+ ? 'now'
11
+ : `${Math.max(1, Math.round(input.heartbeatAgeMs / 1000))}s ago`;
12
+ const rows = [
13
+ `${input.slotId} gen-${Math.max(1, Math.floor(Number(input.generationIndex) || 1))}`,
14
+ `${trimInline(input.role || 'worker', 18)} - ${trimInline(input.backend || 'codex-sdk', 18)} - ${trimInline(input.worktreeId || '-', 18)}`,
15
+ `status: ${trimInline(input.status || 'running', 14)} ${task}`,
16
+ `patch: ${trimInline(input.patchStatus || 'queued', 18)} verify: ${trimInline(input.verifyStatus || 'queued', 18)}`,
17
+ `heartbeat: ${heartbeat}`
18
+ ];
19
+ return rows.slice(0, maxLines).join('\n');
20
+ }
21
+ export async function renderZellijSlotPaneFromArtifacts(input) {
22
+ const artifactDir = path.resolve(input.artifactDir);
23
+ const result = await readJson(path.join(artifactDir, 'worker-result.json'));
24
+ const heartbeatPath = path.join(artifactDir, 'worker-heartbeat.jsonl');
25
+ const heartbeatMtime = await statMtimeMs(heartbeatPath);
26
+ const now = Date.now();
27
+ return renderZellijSlotPane({
28
+ slotId: input.slotId,
29
+ generationIndex: input.generationIndex,
30
+ role: input.role || result?.persona_id || result?.agent_id || null,
31
+ backend: input.backend || result?.backend || null,
32
+ status: result?.status || (heartbeatMtime ? 'running' : 'launching'),
33
+ currentTask: result?.summary || null,
34
+ currentFile: Array.isArray(result?.changed_files) ? result.changed_files[0] : null,
35
+ patchStatus: Array.isArray(result?.patch_envelopes) && result.patch_envelopes.length ? 'candidate' : 'queued',
36
+ verifyStatus: result?.verification?.status || 'queued',
37
+ heartbeatAgeMs: heartbeatMtime ? now - heartbeatMtime : null,
38
+ worktreeId: result?.worktree?.id || null,
39
+ mode: input.mode || 'compact-slots'
40
+ });
41
+ }
42
+ export function buildZellijSlotPaneCommand(input) {
43
+ const args = [
44
+ input.cliPath,
45
+ 'zellij-slot-pane',
46
+ '--mission', input.missionId,
47
+ '--slot', input.slotId,
48
+ '--generation', String(Math.max(1, Math.floor(Number(input.generationIndex) || 1))),
49
+ '--artifact-dir', input.artifactDir,
50
+ '--mode', input.mode || 'compact-slots',
51
+ ...(input.backend ? ['--backend', input.backend] : []),
52
+ ...(input.role ? ['--role', input.role] : []),
53
+ ...(input.watch ? ['--watch'] : [])
54
+ ];
55
+ return [input.nodePath || process.execPath, ...args].map(shellQuote).join(' ');
56
+ }
57
+ async function readJson(file) {
58
+ try {
59
+ return JSON.parse(await fs.promises.readFile(file, 'utf8'));
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function statMtimeMs(file) {
66
+ try {
67
+ return (await fs.promises.stat(file)).mtimeMs;
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ function trimInline(value, max) {
74
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
75
+ if (text.length <= max)
76
+ return text;
77
+ return text.slice(0, Math.max(1, max - 3)) + '...';
78
+ }
79
+ function shellQuote(value) {
80
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
81
+ }
82
+ //# sourceMappingURL=zellij-slot-pane-renderer.js.map
@@ -0,0 +1,16 @@
1
+ export function resolveZellijUiMode(args = [], env = process.env) {
2
+ const fromEnv = String(env.SKS_ZELLIJ_UI_MODE || '').trim();
3
+ if (fromEnv === 'full-debug')
4
+ return 'full-debug';
5
+ if (fromEnv === 'dashboard-plus-slots')
6
+ return 'dashboard-plus-slots';
7
+ if (args.includes('--zellij-dashboard'))
8
+ return 'dashboard-plus-slots';
9
+ if (args.includes('--zellij-full-debug'))
10
+ return 'full-debug';
11
+ return 'compact-slots';
12
+ }
13
+ export function zellijUiModeCreatesDashboard(mode) {
14
+ return mode !== 'compact-slots';
15
+ }
16
+ //# sourceMappingURL=zellij-ui-mode.js.map
@@ -4,6 +4,7 @@ import { providerPaneLabel } from '../provider/provider-badge.js';
4
4
  import { resolveProviderContext } from '../provider/provider-context.js';
5
5
  import { runZellij } from './zellij-command.js';
6
6
  import { extractZellijPaneIdFromOutput } from './zellij-lane-runtime.js';
7
+ import { closeWorkerInRightColumn, prepareWorkerInRightColumn, recordWorkerPaneInRightColumn } from './zellij-right-column-manager.js';
7
8
  export const ZELLIJ_WORKER_PANE_SCHEMA = 'sks.zellij-worker-pane.v1';
8
9
  export const ZELLIJ_WORKER_PANE_EVENT_SCHEMA = 'sks.zellij-worker-pane-event.v1';
9
10
  export function buildWorkerPaneName(slotId, generationIndex) {
@@ -28,7 +29,7 @@ export function buildWorkerPaneArtifact(input) {
28
29
  schema: ZELLIJ_WORKER_PANE_SCHEMA,
29
30
  generated_at: now,
30
31
  updated_at: now,
31
- ok: blockers.length === 0 && isRealZellijWorkerPaneIdSource(paneIdSource) && Boolean(input.paneId),
32
+ ok: blockers.length === 0 && (paneIdSource === 'zellij_worker_headless_overflow' || (isRealZellijWorkerPaneIdSource(paneIdSource) && Boolean(input.paneId))),
32
33
  status: input.status || 'launching',
33
34
  mission_id: input.missionId,
34
35
  session_name: input.sessionName,
@@ -51,16 +52,17 @@ export function buildWorkerPaneArtifact(input) {
51
52
  stdout_log: input.stdoutLog,
52
53
  stderr_log: input.stderrLog,
53
54
  parent_child_transport: 'worker-result-json-and-heartbeat',
54
- scaling_primitive: 'native_cli_process_in_zellij_worker_pane',
55
- command: '<native-cli-worker-command>',
55
+ scaling_primitive: input.scalingPrimitive || 'native_cli_process_in_zellij_worker_pane',
56
+ command: String(input.workerCommand || '').includes('zellij-slot-pane') ? '<zellij-slot-pane-renderer-command>' : '<native-cli-worker-command>',
56
57
  create_session: input.createSession || null,
57
58
  launch: input.launch || null,
58
59
  pane_reconciliation: input.paneReconciliation || null,
59
60
  opened_at: now,
60
61
  closed_at: null,
61
62
  close: null,
62
- direction_requested: 'right',
63
+ direction_requested: input.directionRequested || 'right',
63
64
  direction_applied: input.directionApplied || 'not_applied',
65
+ right_column: input.rightColumn || null,
64
66
  sdk_thread_id: input.sdkThreadId || null,
65
67
  sdk_run_id: input.sdkRunId || null,
66
68
  stream_event_count: Number(input.streamEventCount || 0),
@@ -85,24 +87,79 @@ export async function openWorkerPane(input) {
85
87
  optional: false
86
88
  });
87
89
  const createSession = normalizeExistingZellijSession(input.sessionName, createSessionRaw);
90
+ const rightColumn = input.rightColumnMode === 'spawn-on-first-worker'
91
+ ? await prepareWorkerInRightColumn({
92
+ root,
93
+ ...(input.projectRoot ? { projectRoot: input.projectRoot } : {}),
94
+ missionId: input.missionId,
95
+ sessionName: input.sessionName,
96
+ cwd,
97
+ worker: { slotId: input.slotId, generationIndex: input.generationIndex },
98
+ visiblePaneCap: input.visiblePaneCap || 1,
99
+ uiMode: input.uiMode || 'compact-slots',
100
+ dashboardSnapshot: {
101
+ ...(input.dashboardSnapshot || {}),
102
+ mode: String(input.dashboardSnapshot?.mode || 'naruto'),
103
+ active_workers: Number(input.dashboardSnapshot?.active_workers || input.visiblePaneCap || 1),
104
+ visible_panes: Number(input.dashboardSnapshot?.visible_panes || input.visiblePaneCap || 1)
105
+ }
106
+ })
107
+ : null;
108
+ if (rightColumn?.placement === 'headless') {
109
+ const record = buildWorkerPaneArtifact({
110
+ ...input,
111
+ paneId: null,
112
+ paneIdSource: 'zellij_worker_headless_overflow',
113
+ createSession,
114
+ launch: null,
115
+ paneReconciliation: null,
116
+ directionRequested: 'right',
117
+ directionApplied: 'not_applied',
118
+ rightColumn: {
119
+ mode: 'spawn-on-first-worker',
120
+ focus_pane_id: null,
121
+ y_order: null
122
+ },
123
+ status: 'running',
124
+ providerContext,
125
+ serviceTier: input.serviceTier || providerContext.service_tier,
126
+ scalingPrimitive: 'native_cli_process_headless_with_slot_dashboard',
127
+ blockers: []
128
+ });
129
+ await writeWorkerPaneArtifact(root, record);
130
+ await appendWorkerPaneEvent(root, 'worker_headless_overflow', input, {
131
+ slot_id: input.slotId,
132
+ generation_index: input.generationIndex,
133
+ reason: `visible_pane_cap:${input.visiblePaneCap || 1}`
134
+ });
135
+ return record;
136
+ }
88
137
  const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running', input.worktree || null);
138
+ const directionRequested = rightColumn?.focusPaneId ? 'down' : 'right';
139
+ const focus = rightColumn?.focusPaneId
140
+ ? await focusZellijPaneById(input.sessionName, rightColumn.focusPaneId, cwd)
141
+ : null;
142
+ const newPaneArgs = ['--session', input.sessionName, 'action', 'new-pane', '--direction', directionRequested, ...(directionRequested === 'down' ? ['--near-current-pane'] : []), '--name', paneName, '--', 'sh', '-lc', input.workerCommand];
89
143
  let launch = createSession.ok
90
- ? await runZellij(['--session', input.sessionName, 'action', 'new-pane', '--direction', 'right', '--name', paneName, '--', 'sh', '-lc', input.workerCommand], {
144
+ ? await runZellij(newPaneArgs, {
91
145
  cwd,
92
146
  timeoutMs: 5000,
93
147
  optional: false
94
148
  })
95
149
  : null;
96
- let directionApplied = launch?.ok ? 'right' : 'not_applied';
150
+ let directionApplied = launch?.ok ? directionRequested : 'not_applied';
97
151
  if (createSession.ok && launch && !launch.ok) {
98
- const fallback = await runZellij(['--session', input.sessionName, 'action', 'new-pane', '--name', paneName, '--', 'sh', '-lc', input.workerCommand], {
152
+ const fallbackArgs = rightColumn && directionRequested === 'down'
153
+ ? ['--session', input.sessionName, 'action', 'new-pane', '--direction', 'down', '--name', paneName, '--', 'sh', '-lc', input.workerCommand]
154
+ : ['--session', input.sessionName, 'action', 'new-pane', '--direction', directionRequested, '--name', paneName, '--', 'sh', '-lc', input.workerCommand];
155
+ const fallback = await runZellij(fallbackArgs, {
99
156
  cwd,
100
157
  timeoutMs: 5000,
101
158
  optional: false
102
159
  });
103
160
  if (fallback.ok) {
104
161
  launch = fallback;
105
- directionApplied = 'unknown';
162
+ directionApplied = rightColumn ? 'down' : 'unknown';
106
163
  }
107
164
  }
108
165
  const stdoutPaneId = launch?.ok ? extractZellijPaneIdFromOutput(launch.stdout_tail) : null;
@@ -118,6 +175,7 @@ export async function openWorkerPane(input) {
118
175
  : 'zellij_worker_pane_launch_failed';
119
176
  const blockers = [
120
177
  ...(createSession.ok ? [] : createSession.blockers.map((blocker) => `zellij_worker_session_${blocker}`)),
178
+ ...(rightColumn && rightColumn.placement !== 'zellij-pane' ? [`zellij_worker_right_column_${rightColumn.placement}`] : []),
121
179
  ...(launch && !launch.ok ? launch.blockers.map((blocker) => `zellij_worker_pane_${blocker}`) : []),
122
180
  ...(launch?.ok && !isRealZellijWorkerPaneIdSource(paneIdSource) ? ['zellij_worker_pane_id_real_source_missing'] : [])
123
181
  ];
@@ -127,14 +185,30 @@ export async function openWorkerPane(input) {
127
185
  paneIdSource,
128
186
  createSession,
129
187
  launch,
130
- paneReconciliation: { ...(reconciledPane || {}), rename_pane: renamePane },
188
+ paneReconciliation: {
189
+ ...(reconciledPane || {}),
190
+ focus_pane: focus,
191
+ focus_degraded: focus ? focus.ok !== true : false,
192
+ rename_pane: renamePane
193
+ },
194
+ directionRequested,
131
195
  directionApplied,
196
+ rightColumn: rightColumn ? { mode: 'spawn-on-first-worker', focus_pane_id: rightColumn.focusPaneId, y_order: rightColumn.yOrder } : null,
132
197
  status: blockers.length ? 'failed' : 'running',
133
198
  providerContext,
134
199
  serviceTier: input.serviceTier || providerContext.service_tier,
135
200
  blockers
136
201
  });
137
202
  await writeWorkerPaneArtifact(root, record);
203
+ if (rightColumn) {
204
+ await recordWorkerPaneInRightColumn({
205
+ root,
206
+ ...(input.projectRoot ? { projectRoot: input.projectRoot } : {}),
207
+ missionId: input.missionId,
208
+ record,
209
+ yOrder: rightColumn.yOrder
210
+ });
211
+ }
138
212
  await appendWorkerPaneEvent(root, 'zellij_worker_pane_created', input, {
139
213
  ok: record.ok,
140
214
  pane_id: record.pane_id,
@@ -160,7 +234,7 @@ export async function openWorkerPane(input) {
160
234
  provider_context: record.provider_context,
161
235
  direction_requested: record.direction_requested,
162
236
  direction_applied: record.direction_applied,
163
- command: '<native-cli-worker-command>',
237
+ command: record.command,
164
238
  worker_artifact_dir: input.workerArtifactDir,
165
239
  worker_result_path: input.resultPath,
166
240
  heartbeat_path: input.heartbeatPath,
@@ -174,12 +248,16 @@ export async function openWorkerPane(input) {
174
248
  }
175
249
  export async function closeWorkerPane(input) {
176
250
  const root = path.resolve(input.root);
177
- const close = process.env.SKS_ZELLIJ_CLOSE_WORKER_PANE === '1' && input.paneRecord.pane_id
178
- ? await runZellij(['--session', input.paneRecord.session_name, 'action', 'close-pane', '--pane-id', input.paneRecord.pane_id], {
179
- cwd: input.cwd || packageRoot(),
180
- timeoutMs: 5000,
181
- optional: true
182
- })
251
+ const success = (input.status || 'closed') === 'closed' && !(input.blockers || []).length;
252
+ const closeSuccess = process.env.SKS_ZELLIJ_CLOSE_WORKER_PANE !== '0';
253
+ const closeFailed = process.env.SKS_ZELLIJ_CLOSE_FAILED_PANE === '1' || process.env.SKS_ZELLIJ_KEEP_FAILED_PANE === '0';
254
+ const paneId = input.paneRecord.pane_id;
255
+ const shouldClose = Boolean(paneId) && (success ? closeSuccess : closeFailed);
256
+ const close = shouldClose
257
+ ? await closeZellijPaneById(input.paneRecord.session_name, paneId || '', input.cwd || packageRoot())
258
+ : null;
259
+ const rename = !shouldClose && paneId
260
+ ? await renameZellijPaneById(input.paneRecord.session_name, paneId, `${input.paneRecord.pane_title} · ${success ? 'drained' : 'failed'}`, input.cwd || packageRoot())
183
261
  : null;
184
262
  const next = {
185
263
  ...input.paneRecord,
@@ -193,9 +271,25 @@ export async function closeWorkerPane(input) {
193
271
  stream_event_count: Number(input.streamEventCount || input.paneRecord.stream_event_count || 0),
194
272
  structured_output_valid: input.structuredOutputValid === true || input.paneRecord.structured_output_valid === true,
195
273
  worker_result_path: input.workerResultPath || input.paneRecord.worker_result_path,
196
- blockers: [...input.paneRecord.blockers, ...(input.blockers || []), ...(close && !close.ok ? close.blockers.map((blocker) => `zellij_worker_close_${blocker}`) : [])]
274
+ blockers: [
275
+ ...input.paneRecord.blockers,
276
+ ...(input.blockers || []),
277
+ ...(close && !close.ok ? close.blockers.map((blocker) => `zellij_worker_close_${blocker}`) : []),
278
+ ...(rename && !rename.ok ? rename.blockers.map((blocker) => `zellij_worker_rename_${blocker}`) : [])
279
+ ]
197
280
  };
198
281
  await writeWorkerPaneArtifact(root, next);
282
+ if (next.right_column?.mode === 'spawn-on-first-worker') {
283
+ await closeWorkerInRightColumn({
284
+ root,
285
+ ...(input.projectRoot ? { projectRoot: input.projectRoot } : {}),
286
+ missionId: next.mission_id,
287
+ slotId: next.slot_id,
288
+ generationIndex: next.generation_index,
289
+ paneId: next.pane_id,
290
+ status: next.status === 'failed' ? 'failed' : close?.ok ? 'closed' : 'draining'
291
+ });
292
+ }
199
293
  await appendWorkerPaneEvent(root, 'pane_closed', {
200
294
  root,
201
295
  missionId: next.mission_id,
@@ -374,6 +468,47 @@ async function renameZellijPaneById(sessionName, paneId, paneName, cwd) {
374
468
  }
375
469
  return last;
376
470
  }
471
+ async function focusZellijPaneById(sessionName, paneId, cwd) {
472
+ const candidates = zellijPaneIdCandidates(paneId);
473
+ let last = null;
474
+ for (const candidate of candidates) {
475
+ const focus = await runZellij(['--session', sessionName, 'action', 'focus-pane-id', candidate], {
476
+ cwd,
477
+ timeoutMs: 5000,
478
+ optional: true
479
+ });
480
+ last = focus;
481
+ if (focus.ok)
482
+ return focus;
483
+ }
484
+ return last;
485
+ }
486
+ async function closeZellijPaneById(sessionName, paneId, cwd) {
487
+ const candidates = zellijPaneIdCandidates(paneId);
488
+ let last = null;
489
+ for (const candidate of candidates) {
490
+ const close = await runZellij(['--session', sessionName, 'action', 'close-pane', '--pane-id', candidate], {
491
+ cwd,
492
+ timeoutMs: 5000,
493
+ optional: true
494
+ });
495
+ last = close;
496
+ if (close.ok)
497
+ return close;
498
+ }
499
+ return last;
500
+ }
501
+ function zellijPaneIdCandidates(paneId) {
502
+ const raw = String(paneId || '').trim();
503
+ const numeric = raw.replace(/^terminal_/, '');
504
+ const parsed = Number.parseInt(numeric, 10);
505
+ return [...new Set([
506
+ raw,
507
+ numeric,
508
+ Number.isFinite(parsed) ? String(parsed) : '',
509
+ Number.isFinite(parsed) ? `terminal_${parsed}` : ''
510
+ ].filter(Boolean))];
511
+ }
377
512
  function sleep(ms) {
378
513
  return new Promise((resolve) => setTimeout(resolve, ms));
379
514
  }