sneakoscope 1.21.3 → 1.21.4

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 CHANGED
@@ -16,7 +16,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
16
16
 
17
17
  ## Current Release
18
18
 
19
- SKS **1.21.3** restores macOS native text copy in SKS-launched Zellij sessions: the generated clipboard config and launch options now set `mouse_mode false` alongside `copy_command pbcopy`, so drag-select + `Cmd+C` reaches the terminal/system clipboard instead of being intercepted by Zellij. It also keeps Team/Naruto native agents individually visible in the right-side Zellij UI by separating runtime concurrency (`target_active_slots`) from the visual lane count (`visual_lane_count`). Direct `npm publish` now repairs a stale release-check stamp by running the full authoritative `npm run release:check` once before continuing, instead of failing early in `prepublish:fast-check` after a version bump. Explicit `sks fast-mode on` / `$Fast-On` now repairs Codex's Fast mode UI/default profile as well as the project-local SKS preference. It carries forward the 1.21.2 Zellij launch fix, 1.21.1 launch-speed fix, and Codex legacy-profile cleanup.
19
+ SKS **1.21.4** makes Fast mode state and Naruto clone activity directly visible in SKS Zellij lanes. The lane renderer now resolves the project-local `sks fast-mode on|off` preference even before worker scheduler artifacts arrive, and Naruto launches the right-side Zellij lane stack before clone scheduling starts so each clone slot can show live activity. Naruto's host-capacity model also no longer collapses `codex-exec` concurrency to one slot on capable Macs just because `freemem` is low; use `--concurrency` / `--target-active-slots` or `SKS_NARUTO_MAX_CONCURRENCY` for explicit operator control. SKS-launched interactive Codex panes also use `--no-alt-screen` by default so Mac trackpad/wheel gestures scroll the terminal conversation history instead of the prompt textarea/history; set `SKS_ZELLIJ_CODEX_ALT_SCREEN=1` before launch to opt back into alternate-screen mode. It carries forward the 1.21.3 Zellij clipboard, visual lane count, direct-publish stamp repair, and Codex Fast mode repair fixes.
20
20
 
21
21
  SKS **1.20.4** is a targeted `sks --mad` / codex-lb Zellij usability patch: when a background MAD Zellij session launches successfully, SKS now prints the exact `Attach with: ZELLIJ_SOCKET_DIR=... zellij attach ...` command so operators can enter the fresh session without manually reconstructing the socket namespace.
22
22
 
@@ -341,7 +341,7 @@ sks team open-zellij latest
341
341
  sks team attach-zellij latest
342
342
  ```
343
343
 
344
- Interactive SKS sessions use Zellij layouts. By default SKS launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, and the selected `model_reasoning_effort`. SKS always forces the model to `gpt-5.5`; `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove that model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort. Use `sks --mad --workspace <name>` for an explicit MAD session and `sks help` for CLI help.
344
+ Interactive SKS sessions use Zellij layouts. By default SKS launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, the selected `model_reasoning_effort`, and `--no-alt-screen` for Zellij-backed interactive panes so terminal scrollback captures the conversation transcript. SKS always forces the model to `gpt-5.5`; `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove that model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort, and `SKS_ZELLIJ_CODEX_ALT_SCREEN=1` restores Codex's alternate-screen UI for the next launch. Use `sks --mad --workspace <name>` for an explicit MAD session and `sks help` for CLI help.
345
345
 
346
346
  Before opening the interactive runtime, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens the runtime with the updated Codex CLI.
347
347
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.21.3"
79
+ version = "1.21.4"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "1.21.3"
3
+ version = "1.21.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 1.21.3"),
7
+ Some("--version") => println!("sks-rs 1.21.4"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "1.21.3",
5
- "source_digest": "fdee4bb2a16e87fbf85b069519a8c545b16b2d6fe64ad9da30b5e765ee555887",
4
+ "package_version": "1.21.4",
5
+ "source_digest": "70780332db02a19044731adecdb06abcf7d9efdc8b128627be712ca33b3c6881",
6
6
  "source_file_count": 1755,
7
- "built_at_source_time": 1780312892629
7
+ "built_at_source_time": 1780318491834
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '1.21.3';
2
+ const FAST_PACKAGE_VERSION = '1.21.4';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.21.3",
4
- "package_version": "1.21.3",
3
+ "version": "1.21.4",
4
+ "package_version": "1.21.4",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
7
  "compiled_file_count": 1022,
8
8
  "compiled_js_count": 511,
9
9
  "compiled_dts_count": 511,
10
- "source_digest": "fdee4bb2a16e87fbf85b069519a8c545b16b2d6fe64ad9da30b5e765ee555887",
10
+ "source_digest": "70780332db02a19044731adecdb06abcf7d9efdc8b128627be712ca33b3c6881",
11
11
  "source_file_count": 1755,
12
12
  "source_files_hash": "e6487058a1a0894c6b8a629b64319d45be1ad9a0658ab422a39969c1e39ad7e3",
13
13
  "source_list_hash": "e6487058a1a0894c6b8a629b64319d45be1ad9a0658ab422a39969c1e39ad7e3",
@@ -1,13 +1,18 @@
1
1
  import type { AgentPersona, AgentRosterEntry } from './agent-schema.js';
2
2
  export declare function systemSafeNarutoConcurrency(opts?: {
3
3
  backend?: string;
4
+ cores?: number;
5
+ freeBytes?: number;
6
+ totalBytes?: number;
4
7
  }): {
5
8
  cap: number;
6
9
  cores: number;
7
10
  free_gb: number;
11
+ total_gb: number;
8
12
  backend: string;
9
13
  heavy: boolean;
10
14
  override_applied: boolean;
15
+ memory_model: string;
11
16
  };
12
17
  export declare function normalizeAgentCount(value: unknown, fallback?: number, maxAgentCount?: number): number;
13
18
  export declare function normalizeAgentConcurrency(value: unknown, agents: number, maxAgentCount?: number): number;
@@ -7,22 +7,35 @@ import { buildAgentEffortPolicy, decideAgentEffort, decideNarutoCloneEffort } fr
7
7
  // host can safely sustain — derived from CPU cores and free memory, heavier-bounded for
8
8
  // real child-process backends (codex-exec/zellij/process) than for in-process (fake).
9
9
  export function systemSafeNarutoConcurrency(opts = {}) {
10
- const cores = Math.max(1, Number(os.cpus()?.length) || 4);
10
+ const cores = Math.max(1, Number(opts.cores ?? os.cpus()?.length) || 4);
11
11
  let freeBytes = 2 * 1024 * 1024 * 1024;
12
+ let totalBytes = 8 * 1024 * 1024 * 1024;
12
13
  try {
13
- freeBytes = os.freemem() || freeBytes;
14
+ freeBytes = Number(opts.freeBytes ?? os.freemem()) || freeBytes;
15
+ }
16
+ catch { /* keep fallback */ }
17
+ try {
18
+ totalBytes = Number(opts.totalBytes ?? os.totalmem()) || totalBytes;
14
19
  }
15
20
  catch { /* keep fallback */ }
16
21
  const freeGb = freeBytes / (1024 * 1024 * 1024);
22
+ const totalGb = totalBytes / (1024 * 1024 * 1024);
17
23
  const backend = String(opts.backend || 'codex-exec');
18
24
  const heavy = backend === 'codex-exec' || backend === 'zellij' || backend === 'process';
19
25
  let cap;
20
26
  if (heavy) {
21
- // Real codex children are heavy (a model call + process). Leave a core free and budget
22
- // ~0.6 GB per concurrent worker; clamp to a sane ceiling.
27
+ // Real codex children are heavier than fake workers, but macOS can report
28
+ // very low freemem while reclaimable memory is still available. Use a
29
+ // conservative total-memory floor so Naruto keeps meaningful parallelism
30
+ // instead of collapsing to one slot on otherwise capable machines.
23
31
  const byCpu = Math.max(1, cores - 1);
24
- const byMem = Math.max(1, Math.floor(freeGb / 0.6));
25
- cap = Math.min(byCpu, byMem, 16);
32
+ const gbPerWorker = positiveEnvNumber('SKS_NARUTO_GB_PER_WORKER', 0.6);
33
+ const reclaimableFloorGb = totalGb >= 16 ? 6 : totalGb >= 8 ? 3 : totalGb >= 4 ? 1.5 : freeGb;
34
+ const budgetGb = Math.max(freeGb, reclaimableFloorGb);
35
+ const byMem = Math.max(1, Math.floor(budgetGb / gbPerWorker));
36
+ const minParallelDefault = totalGb >= 16 ? 8 : totalGb >= 8 ? 4 : totalGb >= 4 ? 2 : 1;
37
+ const minParallel = Math.min(byCpu, Math.floor(positiveEnvNumber('SKS_NARUTO_MIN_CONCURRENCY', minParallelDefault)));
38
+ cap = Math.min(byCpu, Math.max(byMem, minParallel), 16);
26
39
  }
27
40
  else {
28
41
  // In-process / light workers can pack tighter.
@@ -32,7 +45,20 @@ export function systemSafeNarutoConcurrency(opts = {}) {
32
45
  if (Number.isFinite(override) && override >= 1)
33
46
  cap = Math.min(Math.floor(override), MAX_NARUTO_AGENT_COUNT);
34
47
  cap = Math.max(1, Math.min(cap, MAX_NARUTO_AGENT_COUNT));
35
- return { cap, cores, free_gb: Math.round(freeGb * 10) / 10, backend, heavy, override_applied: Number.isFinite(override) && override >= 1 };
48
+ return {
49
+ cap,
50
+ cores,
51
+ free_gb: Math.round(freeGb * 10) / 10,
52
+ total_gb: Math.round(totalGb * 10) / 10,
53
+ backend,
54
+ heavy,
55
+ override_applied: Number.isFinite(override) && override >= 1,
56
+ memory_model: heavy ? 'free_or_reclaimable_floor' : 'light_worker_cpu_bound'
57
+ };
58
+ }
59
+ function positiveEnvNumber(name, fallback) {
60
+ const parsed = Number(process.env[name]);
61
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
36
62
  }
37
63
  function resolveMaxAgentCount(value) {
38
64
  const parsed = Number(value);
@@ -4,7 +4,7 @@ export const ZELLIJ_RIGHT_LANE_LAYOUT_SCHEMA = 'sks.agent-zellij-right-lane-layo
4
4
  export const ZELLIJ_RIGHT_LANES_SCHEMA = 'sks.agent-zellij-right-lanes.v1';
5
5
  export function buildZellijRightLaneCockpit(input = {}) {
6
6
  const agents = input.slots || input.agents || [];
7
- const maxVisible = input.maxVisibleLanes || 20;
7
+ const maxVisible = input.maxVisibleLanes || Math.max(agents.length, 1);
8
8
  const lanes = agents.map((agent, index) => ({
9
9
  lane_index: index + 1,
10
10
  slot_id: String(agent.slot_id || agent.id || agent.agent_id || `slot-${String(index + 1).padStart(3, '0')}`),
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { findLatestMission, loadMission } from '../mission.js';
2
+ import { createMission, findLatestMission, loadMission } from '../mission.js';
3
3
  import { readJson, sksRoot } from '../fsx.js';
4
4
  import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
5
5
  import { buildNarutoCloneRoster, systemSafeNarutoConcurrency } from '../agents/agent-roster.js';
@@ -30,10 +30,37 @@ async function narutoRun(parsed) {
30
30
  maxAgentCount: MAX_NARUTO_AGENT_COUNT
31
31
  });
32
32
  // The clone roster is the full work fan-out; live concurrency is throttled to a
33
- // system-safe number so naruto never spawns the whole count at once.
33
+ // system-safe number so naruto never spawns the whole count at once unless an
34
+ // explicit operator override asks for a higher target.
34
35
  const safe = systemSafeNarutoConcurrency({ backend: parsed.backend });
35
- const activeSlots = Math.max(1, Math.min(roster.agent_count, safe.cap));
36
+ const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || safe.cap));
37
+ const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
38
+ const ledgerRoot = path.join(mission.dir, 'agents');
39
+ let liveZellij = null;
40
+ if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
41
+ liveZellij = await launchZellijLayout({
42
+ root,
43
+ missionId: mission.id,
44
+ ledgerRoot,
45
+ kind: 'naruto',
46
+ slotCount: roster.agent_count,
47
+ dryRun: false,
48
+ attach: false
49
+ });
50
+ if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
51
+ console.log('Zellij: prepared ' + roster.agent_count + ' live clone lane(s) in ' + liveZellij.session_name + '. Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
52
+ if (parsed.attach)
53
+ attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
54
+ }
55
+ else if (liveZellij?.ok) {
56
+ console.log('Zellij: optional live panes unavailable (' + ((liveZellij.warnings || []).join('; ') || liveZellij.capability?.status || 'unknown') + ')');
57
+ }
58
+ else {
59
+ console.log('Zellij: blocked (' + Array.from(new Set(liveZellij?.blockers || [])).join('; ') + ')');
60
+ }
61
+ }
36
62
  const result = await runNativeAgentOrchestrator({
63
+ missionId: mission.id,
37
64
  prompt: parsed.prompt,
38
65
  route: NARUTO_ROUTE,
39
66
  routeCommand: 'sks naruto run',
@@ -76,22 +103,7 @@ async function narutoRun(parsed) {
76
103
  run: result,
77
104
  zellij: null
78
105
  };
79
- if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
80
- const ledgerRoot = result.ledger_root
81
- ? path.join(root, result.ledger_root)
82
- : path.join(root, '.sneakoscope', 'missions', result.mission_id, 'agents');
83
- summary.zellij = await launchZellijLayout({
84
- root,
85
- missionId: result.mission_id,
86
- ledgerRoot,
87
- kind: 'naruto',
88
- slotCount: summary.clones,
89
- dryRun: false,
90
- attach: false
91
- });
92
- if (summary.zellij?.ok && summary.zellij.capability?.status === 'ok' && parsed.attach)
93
- attachZellijSessionInteractive(summary.zellij.session_name, { cwd: process.cwd() });
94
- }
106
+ summary.zellij = liveZellij;
95
107
  return emit(parsed, summary, () => {
96
108
  console.log('🍥 Shadow Clone Jutsu — Kage Bunshin no Jutsu');
97
109
  console.log('Mission: ' + result.mission_id);
@@ -158,6 +170,7 @@ function parseNarutoArgs(args = []) {
158
170
  const requestedClones = Number(readOption(args, '--clones', readOption(args, '--agents', DEFAULT_NARUTO_CLONES)));
159
171
  const clones = clampClones(requestedClones);
160
172
  const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones)), clones);
173
+ const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
161
174
  const backend = String(readOption(args, '--backend', hasFlag(args, '--mock') ? 'fake' : 'codex-exec'));
162
175
  const mock = hasFlag(args, '--mock') || backend === 'fake';
163
176
  const real = hasFlag(args, '--real');
@@ -167,9 +180,9 @@ function parseNarutoArgs(args = []) {
167
180
  const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', 'latest')));
168
181
  const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
169
182
  const attach = hasFlag(args, '--attach');
170
- const valueFlags = new Set(['--clones', '--agents', '--work-items', '--backend', '--write-mode', '--mission', '--mission-id']);
183
+ const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id']);
171
184
  const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
172
- return { action, prompt, clones, workItems, backend, mock, real, readonly, writeMode, json, missionId, noOpenZellij, attach };
185
+ return { action, prompt, clones, workItems, concurrency, backend, mock, real, readonly, writeMode, json, missionId, noOpenZellij, attach };
173
186
  }
174
187
  function clampClones(value) {
175
188
  if (!Number.isFinite(value) || value < 1)
@@ -181,6 +194,14 @@ function clampWorkItems(value, clones) {
181
194
  return clones;
182
195
  return Math.floor(value);
183
196
  }
197
+ function normalizeConcurrency(value, clones) {
198
+ if (value == null || value === '')
199
+ return null;
200
+ const parsed = Number(value);
201
+ if (!Number.isFinite(parsed) || parsed < 1)
202
+ return null;
203
+ return Math.min(Math.floor(parsed), clones, MAX_NARUTO_AGENT_COUNT);
204
+ }
184
205
  function hasFlag(args, flag) {
185
206
  return args.includes(flag);
186
207
  }
@@ -1,4 +1,4 @@
1
- export declare const PACKAGE_VERSION = "1.21.3";
1
+ export declare const PACKAGE_VERSION = "1.21.4";
2
2
  export declare const DEFAULT_PROCESS_TAIL_BYTES: number;
3
3
  export declare const DEFAULT_PROCESS_TIMEOUT_MS: number;
4
4
  export interface RunProcessOptions {
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '1.21.3';
8
+ export const PACKAGE_VERSION = '1.21.4';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "1.21.3";
1
+ export declare const PACKAGE_VERSION = "1.21.4";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '1.21.3';
1
+ export const PACKAGE_VERSION = '1.21.4';
2
2
  //# sourceMappingURL=version.js.map
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { appendJsonl, ensureDir, exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ import { resolveFastModePolicy } from '../agents/fast-mode-policy.js';
3
4
  export const ZELLIJ_LANE_RENDER_SCHEMA = 'sks.zellij-lane-render.v1';
4
5
  // Default render width. Zellij panes are commonly 80/100/120 columns; the frame
5
6
  // must stay readable (no wraps, no overflow) across that range.
@@ -289,11 +290,25 @@ async function buildLaneDashboard(root, slot, laneJson) {
289
290
  swarm?.mode
290
291
  ]) || 'Agent';
291
292
  // Fast service tier.
292
- const serviceTier = firstString([laneJson?.service_tier, scheduler?.service_tier, swarm?.service_tier]);
293
- const fastMode = laneJson?.fast_mode ?? scheduler?.fast_mode ?? swarm?.fast_mode;
293
+ const projectRoot = inferProjectRootFromLedgerRoot(root);
294
+ const policy = resolveFastModePolicy({ root: projectRoot });
295
+ const serviceTier = firstString([
296
+ laneJson?.service_tier,
297
+ scheduler?.service_tier,
298
+ swarm?.service_tier,
299
+ proof?.fast_mode_policy?.service_tier,
300
+ policy.service_tier
301
+ ]);
302
+ const fastMode = firstDefined([
303
+ laneJson?.fast_mode,
304
+ scheduler?.fast_mode,
305
+ swarm?.fast_mode,
306
+ proof?.fast_mode_policy?.fast_mode,
307
+ policy.fast_mode
308
+ ]);
294
309
  const fast = serviceTier === 'fast' || fastMode === true
295
310
  ? `on · service_tier=${serviceTier || 'fast'}`
296
- : (serviceTier ? `service_tier=${serviceTier}` : 'off');
311
+ : `off · service_tier=${serviceTier || 'standard'}`;
297
312
  // Workers: live active/target + (naruto) clone fan-out.
298
313
  const cloneTotal = numberOf([scheduler?.clones, scheduler?.clone_count, swarm?.clones]);
299
314
  const cloneActive = numberOf([scheduler?.active_clone, scheduler?.active_slot_count]);
@@ -358,6 +373,21 @@ function firstString(values) {
358
373
  }
359
374
  return null;
360
375
  }
376
+ function firstDefined(values) {
377
+ for (const value of values.flat()) {
378
+ if (value !== undefined && value !== null)
379
+ return value;
380
+ }
381
+ return undefined;
382
+ }
383
+ function inferProjectRootFromLedgerRoot(value) {
384
+ const root = path.resolve(value);
385
+ const parts = root.split(path.sep);
386
+ const index = parts.lastIndexOf('.sneakoscope');
387
+ if (index > 0)
388
+ return parts.slice(0, index).join(path.sep) || path.sep;
389
+ return root;
390
+ }
361
391
  function numberOf(values) {
362
392
  for (const value of values) {
363
393
  const n = Number(value);
@@ -84,8 +84,8 @@ function shellQuote(value) {
84
84
  return `'${value.replace(/'/g, `'\\''`)}'`;
85
85
  }
86
86
  function buildMainPaneCommand(input, sksCommand) {
87
- const codexArgs = (input.codexArgs || []).map((arg) => String(arg)).filter(Boolean);
88
- const shouldLaunchCodex = input.kind === 'mad' || codexArgs.length > 0;
87
+ const requestedCodexArgs = (input.codexArgs || []).map((arg) => String(arg)).filter(Boolean);
88
+ const shouldLaunchCodex = input.kind === 'mad' || requestedCodexArgs.length > 0;
89
89
  if (!shouldLaunchCodex) {
90
90
  const shell = shellQuote(String(process.env.SHELL || '/bin/zsh'));
91
91
  return {
@@ -95,6 +95,7 @@ function buildMainPaneCommand(input, sksCommand) {
95
95
  launchEnvKeys: []
96
96
  };
97
97
  }
98
+ const codexArgs = withCodexScrollbackArgs(requestedCodexArgs);
98
99
  const launchEnv = sanitizeLaunchEnv(input.launchEnv || {});
99
100
  const envPrefix = launchEnv.map(([key, value]) => `${key}=${shellQuote(value)}`);
100
101
  const codexBin = shellQuote(String(input.codexBin || process.env.SKS_CODEX_BIN || 'codex'));
@@ -105,6 +106,13 @@ function buildMainPaneCommand(input, sksCommand) {
105
106
  launchEnvKeys: launchEnv.map(([key]) => key)
106
107
  };
107
108
  }
109
+ function withCodexScrollbackArgs(args) {
110
+ if (process.env.SKS_ZELLIJ_CODEX_ALT_SCREEN === '1')
111
+ return args;
112
+ if (args.includes('--no-alt-screen'))
113
+ return args;
114
+ return ['--no-alt-screen', ...args];
115
+ }
108
116
  function sanitizeLaunchEnv(env) {
109
117
  return Object.entries(env)
110
118
  .filter(([key, value]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && value != null && String(value) !== '')
@@ -267,7 +267,7 @@ const tasks = [
267
267
  task('docs:truthfulness', 'npm run docs:truthfulness --silent', { dependencies: ['build'] }),
268
268
  task('official-docs:compat', 'npm run official-docs:compat --silent', { dependencies: ['build'] }),
269
269
  task('blackbox:matrix:contract', 'npm run blackbox:matrix:contract --silent', { dependencies: ['build'] }),
270
- task('test:blackbox', 'npm run test:blackbox --silent', { dependencies: ['build'] }),
270
+ task('test:blackbox', 'npm run test:blackbox --silent', { dependencies: ['build'], timeout_ms: 20 * 60 * 1000 }),
271
271
  task('rust:check', 'npm run rust:check --silent', { dependencies: ['build'] }),
272
272
  task('rust:smoke', 'npm run rust:smoke --silent', { dependencies: ['build'] }),
273
273
  task('release:dist-freshness', 'npm run release:dist-freshness --silent', { dependencies: ['build'] }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "1.21.3",
4
+ "version": "1.21.4",
5
5
  "description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",