sneakoscope 1.21.0 → 1.21.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/build-manifest.json +11 -9
- package/dist/cli/install-helpers.js +7 -10
- package/dist/commands/mad-sks.d.ts +3 -0
- package/dist/core/auto-review.d.ts +10 -0
- package/dist/core/auto-review.js +58 -0
- package/dist/core/codex/codex-config-eperm-repair.d.ts +1 -0
- package/dist/core/codex/codex-project-config-policy.d.ts +1 -0
- package/dist/core/codex/codex-project-config-policy.js +26 -5
- package/dist/core/commands/mad-sks-command.d.ts +3 -0
- package/dist/core/commands/mad-sks-command.js +13 -3
- package/dist/core/fsx.d.ts +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +7 -21
- package/dist/core/mad-sks/immutable-harness-guard.d.ts +6 -1
- package/dist/core/mad-sks/immutable-harness-guard.js +33 -2
- package/dist/core/preflight/parallel-preflight-engine.d.ts +1 -0
- package/dist/core/preflight/parallel-preflight-engine.js +9 -2
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-clipboard-config.d.ts +21 -0
- package/dist/core/zellij/zellij-clipboard-config.js +54 -0
- package/dist/core/zellij/zellij-launcher.d.ts +7 -0
- package/dist/core/zellij/zellij-launcher.js +17 -1
- package/package.json +1 -1
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.
|
|
19
|
+
SKS **1.21.1** fixes three `sks --mad` launch problems. **(1) Launch speed:** the launch no longer SHA-256-hashes the entire protected core (~1,900 files) on every invocation — the never-compared launch snapshot now uses a cheap metadata digest, and the redundant live `codex exec` config probe is skipped on the launch hot path (restore with `SKS_LAUNCH_FULL_CODEX_PROBE=1`); the immutable-harness integrity gates still use full content hashing. **(2) Zellij clipboard:** sessions now set `copy_command "pbcopy"` + `copy_on_select true` so selected text reaches the macOS clipboard instead of relying on OSC 52 (which Terminal.app drops); Shift+drag remains the native-selection fallback. **(3) Codex legacy-profile warning:** SKS migrated its config profiles to Codex 0.134+ per-file `$CODEX_HOME/<name>.config.toml` overlays — the project-config splitter now drops the deprecated `[profiles.*]` tables / `profile=` selector instead of relocating them into the home config, so Codex stops warning at startup. The Codex App fast-mode `[profiles.sks-fast-high]` table and `default_profile` are preserved.
|
|
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
|
|
|
@@ -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.
|
|
7
|
+
Some("--version") => println!("sks-rs 1.21.1"),
|
|
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.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "1.21.1",
|
|
5
|
+
"source_digest": "a0c970633ae67e63cd3667675e1a590e0d0e26caefd55f7f812764c7f8b8f869",
|
|
6
|
+
"source_file_count": 1752,
|
|
7
|
+
"built_at_source_time": 1780307630901
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/build-manifest.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build.v2",
|
|
3
|
-
"version": "1.21.
|
|
4
|
-
"package_version": "1.21.
|
|
3
|
+
"version": "1.21.1",
|
|
4
|
+
"package_version": "1.21.1",
|
|
5
5
|
"typescript": true,
|
|
6
6
|
"mjs_runtime_files": 0,
|
|
7
|
-
"compiled_file_count":
|
|
8
|
-
"compiled_js_count":
|
|
9
|
-
"compiled_dts_count":
|
|
10
|
-
"source_digest": "
|
|
11
|
-
"source_file_count":
|
|
12
|
-
"source_files_hash": "
|
|
13
|
-
"source_list_hash": "
|
|
7
|
+
"compiled_file_count": 1022,
|
|
8
|
+
"compiled_js_count": 511,
|
|
9
|
+
"compiled_dts_count": 511,
|
|
10
|
+
"source_digest": "a0c970633ae67e63cd3667675e1a590e0d0e26caefd55f7f812764c7f8b8f869",
|
|
11
|
+
"source_file_count": 1752,
|
|
12
|
+
"source_files_hash": "6b8e7de7bcab2c6ddd83d31e15d7219e9d3f595813a1ec8173936208d3c877ef",
|
|
13
|
+
"source_list_hash": "6b8e7de7bcab2c6ddd83d31e15d7219e9d3f595813a1ec8173936208d3c877ef",
|
|
14
14
|
"src_mjs_runtime_files": 0,
|
|
15
15
|
"dist_stamp_schema": "sks.dist-build-stamp.v1",
|
|
16
16
|
"files": [
|
|
@@ -1021,6 +1021,8 @@
|
|
|
1021
1021
|
"core/work-order-ledger.js",
|
|
1022
1022
|
"core/zellij/zellij-capability.d.ts",
|
|
1023
1023
|
"core/zellij/zellij-capability.js",
|
|
1024
|
+
"core/zellij/zellij-clipboard-config.d.ts",
|
|
1025
|
+
"core/zellij/zellij-clipboard-config.js",
|
|
1024
1026
|
"core/zellij/zellij-command.d.ts",
|
|
1025
1027
|
"core/zellij/zellij-command.js",
|
|
1026
1028
|
"core/zellij/zellij-lane-renderer.d.ts",
|
|
@@ -1525,21 +1525,18 @@ function normalizeCodexFastModeUiConfigOnce(text = '') {
|
|
|
1525
1525
|
next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
|
|
1526
1526
|
next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'enabled = true');
|
|
1527
1527
|
next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
|
|
1528
|
+
// Keep ONLY the sks-fast-high config-profile table: the Codex App fast-mode
|
|
1529
|
+
// (`[user.fast_mode] default_profile = "sks-fast-high"`) and the
|
|
1530
|
+
// codex-app:ui-preservation gate still expect it. The other SKS config profiles are
|
|
1531
|
+
// no longer written as `[profiles.sks-*]` tables here (Codex 0.134+ deprecates them);
|
|
1532
|
+
// they are managed as per-file `<name>.config.toml` overlays by
|
|
1533
|
+
// migrateSksProfilesToPerFile (src/core/auto-review.ts), which also writes the
|
|
1534
|
+
// sks-fast-high overlay for CLI `--profile` use.
|
|
1528
1535
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'model = "gpt-5.5"');
|
|
1529
1536
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'service_tier = "fast"');
|
|
1530
1537
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'approval_policy = "on-request"');
|
|
1531
1538
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'sandbox_mode = "workspace-write"');
|
|
1532
1539
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'model_reasoning_effort = "high"');
|
|
1533
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'model = "gpt-5.5"');
|
|
1534
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'service_tier = "fast"');
|
|
1535
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'approval_policy = "on-request"');
|
|
1536
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'sandbox_mode = "workspace-write"');
|
|
1537
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'model_reasoning_effort = "xhigh"');
|
|
1538
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research', 'model = "gpt-5.5"');
|
|
1539
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research', 'service_tier = "fast"');
|
|
1540
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research', 'approval_policy = "never"');
|
|
1541
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research', 'sandbox_mode = "workspace-write"');
|
|
1542
|
-
next = upsertTomlTableKey(next, 'profiles.sks-research', 'model_reasoning_effort = "xhigh"');
|
|
1543
1540
|
// Plugin auto-enable is OPT-IN only. Force-writing `[plugins."name@marketplace"] enabled =
|
|
1544
1541
|
// true` for marketplace plugins the App may not have installed (different build/channel)
|
|
1545
1542
|
// makes the App reference plugins it cannot load -> broken/blocked plugin UI. It also
|
|
@@ -27,6 +27,8 @@ export declare function run(_command: any, args?: any): Promise<void | {
|
|
|
27
27
|
attach_requested: boolean;
|
|
28
28
|
zellij_socket_dir: string | null;
|
|
29
29
|
zellij_socket_dir_source: import("../core/zellij/zellij-command.js").ZellijSocketDirSource;
|
|
30
|
+
clipboard_config_path: string;
|
|
31
|
+
clipboard_copy_command: string;
|
|
30
32
|
pane_proof_path: string;
|
|
31
33
|
pane_proof: {
|
|
32
34
|
schema: string;
|
|
@@ -119,6 +121,7 @@ export declare function run(_command: any, args?: any): Promise<void | {
|
|
|
119
121
|
};
|
|
120
122
|
moved_keys: string[];
|
|
121
123
|
moved_tables: string[];
|
|
124
|
+
removed_legacy_profiles: string[];
|
|
122
125
|
deprecated_approval_policy_fixed: boolean;
|
|
123
126
|
actions: string[];
|
|
124
127
|
parse_smoke: {
|
|
@@ -74,6 +74,16 @@ export declare function enableAutoReview(opts?: any): Promise<{
|
|
|
74
74
|
dynamic_effort: "parent assigns high effort to safety/integrator lanes and medium or higher to verification lanes when proof risk is present";
|
|
75
75
|
}>;
|
|
76
76
|
}>;
|
|
77
|
+
export declare const SKS_CONFIG_PROFILES: Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
stripTable: boolean;
|
|
80
|
+
block: string;
|
|
81
|
+
}>;
|
|
82
|
+
export declare function migrateSksProfilesToPerFile(opts?: any): Promise<{
|
|
83
|
+
config_path: any;
|
|
84
|
+
profiles_written: string[];
|
|
85
|
+
tables_stripped: string[];
|
|
86
|
+
}>;
|
|
77
87
|
export declare function enableMadHighProfile(opts?: any): Promise<{
|
|
78
88
|
config_path: any;
|
|
79
89
|
profile_config_path: string;
|
package/dist/core/auto-review.js
CHANGED
|
@@ -92,8 +92,63 @@ export async function enableAutoReview(opts = {}) {
|
|
|
92
92
|
launch_args: ['--profile', autoReviewProfileName({ high })]
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
+
// Canonical registry of every SKS config profile. Codex 0.134+ deprecated the
|
|
96
|
+
// `[profiles.*]` tables / top-level `profile=` selector (warns at startup) in favor of
|
|
97
|
+
// per-file `$CODEX_HOME/<name>.config.toml` overlays loaded by `--profile <name>`.
|
|
98
|
+
// `stripTable: true` => remove the legacy `[profiles.<name>]` table from the home
|
|
99
|
+
// config during migration. sks-fast-high keeps its table because the Codex App
|
|
100
|
+
// fast-mode (`[user.fast_mode] default_profile = "sks-fast-high"`) and the
|
|
101
|
+
// codex-app:ui-preservation gate still expect it; its per-file overlay is also written
|
|
102
|
+
// so CLI `--profile sks-fast-high` works too.
|
|
103
|
+
export const SKS_CONFIG_PROFILES = [
|
|
104
|
+
{ name: 'sks-task-low', stripTable: true, block: sksProfileFileBlock({ effort: 'low' }) },
|
|
105
|
+
{ name: 'sks-task-medium', stripTable: true, block: sksProfileFileBlock({ effort: 'medium' }) },
|
|
106
|
+
{ name: 'sks-logic-high', stripTable: true, block: sksProfileFileBlock({ effort: 'high' }) },
|
|
107
|
+
{ name: 'sks-fast-high', stripTable: false, block: sksProfileFileBlock({ effort: 'high', serviceTier: 'fast' }) },
|
|
108
|
+
{ name: 'sks-research-xhigh', stripTable: true, block: sksProfileFileBlock({ effort: 'xhigh' }) },
|
|
109
|
+
{ name: 'sks-research', stripTable: true, block: sksProfileFileBlock({ effort: 'xhigh', approvalPolicy: 'never' }) },
|
|
110
|
+
{ name: 'sks-team', stripTable: true, block: sksProfileFileBlock({ effort: 'medium' }) },
|
|
111
|
+
{ name: MAD_HIGH_PROFILE, stripTable: true, block: sksProfileFileBlock({ effort: 'high', approvalPolicy: 'never', sandboxMode: 'danger-full-access', reviewer: AUTO_REVIEW_REVIEWER }) },
|
|
112
|
+
{ name: 'sks-default', stripTable: true, block: sksProfileFileBlock({ effort: 'high' }) }
|
|
113
|
+
];
|
|
114
|
+
function sksProfileFileBlock(opts = {}) {
|
|
115
|
+
return [
|
|
116
|
+
'model = "gpt-5.5"',
|
|
117
|
+
`service_tier = "${opts.serviceTier || 'fast'}"`,
|
|
118
|
+
`approval_policy = "${opts.approvalPolicy || 'on-request'}"`,
|
|
119
|
+
...(opts.reviewer ? [`approvals_reviewer = "${opts.reviewer}"`] : []),
|
|
120
|
+
`sandbox_mode = "${opts.sandboxMode || 'workspace-write'}"`,
|
|
121
|
+
`model_reasoning_effort = "${opts.effort || 'medium'}"`
|
|
122
|
+
].join('\n');
|
|
123
|
+
}
|
|
124
|
+
// Migrate every SKS config profile to a per-file `<name>.config.toml` overlay in
|
|
125
|
+
// CODEX_HOME and strip the deprecated legacy `[profiles.sks-*]` tables / `profile=`
|
|
126
|
+
// selectors from the home config. Idempotent (second run is a no-op). This is the
|
|
127
|
+
// step that clears the Codex deprecation warning on `sks --mad`.
|
|
128
|
+
export async function migrateSksProfilesToPerFile(opts = {}) {
|
|
129
|
+
const configPath = opts.configPath || codexConfigPath(opts.env || process.env);
|
|
130
|
+
await ensureDir(path.dirname(configPath));
|
|
131
|
+
const current = await readText(configPath, '');
|
|
132
|
+
let next = String(current || '');
|
|
133
|
+
for (const profile of SKS_CONFIG_PROFILES) {
|
|
134
|
+
if (profile.stripTable)
|
|
135
|
+
next = removeLegacyProfileConfig(next, profile.name);
|
|
136
|
+
}
|
|
137
|
+
if (next && !next.endsWith('\n'))
|
|
138
|
+
next += '\n';
|
|
139
|
+
if (next !== String(current || ''))
|
|
140
|
+
await writeTextAtomic(configPath, next);
|
|
141
|
+
for (const profile of SKS_CONFIG_PROFILES)
|
|
142
|
+
await writeProfileConfig(configPath, profile.name, profile.block);
|
|
143
|
+
return {
|
|
144
|
+
config_path: configPath,
|
|
145
|
+
profiles_written: SKS_CONFIG_PROFILES.map((profile) => profile.name),
|
|
146
|
+
tables_stripped: SKS_CONFIG_PROFILES.filter((profile) => profile.stripTable).map((profile) => profile.name)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
95
149
|
export async function enableMadHighProfile(opts = {}) {
|
|
96
150
|
const configPath = opts.configPath || codexConfigPath(opts.env || process.env);
|
|
151
|
+
const env = opts.env || process.env;
|
|
97
152
|
await ensureDir(path.dirname(configPath));
|
|
98
153
|
const current = await readText(configPath, '');
|
|
99
154
|
let next = removeLegacyProfileConfig(current, MAD_HIGH_PROFILE);
|
|
@@ -101,6 +156,9 @@ export async function enableMadHighProfile(opts = {}) {
|
|
|
101
156
|
if (!next.endsWith('\n'))
|
|
102
157
|
next += '\n';
|
|
103
158
|
await writeTextAtomic(configPath, next);
|
|
159
|
+
// Convert all SKS profiles to per-file overlays and strip the deprecated tables /
|
|
160
|
+
// selectors so Codex stops warning about the legacy config profile on launch.
|
|
161
|
+
await migrateSksProfilesToPerFile({ configPath, env });
|
|
104
162
|
await writeProfileConfig(configPath, MAD_HIGH_PROFILE, profileConfigBlock({
|
|
105
163
|
effort: 'high',
|
|
106
164
|
approvalPolicy: 'never',
|
|
@@ -32,6 +32,7 @@ export declare function splitCodexProjectConfigPolicy(rootInput?: string, opts?:
|
|
|
32
32
|
};
|
|
33
33
|
moved_keys: string[];
|
|
34
34
|
moved_tables: string[];
|
|
35
|
+
removed_legacy_profiles: string[];
|
|
35
36
|
deprecated_approval_policy_fixed: boolean;
|
|
36
37
|
actions: string[];
|
|
37
38
|
parse_smoke: {
|
|
@@ -4,8 +4,6 @@ import path from 'node:path';
|
|
|
4
4
|
import { ensureDir, nowIso, readText, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
5
5
|
export const CODEX_PROJECT_CONFIG_POLICY_SCHEMA = 'sks.codex-project-config-policy.v1';
|
|
6
6
|
const MACHINE_LOCAL_TOP_LEVEL_KEYS = new Set([
|
|
7
|
-
'profile',
|
|
8
|
-
'profiles',
|
|
9
7
|
'model_provider',
|
|
10
8
|
'model_providers',
|
|
11
9
|
'openai_base_url',
|
|
@@ -20,13 +18,23 @@ const MACHINE_LOCAL_TOP_LEVEL_KEYS = new Set([
|
|
|
20
18
|
'telemetry'
|
|
21
19
|
]);
|
|
22
20
|
const MACHINE_LOCAL_TABLE_PREFIXES = [
|
|
23
|
-
'profiles',
|
|
24
21
|
'model_providers',
|
|
25
22
|
'notify',
|
|
26
23
|
'otel',
|
|
27
24
|
'telemetry',
|
|
28
25
|
'experimental_telemetry'
|
|
29
26
|
];
|
|
27
|
+
// Codex 0.134+ removed the legacy config-profile consumers: `--profile NAME` now
|
|
28
|
+
// layers `$CODEX_HOME/<name>.config.toml` over the base config and the top-level
|
|
29
|
+
// `profile = "..."` selector / `[profiles.*]` tables are deprecated and warned about
|
|
30
|
+
// at startup. So these are NO LONGER machine-local-and-moved-to-home — they are
|
|
31
|
+
// DROPPED from the project config entirely. The per-file profiles are owned by
|
|
32
|
+
// migrateSksProfilesToPerFile (src/core/auto-review.ts), which runs on `sks --mad`.
|
|
33
|
+
const DEPRECATED_LEGACY_PROFILE_TOP_LEVEL_KEYS = new Set(['profile', 'profiles']);
|
|
34
|
+
const DEPRECATED_LEGACY_PROFILE_TABLE_PREFIXES = ['profiles'];
|
|
35
|
+
function isDeprecatedLegacyProfileTable(table) {
|
|
36
|
+
return DEPRECATED_LEGACY_PROFILE_TABLE_PREFIXES.some((prefix) => table === prefix || table.startsWith(`${prefix}.`));
|
|
37
|
+
}
|
|
30
38
|
export async function splitCodexProjectConfigPolicy(rootInput = process.cwd(), opts = {}) {
|
|
31
39
|
const root = path.resolve(rootInput || process.cwd());
|
|
32
40
|
const configPath = path.resolve(opts.configPath || path.join(root, '.codex', 'config.toml'));
|
|
@@ -124,6 +132,7 @@ export async function splitCodexProjectConfigPolicy(rootInput = process.cwd(), o
|
|
|
124
132
|
},
|
|
125
133
|
moved_keys: split.moved_keys,
|
|
126
134
|
moved_tables: split.moved_tables,
|
|
135
|
+
removed_legacy_profiles: split.removed_legacy_profiles,
|
|
127
136
|
deprecated_approval_policy_fixed: split.deprecated_approval_policy_fixed,
|
|
128
137
|
actions,
|
|
129
138
|
parse_smoke: parseSmoke,
|
|
@@ -250,10 +259,17 @@ function splitProjectToml(text) {
|
|
|
250
259
|
const machineBlocks = [];
|
|
251
260
|
const movedKeys = [];
|
|
252
261
|
const movedTables = [];
|
|
262
|
+
const removedLegacyProfiles = [];
|
|
253
263
|
const blockers = [];
|
|
254
264
|
let profileName = null;
|
|
255
265
|
let deprecatedFixed = false;
|
|
256
266
|
for (const block of blocks) {
|
|
267
|
+
// Deprecated legacy config profiles are DROPPED, not moved to the home config
|
|
268
|
+
// (Codex 0.134+ warns about `[profiles.*]` tables and the `profile=` selector).
|
|
269
|
+
if (block.table && isDeprecatedLegacyProfileTable(block.table)) {
|
|
270
|
+
removedLegacyProfiles.push(block.table);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
257
273
|
if (block.table && isMachineLocalTable(block.table)) {
|
|
258
274
|
if (block.array) {
|
|
259
275
|
kept.push(block.text);
|
|
@@ -270,11 +286,15 @@ function splitProjectToml(text) {
|
|
|
270
286
|
const moveLines = [];
|
|
271
287
|
for (const line of block.text.split('\n')) {
|
|
272
288
|
const key = topLevelKey(line);
|
|
289
|
+
if (key && DEPRECATED_LEGACY_PROFILE_TOP_LEVEL_KEYS.has(key)) {
|
|
290
|
+
if (key === 'profile')
|
|
291
|
+
profileName = tomlStringValue(line);
|
|
292
|
+
removedLegacyProfiles.push(`top_level:${key}`);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
273
295
|
if (key && MACHINE_LOCAL_TOP_LEVEL_KEYS.has(key)) {
|
|
274
296
|
moveLines.push(line);
|
|
275
297
|
movedKeys.push(key);
|
|
276
|
-
if (key === 'profile')
|
|
277
|
-
profileName = tomlStringValue(line);
|
|
278
298
|
continue;
|
|
279
299
|
}
|
|
280
300
|
const fixed = fixDeprecatedApprovalPolicy(line);
|
|
@@ -302,6 +322,7 @@ function splitProjectToml(text) {
|
|
|
302
322
|
machine_blocks: machineBlocks,
|
|
303
323
|
moved_keys: [...new Set(movedKeys)],
|
|
304
324
|
moved_tables: [...new Set(movedTables)],
|
|
325
|
+
removed_legacy_profiles: [...new Set(removedLegacyProfiles)],
|
|
305
326
|
kept_keys: [],
|
|
306
327
|
profile_name: profileName,
|
|
307
328
|
deprecated_approval_policy_fixed: deprecatedFixed,
|
|
@@ -27,6 +27,8 @@ export declare function madHighCommand(args?: any, deps?: any): Promise<void | {
|
|
|
27
27
|
attach_requested: boolean;
|
|
28
28
|
zellij_socket_dir: string | null;
|
|
29
29
|
zellij_socket_dir_source: import("../zellij/zellij-command.js").ZellijSocketDirSource;
|
|
30
|
+
clipboard_config_path: string;
|
|
31
|
+
clipboard_copy_command: string;
|
|
30
32
|
pane_proof_path: string;
|
|
31
33
|
pane_proof: {
|
|
32
34
|
schema: string;
|
|
@@ -119,6 +121,7 @@ export declare function madHighCommand(args?: any, deps?: any): Promise<void | {
|
|
|
119
121
|
};
|
|
120
122
|
moved_keys: string[];
|
|
121
123
|
moved_tables: string[];
|
|
124
|
+
removed_legacy_profiles: string[];
|
|
122
125
|
deprecated_approval_policy_fixed: boolean;
|
|
123
126
|
actions: string[];
|
|
124
127
|
parse_smoke: {
|
|
@@ -57,7 +57,12 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
57
57
|
const launchRoot = process.cwd();
|
|
58
58
|
if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
|
|
59
59
|
await initProject(launchRoot, {});
|
|
60
|
-
|
|
60
|
+
// launchFast skips the redundant live-`codex exec` config probe (up to ~20s, run
|
|
61
|
+
// up to 3x via repair re-inspections): the real codex profile is exercised moments
|
|
62
|
+
// later when the Zellij session opens. All filesystem/permission/EPERM/symlink/ACL
|
|
63
|
+
// readability + repair checks still run. SKS_LAUNCH_FULL_CODEX_PROBE=1 restores the
|
|
64
|
+
// old behavior.
|
|
65
|
+
const launchPreflight = await runCodexLaunchPreflight(launchRoot, { fix: true, launchFast: process.env.SKS_LAUNCH_FULL_CODEX_PROBE !== '1', profile: profile.profile_name, sandbox: 'danger-full-access', serviceTier: 'fast' });
|
|
61
66
|
if (!launchPreflight.ok) {
|
|
62
67
|
console.error('SKS MAD launch blocked by config preflight.');
|
|
63
68
|
for (const blocker of launchPreflight.blockers || [])
|
|
@@ -88,7 +93,7 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
88
93
|
// instead of leaving them to copy/paste the attach command by hand.
|
|
89
94
|
if (shouldAutoAttachZellij(args)) {
|
|
90
95
|
console.log(`Opening Zellij session: ${launch.session_name} (detach with Ctrl+q, re-attach later with: ${launch.attach_command_with_env})`);
|
|
91
|
-
const attached = attachZellijSessionInteractive(launch.session_name, { cwd: process.cwd() });
|
|
96
|
+
const attached = attachZellijSessionInteractive(launch.session_name, { cwd: process.cwd(), configPath: launch.clipboard_config_path });
|
|
92
97
|
if (!attached.ok) {
|
|
93
98
|
console.log(`Could not open the Zellij session automatically${attached.error ? ` (${attached.error})` : ''}.`);
|
|
94
99
|
if (launch.attach_command_with_env)
|
|
@@ -148,7 +153,12 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
|
|
|
148
153
|
const dbWriteAllowed = has('db_write');
|
|
149
154
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
|
|
150
155
|
const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
|
|
151
|
-
|
|
156
|
+
// The interactive launch 'before' snapshot is only persisted (env + policy json)
|
|
157
|
+
// and is never compared against an 'after' snapshot during the session, so the
|
|
158
|
+
// strong full-content hash is wasted here. Use the cheap metadata digest (no file
|
|
159
|
+
// reads) on the launch hot path. run/apply and the release gates still take their
|
|
160
|
+
// own strong content snapshots where the digest is actually compared.
|
|
161
|
+
const protectedCoreBefore = await snapshotProtectedCore(packageRoot(), 'mad-live-before', { mode: 'metadata' });
|
|
152
162
|
const protectedCorePolicyPath = path.join(dir, 'mad-sks-protected-core-policy.json');
|
|
153
163
|
const protectedCoreBeforePath = path.join(dir, 'mad-sks-live-protected-core-before.json');
|
|
154
164
|
await writeJsonAtomic(protectedCorePolicyPath, {
|
package/dist/core/fsx.d.ts
CHANGED
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.
|
|
8
|
+
export const PACKAGE_VERSION = '1.21.1';
|
|
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() {
|
package/dist/core/init.js
CHANGED
|
@@ -803,19 +803,16 @@ export async function initProject(root, opts = {}) {
|
|
|
803
803
|
{ table: 'agents.implementation_worker', text: agentConfigBlock('implementation_worker', 'SKS bounded implementation worker.', './agents/implementation-worker.toml', ['Builder', 'Mason']) },
|
|
804
804
|
{ table: 'agents.db_safety_reviewer', text: agentConfigBlock('db_safety_reviewer', 'Read-only DB safety reviewer.', './agents/db-safety-reviewer.toml', ['Sentinel', 'Ledger']) },
|
|
805
805
|
{ table: 'agents.qa_reviewer', text: agentConfigBlock('qa_reviewer', 'Read-only QA reviewer.', './agents/qa-reviewer.toml', ['Verifier', 'Reviewer']) },
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
{ table: 'profiles.sks-team', text: profileConfigBlock('sks-team', 'medium') },
|
|
813
|
-
{ table: 'profiles.sks-mad-high', text: profileConfigBlock('sks-mad-high', 'high', { approval: 'never', sandbox: 'danger-full-access', approvalsReviewer: 'auto_review' }) },
|
|
806
|
+
// NOTE: SKS config profiles are NO LONGER emitted as `[profiles.sks-*]` tables.
|
|
807
|
+
// Codex 0.134+ deprecated config-profile tables / the `profile=` selector (warns at
|
|
808
|
+
// startup) in favor of per-file `$CODEX_HOME/<name>.config.toml` overlays loaded by
|
|
809
|
+
// `--profile <name>`. Those per-file profiles are owned by migrateSksProfilesToPerFile
|
|
810
|
+
// (src/core/auto-review.ts), invoked on `sks --mad`. Emitting the tables here only got
|
|
811
|
+
// them relocated into the home config by the splitter, re-triggering the warning.
|
|
814
812
|
{
|
|
815
813
|
table: 'auto_review',
|
|
816
814
|
text: '[auto_review]\npolicy = "In MAD launches, allow live-server work, normal DB writes, Supabase MCP DB writes, direct execute SQL, schema cleanup, and migration application for the active invocation. Deny only catastrophic database wipes, all-row value deletion/update, dangerous project or branch management, credential exfiltration, persistent security weakening, broad unrelated file deletion, and unrequested fallback implementation code."'
|
|
817
|
-
}
|
|
818
|
-
{ table: 'profiles.sks-default', text: profileConfigBlock('sks-default', 'high') }
|
|
815
|
+
}
|
|
819
816
|
];
|
|
820
817
|
}
|
|
821
818
|
function agentConfigBlock(table, description, configFile, nicknames = []) {
|
|
@@ -826,17 +823,6 @@ export async function initProject(root, opts = {}) {
|
|
|
826
823
|
`nickname_candidates = [${nicknames.map((name) => `"${name}"`).join(', ')}]`
|
|
827
824
|
].join('\n');
|
|
828
825
|
}
|
|
829
|
-
function profileConfigBlock(profile, effort, opts = {}) {
|
|
830
|
-
return [
|
|
831
|
-
`[profiles.${profile}]`,
|
|
832
|
-
'model = "gpt-5.5"',
|
|
833
|
-
`service_tier = "${opts.serviceTier || 'fast'}"`,
|
|
834
|
-
`approval_policy = "${opts.approval || 'on-request'}"`,
|
|
835
|
-
...(opts.approvalsReviewer ? [`approvals_reviewer = "${opts.approvalsReviewer}"`] : []),
|
|
836
|
-
`sandbox_mode = "${opts.sandbox || 'workspace-write'}"`,
|
|
837
|
-
`model_reasoning_effort = "${effort}"`
|
|
838
|
-
].join('\n');
|
|
839
|
-
}
|
|
840
826
|
function upsertTomlTableKey(text, table, line) {
|
|
841
827
|
const key = (String(line).split('=')[0] || '').trim();
|
|
842
828
|
let lines = String(text || '').split('\n');
|
|
@@ -83,9 +83,13 @@ export declare function evaluateMadSksWrite({ packageRoot: packageRootInput, tar
|
|
|
83
83
|
};
|
|
84
84
|
wrongness_kind: string | null;
|
|
85
85
|
}>;
|
|
86
|
-
export
|
|
86
|
+
export interface SnapshotProtectedCoreOptions {
|
|
87
|
+
mode?: 'content' | 'metadata';
|
|
88
|
+
}
|
|
89
|
+
export declare function snapshotProtectedCore(root?: string, label?: string, opts?: SnapshotProtectedCoreOptions): Promise<{
|
|
87
90
|
schema: string;
|
|
88
91
|
label: string;
|
|
92
|
+
digest_mode: string;
|
|
89
93
|
generated_at: string;
|
|
90
94
|
package_root: string;
|
|
91
95
|
engine_source_exception: boolean;
|
|
@@ -116,6 +120,7 @@ export declare function buildProtectedCoreSnapshot({ packageRoot: packageRootInp
|
|
|
116
120
|
}): Promise<{
|
|
117
121
|
schema: string;
|
|
118
122
|
label: string;
|
|
123
|
+
digest_mode: string;
|
|
119
124
|
generated_at: string;
|
|
120
125
|
package_root: string;
|
|
121
126
|
engine_source_exception: boolean;
|
|
@@ -122,16 +122,18 @@ export async function evaluateMadSksWrite({ packageRoot: packageRootInput = pack
|
|
|
122
122
|
wrongness_kind: decision.wrongness_kind
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
-
export async function snapshotProtectedCore(root = packageRoot(), label = 'snapshot') {
|
|
125
|
+
export async function snapshotProtectedCore(root = packageRoot(), label = 'snapshot', opts = {}) {
|
|
126
|
+
const mode = opts.mode === 'metadata' ? 'metadata' : 'content';
|
|
126
127
|
const resolution = resolveProtectedCore(root);
|
|
127
128
|
const entries = [];
|
|
128
129
|
for (const entry of resolution.protected_paths) {
|
|
129
|
-
entries.push(await hashProtectedEntry(entry));
|
|
130
|
+
entries.push(mode === 'metadata' ? await metadataHashProtectedEntry(entry) : await hashProtectedEntry(entry));
|
|
130
131
|
}
|
|
131
132
|
const digest = sha256(entries.map((entry) => `${entry.id}:${entry.hash || 'missing'}:${entry.file_count}:${entry.bytes}`).join('\n'));
|
|
132
133
|
return {
|
|
133
134
|
schema: MAD_SKS_PROTECTED_CORE_SNAPSHOT_SCHEMA,
|
|
134
135
|
label,
|
|
136
|
+
digest_mode: mode,
|
|
135
137
|
generated_at: nowIso(),
|
|
136
138
|
package_root: resolution.package_root,
|
|
137
139
|
engine_source_exception: resolution.engine_source_exception,
|
|
@@ -186,6 +188,35 @@ async function hashProtectedEntry(entry) {
|
|
|
186
188
|
}
|
|
187
189
|
return { id: entry.id, path: entry.path, relative_path: entry.path, present: true, hash: sha256(parts.join('\n')), file_count: files.length, bytes };
|
|
188
190
|
}
|
|
191
|
+
// Cheap variant of hashProtectedEntry: hash only filesystem metadata
|
|
192
|
+
// (mtimeMs + size + mode) instead of reading + sha256-ing file contents. Walks the
|
|
193
|
+
// same file set so file_count/bytes stay comparable, but avoids the ~10MB read +
|
|
194
|
+
// per-file sha256 cost. Only used for the never-compared interactive launch 'before'
|
|
195
|
+
// snapshot; integrity-critical callers keep the default 'content' mode.
|
|
196
|
+
async function metadataHashProtectedEntry(entry) {
|
|
197
|
+
if (!(await exists(entry.absolute_path))) {
|
|
198
|
+
return { id: entry.id, path: entry.path, relative_path: entry.path, present: false, hash: null, file_count: 0, bytes: 0 };
|
|
199
|
+
}
|
|
200
|
+
const stat = await fsp.lstat(entry.absolute_path);
|
|
201
|
+
if (stat.isFile()) {
|
|
202
|
+
return { id: entry.id, path: entry.path, relative_path: entry.path, present: true, hash: sha256(`${stat.mtimeMs}:${stat.size}:${stat.mode}`), file_count: 1, bytes: stat.size };
|
|
203
|
+
}
|
|
204
|
+
if (!stat.isDirectory()) {
|
|
205
|
+
return { id: entry.id, path: entry.path, relative_path: entry.path, present: true, hash: sha256(`${stat.mode}:${stat.size}`), file_count: 1, bytes: stat.size };
|
|
206
|
+
}
|
|
207
|
+
if (entry.match === 'exact') {
|
|
208
|
+
return { id: entry.id, path: entry.path, relative_path: entry.path, present: true, hash: sha256(`dir:${stat.mode}:${stat.uid}:${stat.gid}`), file_count: 1, bytes: 0 };
|
|
209
|
+
}
|
|
210
|
+
const files = await walk(entry.absolute_path);
|
|
211
|
+
let bytes = 0;
|
|
212
|
+
const parts = [];
|
|
213
|
+
for (const file of files.sort()) {
|
|
214
|
+
const st = await fsp.lstat(file);
|
|
215
|
+
bytes += st.size;
|
|
216
|
+
parts.push(`${path.relative(entry.absolute_path, file).split(path.sep).join('/')}:${st.mtimeMs}:${st.size}:${st.mode}`);
|
|
217
|
+
}
|
|
218
|
+
return { id: entry.id, path: entry.path, relative_path: entry.path, present: true, hash: sha256(parts.join('\n')), file_count: files.length, bytes };
|
|
219
|
+
}
|
|
189
220
|
async function walk(dir, out = []) {
|
|
190
221
|
const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
191
222
|
for (const entry of entries) {
|
|
@@ -78,6 +78,7 @@ export declare function runCodexLaunchPreflight(rootInput?: string, opts?: any):
|
|
|
78
78
|
};
|
|
79
79
|
moved_keys: string[];
|
|
80
80
|
moved_tables: string[];
|
|
81
|
+
removed_legacy_profiles: string[];
|
|
81
82
|
deprecated_approval_policy_fixed: boolean;
|
|
82
83
|
actions: string[];
|
|
83
84
|
parse_smoke: {
|
|
@@ -29,12 +29,19 @@ export async function runParallelPreflight(checks) {
|
|
|
29
29
|
export async function runCodexLaunchPreflight(rootInput = process.cwd(), opts = {}) {
|
|
30
30
|
const root = path.resolve(rootInput || process.cwd());
|
|
31
31
|
const reportPath = opts.reportPath || path.join(root, '.sneakoscope', 'reports', 'mad-launch-preflight.json');
|
|
32
|
+
// On the interactive launch path the real codex profile is exercised the moment the
|
|
33
|
+
// Zellij session opens, so spawning `codex exec` here (up to ~20s, and again inside
|
|
34
|
+
// the repair re-inspections) is redundant. launchFast skips ONLY the live-codex probe;
|
|
35
|
+
// all filesystem/permission/symlink/ACL/EPERM readability + repair checks still run, so
|
|
36
|
+
// the EPERM/tcc_possible/EACCES blockers still fire for unreadable configs
|
|
37
|
+
// (codex_cli_config_eperm is probe-only and intentionally not exercised on this path).
|
|
38
|
+
const probeCodex = opts.launchFast === true ? false : opts.actualCodex !== false;
|
|
32
39
|
const readonly = await runParallelPreflight([
|
|
33
|
-
{ id: 'codex_config_readability', run: () => inspectCodexConfigReadability(root, { ...opts, codexProbe:
|
|
40
|
+
{ id: 'codex_config_readability', run: () => inspectCodexConfigReadability(root, { ...opts, codexProbe: probeCodex, actualCodex: probeCodex, writeReport: false }) },
|
|
34
41
|
{ id: 'codex_project_config_policy', run: () => splitCodexProjectConfigPolicy(root, { ...opts, writeReport: false }) }
|
|
35
42
|
]);
|
|
36
43
|
const repair = opts.fix === true || readonly.ok === false
|
|
37
|
-
? await repairCodexConfigEperm(root, { ...opts, codexProbe:
|
|
44
|
+
? await repairCodexConfigEperm(root, { ...opts, codexProbe: probeCodex, actualCodex: probeCodex, fix: opts.fix !== false, writeReport: false })
|
|
38
45
|
: null;
|
|
39
46
|
const zellijCapability = opts.zellijCapability === false
|
|
40
47
|
? null
|
package/dist/core/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "1.21.
|
|
1
|
+
export declare const PACKAGE_VERSION = "1.21.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '1.21.
|
|
1
|
+
export const PACKAGE_VERSION = '1.21.1';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const ZELLIJ_CLIPBOARD_CONFIG_SCHEMA = "sks.zellij-clipboard-config.v1";
|
|
2
|
+
export interface ZellijClipboardConfig {
|
|
3
|
+
copy_command: string;
|
|
4
|
+
copy_clipboard: 'system' | 'primary';
|
|
5
|
+
copy_on_select: boolean;
|
|
6
|
+
/** Flags to append to the `zellij ... options` subcommand for the created session. */
|
|
7
|
+
optionFlags: string[];
|
|
8
|
+
/** Path to a dedicated config.kdl for the interactive attach (delivered via ZELLIJ_CONFIG_FILE). */
|
|
9
|
+
config_path: string;
|
|
10
|
+
generated_at: string;
|
|
11
|
+
}
|
|
12
|
+
/** Pick the platform-correct clipboard command. macOS is the primary target. */
|
|
13
|
+
export declare function resolveCopyCommand(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
|
|
14
|
+
/** Render a minimal, valid Zellij config.kdl that enables clipboard copy. */
|
|
15
|
+
export declare function buildZellijClipboardKdl(cfg: {
|
|
16
|
+
copy_command: string;
|
|
17
|
+
copy_clipboard: 'system' | 'primary';
|
|
18
|
+
copy_on_select: boolean;
|
|
19
|
+
}): string;
|
|
20
|
+
export declare function writeZellijClipboardConfig(root: string, missionId: string, platform?: NodeJS.Platform): Promise<ZellijClipboardConfig>;
|
|
21
|
+
//# sourceMappingURL=zellij-clipboard-config.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureDir, nowIso, writeTextAtomic } from '../fsx.js';
|
|
3
|
+
// Single source of truth for the Zellij clipboard pipeline used by `sks --mad`
|
|
4
|
+
// (and any other SKS-launched Zellij session). By default Zellij copies via the
|
|
5
|
+
// OSC 52 escape sequence, which several macOS terminals (Terminal.app always,
|
|
6
|
+
// some iTerm2 configs) silently drop — so text selected inside a pane never
|
|
7
|
+
// reaches the system clipboard. Setting copy_command="pbcopy" pipes selections
|
|
8
|
+
// straight to the macOS clipboard, and copy_on_select=true makes a mouse drag
|
|
9
|
+
// copy without entering copy mode. (Shift+drag still falls back to native
|
|
10
|
+
// terminal selection regardless.)
|
|
11
|
+
export const ZELLIJ_CLIPBOARD_CONFIG_SCHEMA = 'sks.zellij-clipboard-config.v1';
|
|
12
|
+
/** Pick the platform-correct clipboard command. macOS is the primary target. */
|
|
13
|
+
export function resolveCopyCommand(platform = process.platform, env = process.env) {
|
|
14
|
+
if (platform === 'darwin')
|
|
15
|
+
return 'pbcopy';
|
|
16
|
+
// Wayland sessions prefer wl-copy; fall back to xclip on X11. pbcopy is darwin-only.
|
|
17
|
+
if (env.WAYLAND_DISPLAY)
|
|
18
|
+
return 'wl-copy';
|
|
19
|
+
return 'xclip -selection clipboard';
|
|
20
|
+
}
|
|
21
|
+
function kdlString(value) {
|
|
22
|
+
return JSON.stringify(String(value || ''));
|
|
23
|
+
}
|
|
24
|
+
/** Render a minimal, valid Zellij config.kdl that enables clipboard copy. */
|
|
25
|
+
export function buildZellijClipboardKdl(cfg) {
|
|
26
|
+
return [
|
|
27
|
+
'// Generated by Sneakoscope (sks) so launched Zellij sessions copy to the OS clipboard.',
|
|
28
|
+
`copy_command ${kdlString(cfg.copy_command)}`,
|
|
29
|
+
`copy_clipboard ${kdlString(cfg.copy_clipboard)}`,
|
|
30
|
+
`copy_on_select ${cfg.copy_on_select ? 'true' : 'false'}`,
|
|
31
|
+
''
|
|
32
|
+
].join('\n');
|
|
33
|
+
}
|
|
34
|
+
export async function writeZellijClipboardConfig(root, missionId, platform = process.platform) {
|
|
35
|
+
const copy_command = resolveCopyCommand(platform);
|
|
36
|
+
const copy_clipboard = 'system';
|
|
37
|
+
const copy_on_select = true;
|
|
38
|
+
const dir = path.join(root, '.sneakoscope', 'missions', missionId);
|
|
39
|
+
await ensureDir(dir);
|
|
40
|
+
const config_path = path.join(dir, 'zellij-clipboard.kdl');
|
|
41
|
+
await writeTextAtomic(config_path, buildZellijClipboardKdl({ copy_command, copy_clipboard, copy_on_select }));
|
|
42
|
+
return {
|
|
43
|
+
copy_command,
|
|
44
|
+
copy_clipboard,
|
|
45
|
+
copy_on_select,
|
|
46
|
+
// Appended AFTER `--default-layout <path>` in the create command. The `options`
|
|
47
|
+
// subcommand sets these on the *created* background session; per Zellij CLI docs
|
|
48
|
+
// the value formats are: --copy-on-select true|false, --copy-clipboard system|primary.
|
|
49
|
+
optionFlags: ['--copy-command', copy_command, '--copy-clipboard', copy_clipboard, '--copy-on-select', String(copy_on_select)],
|
|
50
|
+
config_path,
|
|
51
|
+
generated_at: nowIso()
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=zellij-clipboard-config.js.map
|
|
@@ -44,6 +44,8 @@ export declare function launchZellijLayout(opts?: ZellijLaunchOptions): Promise<
|
|
|
44
44
|
attach_requested: boolean;
|
|
45
45
|
zellij_socket_dir: string | null;
|
|
46
46
|
zellij_socket_dir_source: import("./zellij-command.js").ZellijSocketDirSource;
|
|
47
|
+
clipboard_config_path: string;
|
|
48
|
+
clipboard_copy_command: string;
|
|
47
49
|
pane_proof_path: string;
|
|
48
50
|
pane_proof: {
|
|
49
51
|
schema: string;
|
|
@@ -105,6 +107,8 @@ export declare function launchMadZellijUi(args?: readonly unknown[], opts?: Zell
|
|
|
105
107
|
attach_requested: boolean;
|
|
106
108
|
zellij_socket_dir: string | null;
|
|
107
109
|
zellij_socket_dir_source: import("./zellij-command.js").ZellijSocketDirSource;
|
|
110
|
+
clipboard_config_path: string;
|
|
111
|
+
clipboard_copy_command: string;
|
|
108
112
|
pane_proof_path: string;
|
|
109
113
|
pane_proof: {
|
|
110
114
|
schema: string;
|
|
@@ -166,6 +170,8 @@ export declare function launchTeamZellijView(opts?: ZellijLaunchOptions): Promis
|
|
|
166
170
|
attach_requested: boolean;
|
|
167
171
|
zellij_socket_dir: string | null;
|
|
168
172
|
zellij_socket_dir_source: import("./zellij-command.js").ZellijSocketDirSource;
|
|
173
|
+
clipboard_config_path: string;
|
|
174
|
+
clipboard_copy_command: string;
|
|
169
175
|
pane_proof_path: string;
|
|
170
176
|
pane_proof: {
|
|
171
177
|
schema: string;
|
|
@@ -217,6 +223,7 @@ export interface ZellijAttachResult {
|
|
|
217
223
|
*/
|
|
218
224
|
export declare function attachZellijSessionInteractive(sessionName: string, opts?: {
|
|
219
225
|
cwd?: string;
|
|
226
|
+
configPath?: string;
|
|
220
227
|
}): ZellijAttachResult;
|
|
221
228
|
export declare function sanitizeZellijSessionName(value: unknown): string;
|
|
222
229
|
//# sourceMappingURL=zellij-launcher.d.ts.map
|
|
@@ -4,6 +4,7 @@ import { appendJsonl, nowIso, sha256, writeJsonAtomic } from '../fsx.js';
|
|
|
4
4
|
import { checkZellijCapability } from './zellij-capability.js';
|
|
5
5
|
import { formatZellijCommand, resolveZellijProcessEnvMeta, runZellij } from './zellij-command.js';
|
|
6
6
|
import { writeZellijLayout } from './zellij-layout-builder.js';
|
|
7
|
+
import { writeZellijClipboardConfig } from './zellij-clipboard-config.js';
|
|
7
8
|
import { writeZellijPaneProof } from './zellij-pane-proof.js';
|
|
8
9
|
export const ZELLIJ_SESSION_SCHEMA = 'sks.zellij-session.v1';
|
|
9
10
|
export const ZELLIJ_SESSION_NAME_MAX = 64;
|
|
@@ -27,7 +28,13 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
27
28
|
layoutInput.codexBin = opts.codexBin;
|
|
28
29
|
const layout = await writeZellijLayout(root, layoutInput);
|
|
29
30
|
const capability = await checkZellijCapability({ root, require: opts.requireZellij === true });
|
|
30
|
-
|
|
31
|
+
// Configure the clipboard pipeline so selections inside the session reach the OS
|
|
32
|
+
// clipboard (Zellij's default OSC-52 copy is dropped by Terminal.app etc.). The
|
|
33
|
+
// copy option flags are appended AFTER `--default-layout <path>` so the launch
|
|
34
|
+
// command prefix ['zellij','attach','--create-background',session,'options','--default-layout',...]
|
|
35
|
+
// is preserved (required by the zellij launch-command-truth gate + E2E assertions).
|
|
36
|
+
const clipboard = await writeZellijClipboardConfig(root, missionId);
|
|
37
|
+
const createCommand = ['attach', '--create-background', sessionName, 'options', '--default-layout', layout.layout_path, ...clipboard.optionFlags];
|
|
31
38
|
const attachCommand = ['attach', sessionName];
|
|
32
39
|
const zellijEnv = resolveZellijProcessEnvMeta();
|
|
33
40
|
const launch = opts.dryRun === true || capability.status !== 'ok'
|
|
@@ -86,6 +93,8 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
86
93
|
attach_requested: opts.attach === true,
|
|
87
94
|
zellij_socket_dir: zellijEnv.zellij_socket_dir,
|
|
88
95
|
zellij_socket_dir_source: zellijEnv.zellij_socket_dir_source,
|
|
96
|
+
clipboard_config_path: clipboard.config_path,
|
|
97
|
+
clipboard_copy_command: clipboard.copy_command,
|
|
89
98
|
pane_proof_path: path.join(root, '.sneakoscope', 'missions', missionId, 'zellij-pane-proof.json'),
|
|
90
99
|
pane_proof: paneProof,
|
|
91
100
|
dry_run: opts.dryRun === true,
|
|
@@ -146,6 +155,13 @@ export function attachZellijSessionInteractive(sessionName, opts = {}) {
|
|
|
146
155
|
const env = { ...process.env };
|
|
147
156
|
if (meta.zellij_socket_dir && !env.ZELLIJ_SOCKET_DIR)
|
|
148
157
|
env.ZELLIJ_SOCKET_DIR = meta.zellij_socket_dir;
|
|
158
|
+
// Steer the foreground attach at our generated clipboard config so the interactive
|
|
159
|
+
// session honors copy_command=pbcopy + copy_on_select. The `options` subcommand only
|
|
160
|
+
// configures the *created* background session, so the attach needs its own config
|
|
161
|
+
// delivery; ZELLIJ_CONFIG_FILE avoids reordering CLI args. Defer to a user-exported
|
|
162
|
+
// ZELLIJ_CONFIG_FILE if they already set one.
|
|
163
|
+
if (opts.configPath && !env.ZELLIJ_CONFIG_FILE)
|
|
164
|
+
env.ZELLIJ_CONFIG_FILE = opts.configPath;
|
|
149
165
|
try {
|
|
150
166
|
const result = spawnSync('zellij', ['attach', sessionName], {
|
|
151
167
|
cwd: opts.cwd || process.cwd(),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "1.21.
|
|
4
|
+
"version": "1.21.1",
|
|
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",
|