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.
- package/README.md +8 -4
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +33 -8
- package/dist/cli/command-registry.js +1 -0
- package/dist/commands/doctor.js +18 -1
- package/dist/commands/zellij-slot-pane.js +26 -0
- package/dist/commands/zellij.js +144 -1
- package/dist/core/agents/agent-orchestrator.js +202 -9
- package/dist/core/agents/agent-role-config.js +92 -0
- package/dist/core/agents/native-cli-session-swarm.js +230 -48
- package/dist/core/commands/mad-sks-command.js +17 -26
- package/dist/core/commands/naruto-command.js +155 -37
- package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +4 -0
- package/dist/core/init.js +1 -0
- package/dist/core/naruto/naruto-active-pool.js +141 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +17 -2
- package/dist/core/naruto/naruto-real-worker-child.js +35 -0
- package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
- package/dist/core/naruto/naruto-work-graph.js +2 -1
- package/dist/core/release/release-gate-cache-v2.js +58 -4
- package/dist/core/release/release-gate-dag.js +36 -25
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-dashboard-renderer.js +22 -6
- package/dist/core/zellij/zellij-launcher.js +3 -3
- package/dist/core/zellij/zellij-layout-builder.js +1 -1
- package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
- package/dist/core/zellij/zellij-right-column-manager.js +304 -0
- package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
- package/dist/core/zellij/zellij-ui-mode.js +16 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +152 -17
- package/dist/scripts/agent-role-config-repair-check.js +33 -0
- package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
- package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
- package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
- package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
- package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
- package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
- package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
- package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
- package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
- package/dist/scripts/naruto-extreme-parallelism-real-check.js +42 -0
- package/dist/scripts/naruto-real-active-pool-check.js +39 -0
- package/dist/scripts/naruto-real-active-pool-runtime-check.js +53 -0
- package/dist/scripts/naruto-work-graph-check.js +1 -1
- package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +48 -0
- package/dist/scripts/product-design-auto-install-check.js +3 -3
- package/dist/scripts/product-design-plugin-routing-check.js +3 -3
- package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
- package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
- package/dist/scripts/release-check-dynamic-execute.js +27 -1
- package/dist/scripts/release-check-dynamic.js +38 -11
- package/dist/scripts/release-check-stamp.js +7 -2
- package/dist/scripts/release-dag-full-coverage-check.js +35 -0
- package/dist/scripts/release-dynamic-performance-check.js +31 -1
- package/dist/scripts/release-gate-existence-audit.js +29 -33
- package/dist/scripts/release-parallel-speed-budget-check.js +67 -13
- package/dist/scripts/release-readiness-report.js +14 -3
- package/dist/scripts/zellij-dashboard-pane-check.js +6 -4
- package/dist/scripts/zellij-developer-controls-check.js +20 -0
- package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
- package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
- package/dist/scripts/zellij-right-column-geometry-proof.js +162 -0
- package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
- package/dist/scripts/zellij-right-column-manager-check.js +22 -0
- package/dist/scripts/zellij-slot-only-ui-check.js +22 -0
- package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
- package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
- package/package.json +23 -5
- 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(
|
|
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 ?
|
|
150
|
+
let directionApplied = launch?.ok ? directionRequested : 'not_applied';
|
|
97
151
|
if (createSession.ok && launch && !launch.ok) {
|
|
98
|
-
const
|
|
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: {
|
|
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:
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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: [
|
|
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
|
}
|