sneakoscope 3.0.4 → 3.1.1
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 +1 -1
- 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/cli/command-registry.js +1 -0
- package/dist/cli/context7-command.js +29 -5
- package/dist/cli/install-helpers.js +15 -7
- package/dist/commands/zellij-slot-column-anchor.js +3 -1
- package/dist/commands/zellij-slot-pane.js +19 -2
- package/dist/core/agents/agent-janitor.js +10 -1
- package/dist/core/agents/agent-orchestrator.js +1 -0
- package/dist/core/agents/agent-runner-ollama.js +11 -4
- package/dist/core/agents/native-cli-session-swarm.js +69 -9
- package/dist/core/agents/runtime-proof-summary.js +4 -0
- package/dist/core/codex-control/codex-task-runner.js +9 -0
- package/dist/core/commands/goal-command.js +19 -1
- package/dist/core/commands/loop-command.js +176 -0
- package/dist/core/commands/naruto-command.js +26 -17
- package/dist/core/commands/team-command.js +1 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +6 -1
- package/dist/core/locks/file-lock.js +88 -0
- package/dist/core/loops/goal-to-loop-compat.js +23 -0
- package/dist/core/loops/loop-artifacts.js +72 -0
- package/dist/core/loops/loop-checkpoint.js +22 -0
- package/dist/core/loops/loop-decomposer.js +56 -0
- package/dist/core/loops/loop-finalizer.js +54 -0
- package/dist/core/loops/loop-gate-ladder.js +16 -0
- package/dist/core/loops/loop-gate-registry.js +96 -0
- package/dist/core/loops/loop-gate-runner.js +177 -0
- package/dist/core/loops/loop-gate-selector.js +52 -0
- package/dist/core/loops/loop-gpt-final-arbiter.js +61 -0
- package/dist/core/loops/loop-integration-merge.js +75 -0
- package/dist/core/loops/loop-iteration-runner.js +2 -0
- package/dist/core/loops/loop-lease.js +91 -0
- package/dist/core/loops/loop-observability.js +19 -0
- package/dist/core/loops/loop-owner-inference.js +57 -0
- package/dist/core/loops/loop-owner-ledger.js +2 -0
- package/dist/core/loops/loop-planner.js +170 -0
- package/dist/core/loops/loop-proof-summary.js +10 -0
- package/dist/core/loops/loop-proof.js +2 -0
- package/dist/core/loops/loop-risk-classifier.js +42 -0
- package/dist/core/loops/loop-runtime-control.js +25 -0
- package/dist/core/loops/loop-runtime.js +314 -0
- package/dist/core/loops/loop-scheduler.js +69 -0
- package/dist/core/loops/loop-schema.js +63 -0
- package/dist/core/loops/loop-state.js +61 -0
- package/dist/core/loops/loop-worker-prompts.js +43 -0
- package/dist/core/loops/loop-worker-runtime.js +275 -0
- package/dist/core/loops/loop-worktree-runtime.js +92 -0
- package/dist/core/naruto/naruto-finalizer.js +7 -2
- package/dist/core/naruto/naruto-loop-mesh.js +39 -0
- package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
- package/dist/core/pipeline-internals/runtime-core.js +82 -2
- package/dist/core/proof/proof-schema.js +6 -0
- package/dist/core/proof/proof-writer.js +5 -2
- package/dist/core/proof/root-cause-policy.js +70 -0
- package/dist/core/proof/route-adapter.js +18 -1
- package/dist/core/proof/route-proof-gate.js +4 -0
- package/dist/core/release/release-gate-batch-runner.js +56 -10
- package/dist/core/release/release-gate-cache-v2.js +18 -3
- package/dist/core/release/release-gate-dag.js +65 -17
- package/dist/core/release/release-gate-node.js +2 -1
- package/dist/core/release/release-gate-resource-governor.js +27 -6
- package/dist/core/skills/core-skill-meta-update.js +24 -0
- package/dist/core/skills/core-skill-reflection.js +94 -0
- package/dist/core/skills/core-skill-trainer.js +103 -0
- package/dist/core/trust-kernel/completion-contract.js +4 -0
- package/dist/core/trust-kernel/route-contract.js +4 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-right-column-manager.js +13 -2
- package/dist/core/zellij/zellij-slot-column-anchor.js +45 -5
- package/dist/core/zellij/zellij-slot-pane-renderer.js +37 -10
- package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
- package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
- package/dist/scripts/loop-directive-check-lib.js +388 -0
- package/dist/scripts/loop-worker-fixture-child.js +53 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
- package/package.json +38 -3
- package/schemas/loops/loop-node.schema.json +21 -0
- package/schemas/loops/loop-plan.schema.json +21 -0
- package/schemas/loops/loop-proof.schema.json +20 -0
- package/schemas/loops/loop-state.schema.json +19 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
|
-
import { appendJsonlBounded, ensureDir, nowIso, readJson, readText } from '../fsx.js';
|
|
3
|
+
import { appendJsonlBounded, ensureDir, nowIso, readJson, readText, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
import { withFileLock } from '../locks/file-lock.js';
|
|
4
5
|
export const ZELLIJ_SLOT_TELEMETRY_EVENT_SCHEMA = 'sks.zellij-slot-telemetry-event.v1';
|
|
5
6
|
export const ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA = 'sks.zellij-slot-telemetry-snapshot.v1';
|
|
6
7
|
const telemetrySnapshotCache = new Map();
|
|
@@ -71,7 +72,7 @@ export function mergeTelemetrySnapshots(base, overlay, opts = {}) {
|
|
|
71
72
|
const slots = { ...(base.slots || {}) };
|
|
72
73
|
for (const [key, row] of Object.entries(overlay.slots || {})) {
|
|
73
74
|
const existing = slots[key];
|
|
74
|
-
slots[key] = !existing
|
|
75
|
+
slots[key] = !existing ? row : newerSlotTelemetry(existing, row);
|
|
75
76
|
}
|
|
76
77
|
const baseTs = Date.parse(String(base.updated_at || '')) || 0;
|
|
77
78
|
const overlayTs = Date.parse(String(overlay.updated_at || '')) || 0;
|
|
@@ -88,6 +89,21 @@ function telemetryTsMs(value) {
|
|
|
88
89
|
const parsed = Date.parse(String(value || ''));
|
|
89
90
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
90
91
|
}
|
|
92
|
+
function newerSlotTelemetry(current, incoming) {
|
|
93
|
+
const currentTs = telemetryTsMs(current.latest_ts);
|
|
94
|
+
const incomingTs = telemetryTsMs(incoming.latest_ts);
|
|
95
|
+
const currentTerminal = isTelemetryTerminalStatus(current.status);
|
|
96
|
+
const incomingTerminal = isTelemetryTerminalStatus(incoming.status);
|
|
97
|
+
if (currentTerminal && !incomingTerminal)
|
|
98
|
+
return current;
|
|
99
|
+
if (!currentTerminal && incomingTerminal)
|
|
100
|
+
return incoming;
|
|
101
|
+
if (incomingTs > currentTs)
|
|
102
|
+
return incoming;
|
|
103
|
+
if (incomingTs < currentTs)
|
|
104
|
+
return current;
|
|
105
|
+
return incoming;
|
|
106
|
+
}
|
|
91
107
|
async function statTelemetryFile(file) {
|
|
92
108
|
try {
|
|
93
109
|
const st = await fsp.stat(file);
|
|
@@ -99,17 +115,17 @@ async function statTelemetryFile(file) {
|
|
|
99
115
|
}
|
|
100
116
|
export function applyTelemetryEventToSnapshot(snapshot, event) {
|
|
101
117
|
const key = slotTelemetryKey(event.slot_id || event.worker_id, event.generation_index);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
const previous = snapshot.slots?.[key];
|
|
119
|
+
const nextSlot = mergeSlotTelemetry(previous, event);
|
|
120
|
+
const slots = snapshot.slots || {};
|
|
121
|
+
slots[key] = nextSlot;
|
|
106
122
|
return {
|
|
107
123
|
schema: ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA,
|
|
108
124
|
mission_id: event.mission_id || snapshot.mission_id,
|
|
109
125
|
updated_at: nowIso(),
|
|
110
126
|
flush_count: snapshot.flush_count || 0,
|
|
111
127
|
slots,
|
|
112
|
-
counts: countSlotTelemetry(slots)
|
|
128
|
+
counts: adjustSlotTelemetryCounts(snapshot.counts || countSlotTelemetry(snapshot.slots || {}), previous, nextSlot)
|
|
113
129
|
};
|
|
114
130
|
}
|
|
115
131
|
export async function rebuildZellijSlotTelemetrySnapshot(root, missionId) {
|
|
@@ -176,11 +192,19 @@ function normalizeTelemetryEvent(event) {
|
|
|
176
192
|
};
|
|
177
193
|
}
|
|
178
194
|
function mergeSlotTelemetry(previous, event) {
|
|
195
|
+
const previousTs = telemetryTsMs(previous?.latest_ts);
|
|
196
|
+
const eventTs = telemetryTsMs(event.ts);
|
|
197
|
+
const previousTerminal = isTelemetryTerminalStatus(previous?.status);
|
|
198
|
+
const eventTerminal = isTelemetryTerminalStatus(event.status);
|
|
199
|
+
const terminalRegression = Boolean(previous && previousTerminal && !eventTerminal);
|
|
200
|
+
const stale = Boolean(previous && (terminalRegression
|
|
201
|
+
|| (!eventTerminal && eventTs < previousTs)
|
|
202
|
+
|| (previousTerminal && eventTerminal && eventTs < previousTs)));
|
|
179
203
|
return {
|
|
180
|
-
slot_id: event.slot_id,
|
|
181
|
-
generation_index: event.generation_index,
|
|
182
|
-
worker_id: event.worker_id,
|
|
183
|
-
status: event.status,
|
|
204
|
+
slot_id: stale ? previous.slot_id : event.slot_id,
|
|
205
|
+
generation_index: stale ? previous.generation_index : event.generation_index,
|
|
206
|
+
worker_id: stale ? previous.worker_id : event.worker_id,
|
|
207
|
+
status: stale ? previous.status : event.status,
|
|
184
208
|
role: event.role || previous?.role || 'worker',
|
|
185
209
|
backend: event.backend || previous?.backend || 'unknown',
|
|
186
210
|
provider: event.provider || previous?.provider || 'unknown',
|
|
@@ -188,34 +212,55 @@ function mergeSlotTelemetry(previous, event) {
|
|
|
188
212
|
worktree_id: event.worktree_id ?? previous?.worktree_id ?? null,
|
|
189
213
|
worktree_path: event.worktree_path ?? previous?.worktree_path ?? null,
|
|
190
214
|
task_title: event.task_title || previous?.task_title || 'waiting for task',
|
|
191
|
-
current_file: event.current_file ?? previous?.current_file ?? null,
|
|
192
|
-
latest_event_type: event.event_type,
|
|
193
|
-
latest_ts: event.ts,
|
|
194
|
-
progress: event.progress || previous?.progress || null,
|
|
215
|
+
current_file: stale ? previous.current_file ?? (terminalRegression ? null : event.current_file ?? null) : event.current_file ?? previous?.current_file ?? null,
|
|
216
|
+
latest_event_type: stale ? previous.latest_event_type : event.event_type,
|
|
217
|
+
latest_ts: stale ? previous.latest_ts : event.ts,
|
|
218
|
+
progress: stale ? previous.progress || (terminalRegression ? null : event.progress || null) : event.progress || previous?.progress || null,
|
|
195
219
|
artifact_paths: unique([...(previous?.artifact_paths || []), ...(event.artifact_paths || [])]),
|
|
196
220
|
blockers: unique([...(previous?.blockers || []), ...(event.blockers || [])]),
|
|
197
|
-
log_tail: event.log_tail || previous?.log_tail || ''
|
|
221
|
+
log_tail: stale ? previous.log_tail || (terminalRegression ? '' : event.log_tail || '') : event.log_tail || previous?.log_tail || ''
|
|
198
222
|
};
|
|
199
223
|
}
|
|
200
224
|
function countSlotTelemetry(slots) {
|
|
201
225
|
const counts = { queued: 0, running: 0, verifying: 0, completed: 0, failed: 0, headless: 0 };
|
|
202
226
|
for (const row of Object.values(slots)) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
counts
|
|
227
|
+
const bucket = slotTelemetryCountBucket(row.status);
|
|
228
|
+
counts[bucket] += 1;
|
|
229
|
+
}
|
|
230
|
+
return counts;
|
|
231
|
+
}
|
|
232
|
+
function adjustSlotTelemetryCounts(current, previous, next) {
|
|
233
|
+
const counts = { ...current };
|
|
234
|
+
const nextBucket = slotTelemetryCountBucket(next.status);
|
|
235
|
+
if (previous) {
|
|
236
|
+
const previousBucket = slotTelemetryCountBucket(previous.status);
|
|
237
|
+
if (previousBucket !== nextBucket) {
|
|
238
|
+
counts[previousBucket] = Math.max(0, Number(counts[previousBucket] || 0) - 1);
|
|
239
|
+
counts[nextBucket] = Number(counts[nextBucket] || 0) + 1;
|
|
240
|
+
}
|
|
241
|
+
return counts;
|
|
216
242
|
}
|
|
243
|
+
counts[nextBucket] = Number(counts[nextBucket] || 0) + 1;
|
|
217
244
|
return counts;
|
|
218
245
|
}
|
|
246
|
+
function slotTelemetryCountBucket(status) {
|
|
247
|
+
const normalized = normalizeStatus(status);
|
|
248
|
+
if (normalized === 'queued' || normalized === 'launching')
|
|
249
|
+
return 'queued';
|
|
250
|
+
if (normalized === 'verifying')
|
|
251
|
+
return 'verifying';
|
|
252
|
+
if (normalized === 'completed' || normalized === 'drained')
|
|
253
|
+
return 'completed';
|
|
254
|
+
if (normalized === 'failed')
|
|
255
|
+
return 'failed';
|
|
256
|
+
if (normalized === 'headless')
|
|
257
|
+
return 'headless';
|
|
258
|
+
return 'running';
|
|
259
|
+
}
|
|
260
|
+
function isTelemetryTerminalStatus(status) {
|
|
261
|
+
const normalized = normalizeStatus(status);
|
|
262
|
+
return normalized === 'completed' || normalized === 'failed' || normalized === 'drained';
|
|
263
|
+
}
|
|
219
264
|
function normalizeStatus(value) {
|
|
220
265
|
const text = String(value || '').toLowerCase();
|
|
221
266
|
if (text === 'closed' || text === 'done' || text === 'passed')
|
|
@@ -258,20 +303,26 @@ function tail(value, max) {
|
|
|
258
303
|
}
|
|
259
304
|
async function writeTelemetrySnapshotFast(file, snapshot) {
|
|
260
305
|
await ensureDir(path.dirname(file));
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
306
|
+
await withFileLock({
|
|
307
|
+
lockPath: `${file}.lock`,
|
|
308
|
+
timeoutMs: 30000,
|
|
309
|
+
staleMs: 2 * 60 * 1000
|
|
310
|
+
}, async () => {
|
|
311
|
+
const flushCount = Number(telemetrySnapshotFlushCounts.get(file) || 0) + 1;
|
|
312
|
+
telemetrySnapshotFlushCounts.set(file, flushCount);
|
|
313
|
+
telemetrySnapshotLastFlushMs.set(file, Date.now());
|
|
314
|
+
// Multiple processes (orchestrator + worker children) flush this file.
|
|
315
|
+
// Serialize the read/merge/write critical section so a slower writer
|
|
316
|
+
// cannot overwrite a newer slot observed by a different process.
|
|
317
|
+
const disk = await readJson(file, null);
|
|
318
|
+
const merged = disk?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA ? mergeTelemetrySnapshots(disk, snapshot) : snapshot;
|
|
319
|
+
const next = { ...merged, flush_count: Math.max(flushCount, Number(merged.flush_count || 0)) };
|
|
320
|
+
telemetrySnapshotCache.set(file, next);
|
|
321
|
+
await writeTextAtomic(file, `${JSON.stringify(next)}\n`);
|
|
322
|
+
const stat = await statTelemetryFile(file);
|
|
323
|
+
if (stat)
|
|
324
|
+
telemetrySnapshotDiskStat.set(file, stat);
|
|
325
|
+
});
|
|
275
326
|
}
|
|
276
327
|
function shouldFlushTelemetrySnapshot(file, event) {
|
|
277
328
|
const next = (telemetrySnapshotWriteCounts.get(file) || 0) + 1;
|
|
@@ -289,7 +340,8 @@ function shouldFlushTelemetrySnapshot(file, event) {
|
|
|
289
340
|
|| event.event_type === 'worker_completed'
|
|
290
341
|
|| event.event_type === 'worker_failed'
|
|
291
342
|
|| event.status === 'completed'
|
|
292
|
-
|| event.status === 'failed'
|
|
343
|
+
|| event.status === 'failed'
|
|
344
|
+
|| event.status === 'drained';
|
|
293
345
|
const should = next === 1
|
|
294
346
|
|| important
|
|
295
347
|
|| now - last >= flushMs
|
|
@@ -14,12 +14,49 @@ export const ZELLIJ_PANE_CREATION_LOCK_METRICS_SCHEMA = 'sks.zellij-pane-creatio
|
|
|
14
14
|
export function buildWorkerPaneName(slotId, generationIndex) {
|
|
15
15
|
return `${slotId}/gen-${Math.max(1, Math.floor(Number(generationIndex) || 1))}`;
|
|
16
16
|
}
|
|
17
|
-
export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status, worktree) {
|
|
17
|
+
export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status, worktree, taskTitle) {
|
|
18
|
+
// When a task label is available, the pane title is JUST the label + status:
|
|
19
|
+
// users identify panes by what they are doing, not by slot ids. Slot/gen,
|
|
20
|
+
// backend, provider, and worktree details stay inside the pane body and the
|
|
21
|
+
// SLOTS anchor rows. The verbose format remains the fallback for callers
|
|
22
|
+
// that have no task context.
|
|
23
|
+
const task = shortWorkerTaskLabel(taskTitle);
|
|
24
|
+
if (task)
|
|
25
|
+
return `${task} · ${workerBackendTag(backend, context?.provider)} · ${status || 'launching'}`;
|
|
18
26
|
const base = buildWorkerPaneName(slotId, generationIndex);
|
|
19
27
|
const normalized = normalizePaneProviderContext(context, serviceTier);
|
|
20
28
|
const wt = worktree ? ` · WT:${worktree.id} · branch:${worktree.branch}` : '';
|
|
21
29
|
return `${base}${wt} · ${backend || 'codex-sdk'} · ${providerPaneLabel(normalized)} · ${status || 'launching'}`;
|
|
22
30
|
}
|
|
31
|
+
// Local-LLM workers must be visually distinguishable from GPT/codex workers:
|
|
32
|
+
// only trivial long-running edits belong on local models, while web research
|
|
33
|
+
// and code review must run on GPT. The tag makes a misrouted task obvious.
|
|
34
|
+
export function workerBackendTag(backend, provider) {
|
|
35
|
+
const text = `${String(backend || '')} ${String(provider || '')}`.toLowerCase();
|
|
36
|
+
if (/ollama|local/.test(text))
|
|
37
|
+
return 'LOCAL';
|
|
38
|
+
if (/fake|fixture/.test(text))
|
|
39
|
+
return 'fixture';
|
|
40
|
+
return 'GPT';
|
|
41
|
+
}
|
|
42
|
+
// Lead the pane title with a 1-3 word task label so users can tell WHAT each
|
|
43
|
+
// slot is doing at a glance instead of an anonymous "slot-002/gen-1".
|
|
44
|
+
export function shortWorkerTaskLabel(value, maxChars = 24) {
|
|
45
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
46
|
+
if (!text)
|
|
47
|
+
return '';
|
|
48
|
+
const words = text.split(' ');
|
|
49
|
+
let label = '';
|
|
50
|
+
for (const word of words.slice(0, 3)) {
|
|
51
|
+
const next = label ? `${label} ${word}` : word;
|
|
52
|
+
if (next.length > maxChars)
|
|
53
|
+
break;
|
|
54
|
+
label = next;
|
|
55
|
+
}
|
|
56
|
+
if (!label)
|
|
57
|
+
label = text.slice(0, maxChars);
|
|
58
|
+
return label;
|
|
59
|
+
}
|
|
23
60
|
export function isRealZellijWorkerPaneIdSource(value) {
|
|
24
61
|
return value === 'zellij_worker_new_pane_stdout' || value === 'zellij_worker_list_panes';
|
|
25
62
|
}
|
|
@@ -162,7 +199,7 @@ export async function openWorkerPane(input) {
|
|
|
162
199
|
});
|
|
163
200
|
return record;
|
|
164
201
|
}
|
|
165
|
-
const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running', input.worktree || null);
|
|
202
|
+
const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running', input.worktree || null, input.taskTitle || null);
|
|
166
203
|
// CRITICAL: serialize anchor + worker pane creation per session. Workers
|
|
167
204
|
// launch concurrently, and without this lock every worker raced past the
|
|
168
205
|
// "does the SLOTS anchor exist yet?" check and created its OWN anchor with
|
|
@@ -695,7 +732,8 @@ async function withZellijPaneCreationLock(input, fn) {
|
|
|
695
732
|
const current = new Promise((resolve) => {
|
|
696
733
|
release = resolve;
|
|
697
734
|
});
|
|
698
|
-
|
|
735
|
+
const chained = previous.then(() => current, () => current);
|
|
736
|
+
zellijPaneCreationLocks.set(key, chained);
|
|
699
737
|
await previous.catch(() => undefined);
|
|
700
738
|
const acquiredAt = nowIso();
|
|
701
739
|
const acquiredMs = Date.now();
|
|
@@ -741,7 +779,7 @@ async function withZellijPaneCreationLock(input, fn) {
|
|
|
741
779
|
meta: { wait_ms: metrics.wait_ms, held_ms: metrics.held_ms }
|
|
742
780
|
}).catch(() => undefined);
|
|
743
781
|
release();
|
|
744
|
-
if (zellijPaneCreationLocks.get(key) ===
|
|
782
|
+
if (zellijPaneCreationLocks.get(key) === chained)
|
|
745
783
|
zellijPaneCreationLocks.delete(key);
|
|
746
784
|
}
|
|
747
785
|
}
|