sneakoscope 6.0.0 → 6.0.2
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 +2 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +0 -40
- package/dist/bin/sks-dispatch.js +0 -16
- package/dist/cli/codex-app-command.js +1 -2
- package/dist/cli/help-fast.js +2 -2
- package/dist/cli/install-helpers.js +26 -21
- package/dist/config/skills-manifest.json +57 -57
- package/dist/core/agents/agent-effort-policy.js +19 -38
- package/dist/core/agents/agent-orchestrator.js +2 -2
- package/dist/core/agents/agent-role-config.js +17 -10
- package/dist/core/agents/agent-roster.js +19 -39
- package/dist/core/agents/agent-scheduler.js +6 -5
- package/dist/core/agents/agent-schema.js +4 -5
- package/dist/core/agents/agent-task-graph.js +1 -1
- package/dist/core/agents/agent-work-partition.js +2 -1
- package/dist/core/agents/native-worker-backend-router.js +1 -1
- package/dist/core/agents/route-collaboration-ledger.js +2 -2
- package/dist/core/auto-review.js +1 -4
- package/dist/core/codex/codex-config-guard.js +11 -9
- package/dist/core/codex-adapter.js +2 -2
- package/dist/core/codex-app/codex-agent-role-sync.js +0 -1
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +1 -7
- package/dist/core/codex-app.js +0 -2
- package/dist/core/codex-control/codex-model-capabilities.js +1 -2
- package/dist/core/codex-control/codex-model-metadata.js +13 -6
- package/dist/core/codex-control/codex-sdk-config-policy.js +3 -3
- package/dist/core/codex-control/codex-task-runner.js +2 -3
- package/dist/core/codex-model-guard.js +24 -89
- package/dist/core/commands/basic-cli.js +6 -0
- package/dist/core/commands/loop-command.js +2 -2
- package/dist/core/commands/mad-db-command.js +1 -1
- package/dist/core/commands/naruto-command.js +11 -11
- package/dist/core/commands/profile-command.js +14 -5
- package/dist/core/commands/qa-loop-command.js +2 -2
- package/dist/core/commands/research-command.js +2 -2
- package/dist/core/doctor/doctor-codex-startup-repair.js +0 -3
- package/dist/core/evaluation.js +1 -1
- package/dist/core/feature-fixtures.js +2 -2
- package/dist/core/feature-registry.js +1 -1
- package/dist/core/hooks-runtime.js +7 -45
- package/dist/core/image-ux-review/imagegen-adapter.js +17 -2
- package/dist/core/init/skills.js +6 -7
- package/dist/core/init.js +7 -20
- package/dist/core/loops/loop-concurrency-budget.js +6 -4
- package/dist/core/loops/loop-planner.js +1 -1
- package/dist/core/loops/loop-runtime.js +2 -2
- package/dist/core/loops/loop-scheduler.js +7 -7
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -4
- package/dist/core/naruto/naruto-concurrency-governor.js +20 -17
- package/dist/core/naruto/naruto-loop-mesh.js +3 -3
- package/dist/core/naruto/resource-pressure-monitor.js +5 -2
- package/dist/core/provider/model-router.js +15 -35
- package/dist/core/release/release-gate-batch-runner.js +2 -1
- package/dist/core/release/release-gate-resource-governor.js +12 -12
- package/dist/core/release/resource-class-budget.js +5 -5
- package/dist/core/routes/constants.js +3 -4
- package/dist/core/routes.js +1 -1
- package/dist/core/structured-output-adapter.js +15 -1
- package/dist/core/update/update-migration-state.js +3 -16
- package/dist/core/version.js +1 -1
- package/dist/scripts/agent-backfill-replenishment-check.js +1 -1
- package/dist/scripts/agent-dynamic-pool-check.js +3 -3
- package/dist/scripts/agent-dynamic-pool-fixture.js +1 -1
- package/dist/scripts/agent-goal-mode-propagation-check.js +1 -1
- package/dist/scripts/agent-patch-swarm-gate-lib.js +2 -2
- package/dist/scripts/agent-role-config-repair-check.js +9 -5
- package/dist/scripts/agent-scheduler-proof-check.js +1 -1
- package/dist/scripts/agent-scheduler-proof-hardening-check.js +1 -1
- package/dist/scripts/agent-source-intelligence-propagation-check.js +1 -1
- package/dist/scripts/agent-strategy-to-lease-wiring-check.js +2 -2
- package/dist/scripts/agent-zellij-dynamic-backfill-panes-check.js +1 -1
- package/dist/scripts/codex-app-fast-ui-preservation-check.js +2 -2
- package/dist/scripts/codex-app-ui-preservation-check.js +6 -5
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +4 -4
- package/dist/scripts/codex-lb-gpt55-fast-profile-check.js +3 -2
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +6 -6
- package/dist/scripts/install-update-preserves-config-check.js +3 -3
- package/dist/scripts/loop-hardening-check-lib.js +1 -1
- package/dist/scripts/naruto-parallelism-mode-check.js +2 -1
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +25 -32
- package/dist/scripts/npm-publish-performance-check.js +1 -1
- package/dist/scripts/release-aggressive-resource-governor-check.js +2 -2
- package/dist/scripts/release-parallel-check.js +1 -2
- package/dist/scripts/release-real-check.js +1 -1
- package/dist/scripts/sizecheck.js +3 -3
- package/package.json +3 -2
|
@@ -2,7 +2,7 @@ import { probeHardwareCapacity } from './hardware-capacity-probe.js';
|
|
|
2
2
|
import { applyNarutoBackpressure } from './naruto-backpressure.js';
|
|
3
3
|
import { monitorNarutoResourcePressure } from './resource-pressure-monitor.js';
|
|
4
4
|
export function decideNarutoConcurrency(input = {}) {
|
|
5
|
-
const requestedClones = normalizePositiveInt(input.requestedClones,
|
|
5
|
+
const requestedClones = normalizePositiveInt(input.requestedClones, 8);
|
|
6
6
|
const totalWorkItems = normalizePositiveInt(input.totalWorkItems, requestedClones);
|
|
7
7
|
const pending = normalizeNonNegativeInt(input.pendingWorkQueueSize, totalWorkItems);
|
|
8
8
|
const leaseConflicts = normalizeNonNegativeInt(input.activeLeaseConflicts, 0);
|
|
@@ -12,34 +12,37 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
12
12
|
const parallelismMode = normalizeParallelismMode(input.parallelismMode);
|
|
13
13
|
const freeGb = hardware.free_memory_bytes / (1024 * 1024 * 1024);
|
|
14
14
|
const totalGb = hardware.total_memory_bytes / (1024 * 1024 * 1024);
|
|
15
|
-
const
|
|
16
|
-
const memoryBudgetGb = Math.max(
|
|
15
|
+
const reservedInteractiveGb = Math.max(2, totalGb * 0.2);
|
|
16
|
+
const memoryBudgetGb = Math.max(0.5, freeGb - reservedInteractiveGb);
|
|
17
17
|
const heavy = backend === 'codex-sdk' || backend === 'zellij' || backend === 'process' || backend === 'ollama';
|
|
18
|
-
const gbPerWorker = heavy ? Number(process.env.SKS_NARUTO_GB_PER_WORKER ||
|
|
19
|
-
const memoryCap = Math.max(1, Math.floor(memoryBudgetGb / Math.max(0.
|
|
18
|
+
const gbPerWorker = heavy ? Number(process.env.SKS_NARUTO_GB_PER_WORKER || 1.5) : Number(process.env.SKS_NARUTO_LIGHT_GB_PER_WORKER || 0.5);
|
|
19
|
+
const memoryCap = Math.max(1, Math.floor(memoryBudgetGb / Math.max(0.25, gbPerWorker)));
|
|
20
20
|
const fdCap = Math.max(1, Math.floor((hardware.file_descriptor_limit - hardware.process_count) / 6));
|
|
21
|
-
const cpuCap = Math.max(1, hardware.cpu_core_count * (heavy ?
|
|
22
|
-
const ioCap = Math.max(2, Math.floor(hardware.cpu_core_count /
|
|
23
|
-
const
|
|
21
|
+
const cpuCap = Math.max(1, Math.min(4, Math.floor(hardware.cpu_core_count * (heavy ? 0.4 : 0.5))));
|
|
22
|
+
const ioCap = Math.max(1, Math.min(2, Math.floor(hardware.cpu_core_count / 4)));
|
|
23
|
+
const configuredProcessCap = Math.max(1, Number(process.env.SKS_NARUTO_HEADLESS_PROCESS_CAP || 4));
|
|
24
|
+
const processCap = Math.min(configuredProcessCap, cpuCap, 4);
|
|
24
25
|
const gitWorktreeCap = Math.max(1, Number(process.env.SKS_NARUTO_GIT_WORKTREE_CAP || Math.min(requestedClones, processCap)));
|
|
25
26
|
const localLlmParallel = Math.max(1, Math.min(4, hardware.local_llm_max_parallel_requests));
|
|
26
27
|
const remoteCodexParallel = Math.max(1, Math.min(hardware.remote_api_rate_limit_budget, requestedClones));
|
|
27
28
|
const backendBudget = backend === 'ollama' || backend === 'local-llm'
|
|
28
29
|
? localLlmParallel
|
|
29
30
|
: backend === 'codex-sdk' || backend === 'zellij'
|
|
30
|
-
? Math.
|
|
31
|
+
? Math.min(remoteCodexParallel, processCap)
|
|
31
32
|
: processCap;
|
|
32
33
|
const queueCap = Math.max(1, Math.min(requestedClones, pending || totalWorkItems));
|
|
33
34
|
const leaseCap = Math.max(1, requestedClones - leaseConflicts);
|
|
34
|
-
const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, cpuCap +
|
|
35
|
+
const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, cpuCap, ioCap + 1, gitWorktreeCap, processCap, backendBudget, queueCap, leaseCap, 4));
|
|
35
36
|
const pressure = monitorNarutoResourcePressure(hardware, { activeWorkers: rawSafe, zellijVisiblePaneCap });
|
|
36
37
|
const backpressure = applyNarutoBackpressure(rawSafe, pressure);
|
|
37
38
|
const currentSafeActiveWorkers = Math.max(1, Math.min(rawSafe, backpressure.adjusted_active_workers));
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
// Every mode respects live backpressure. Modes only lower the bounded cap.
|
|
40
|
+
const modeCap = parallelismMode === 'safe'
|
|
41
|
+
? Math.max(1, Math.ceil(rawSafe * 0.5))
|
|
40
42
|
: parallelismMode === 'balanced'
|
|
41
|
-
? Math.max(1, Math.
|
|
42
|
-
:
|
|
43
|
+
? Math.max(1, Math.ceil(rawSafe * 0.75))
|
|
44
|
+
: rawSafe;
|
|
45
|
+
const safeActiveWorkers = Math.max(1, Math.min(modeCap, currentSafeActiveWorkers));
|
|
43
46
|
const safeVisible = Math.min(safeActiveWorkers, zellijVisiblePaneCap);
|
|
44
47
|
const reasons = [
|
|
45
48
|
...(memoryCap < requestedClones ? ['memory_cap'] : []),
|
|
@@ -65,7 +68,7 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
65
68
|
process_parallel: processCap,
|
|
66
69
|
git_worktree_parallel: gitWorktreeCap,
|
|
67
70
|
cpu_io_parallel: cpuCap + ioCap,
|
|
68
|
-
verification_parallel: Math.max(1, Math.min(
|
|
71
|
+
verification_parallel: Math.max(1, Math.min(2, safeActiveWorkers)),
|
|
69
72
|
parallelism_mode: parallelismMode,
|
|
70
73
|
reasons: [...new Set(reasons)],
|
|
71
74
|
backpressure: backpressure.backpressure,
|
|
@@ -73,10 +76,10 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
function normalizeParallelismMode(value) {
|
|
76
|
-
const text = String(value || process.env.SKS_NARUTO_PARALLELISM || '
|
|
79
|
+
const text = String(value || process.env.SKS_NARUTO_PARALLELISM || 'safe').toLowerCase();
|
|
77
80
|
if (text === 'safe' || text === 'balanced' || text === 'extreme')
|
|
78
81
|
return text;
|
|
79
|
-
return '
|
|
82
|
+
return 'safe';
|
|
80
83
|
}
|
|
81
84
|
function normalizePositiveInt(value, fallback) {
|
|
82
85
|
const parsed = Number(value);
|
|
@@ -27,10 +27,10 @@ export async function runNarutoLoopMesh(input) {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
export function splitActiveWorkerBudget(plan, parallelism) {
|
|
30
|
-
const cap = parallelism === 'safe' ?
|
|
31
|
-
const integrationReserved =
|
|
30
|
+
const cap = parallelism === 'safe' ? 2 : parallelism === 'extreme' ? 4 : 3;
|
|
31
|
+
const integrationReserved = 1;
|
|
32
32
|
const nonIntegration = plan.graph.nodes.filter((node) => node.route !== '$Integration');
|
|
33
|
-
const perLoopCap = Math.max(
|
|
33
|
+
const perLoopCap = Math.max(1, Math.floor((cap - integrationReserved) / Math.max(1, nonIntegration.length)));
|
|
34
34
|
const perLoop = nonIntegration.map((node) => ({
|
|
35
35
|
loop_id: node.loop_id,
|
|
36
36
|
maker_checker_workers: Math.min(perLoopCap, node.maker.worker_count + node.checker.worker_count)
|
|
@@ -8,13 +8,16 @@ export function monitorNarutoResourcePressure(probe, input = {}) {
|
|
|
8
8
|
const fdPressure = Math.min(1, (activeWorkers * 6 + probe.process_count) / Math.max(1, probe.file_descriptor_limit));
|
|
9
9
|
const zellijPressure = Math.min(1, probe.zellij_pane_count / zellijCap);
|
|
10
10
|
const diskIoPressure = Number(probe.disk_io_pressure || 0);
|
|
11
|
-
const
|
|
11
|
+
const current = {
|
|
12
12
|
memory: memoryPressure,
|
|
13
13
|
cpu: cpuPressure,
|
|
14
14
|
fd: fdPressure,
|
|
15
15
|
zellij: zellijPressure,
|
|
16
16
|
disk: diskIoPressure
|
|
17
|
-
}
|
|
17
|
+
};
|
|
18
|
+
const average = movingAveragePressure(current);
|
|
19
|
+
const pressureKeys = Object.keys(current);
|
|
20
|
+
const sample = Object.fromEntries(pressureKeys.map((key) => [key, Math.max(current[key] || 0, average[key] || 0)]));
|
|
18
21
|
const entries = Object.entries(sample).sort((a, b) => b[1] - a[1]);
|
|
19
22
|
const [dominantMetric = 'unknown', dominantPressure = 0] = entries[0] || [];
|
|
20
23
|
const reasons = entries
|
|
@@ -1,37 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ model: REQUIRED_CODEX_MODEL, reasoning: 'medium', serviceTier: 'fast' }
|
|
9
|
-
],
|
|
10
|
-
agentic: [
|
|
11
|
-
{ model: 'gpt-5.3-codex', reasoning: 'high', serviceTier: 'fast' },
|
|
12
|
-
{ model: REQUIRED_CODEX_MODEL, reasoning: 'high', serviceTier: 'fast' }
|
|
13
|
-
],
|
|
14
|
-
ultrabrain: [
|
|
15
|
-
{ model: REQUIRED_CODEX_MODEL, reasoning: 'xhigh', serviceTier: 'standard' }
|
|
16
|
-
],
|
|
17
|
-
verify: [
|
|
18
|
-
{ model: 'gpt-5.4-mini', reasoning: 'medium', serviceTier: 'fast' }
|
|
19
|
-
],
|
|
20
|
-
review: [
|
|
21
|
-
{ model: REQUIRED_CODEX_MODEL, reasoning: 'high', serviceTier: 'fast' }
|
|
22
|
-
]
|
|
1
|
+
const CATEGORY_POLICY = {
|
|
2
|
+
quick: { reasoning: 'low', serviceTier: 'fast' },
|
|
3
|
+
standard: { reasoning: 'medium', serviceTier: 'fast' },
|
|
4
|
+
agentic: { reasoning: 'high', serviceTier: 'fast' },
|
|
5
|
+
ultrabrain: { reasoning: 'xhigh', serviceTier: 'standard' },
|
|
6
|
+
verify: { reasoning: 'medium', serviceTier: 'fast' },
|
|
7
|
+
review: { reasoning: 'high', serviceTier: 'fast' }
|
|
23
8
|
};
|
|
24
9
|
export async function routeModel(category, opts = {}) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
continue;
|
|
30
|
-
if (opts.lbHealth?.quota_low && choice.reasoning === 'xhigh')
|
|
31
|
-
return { ...choice, reasoning: 'high' };
|
|
32
|
-
return choice;
|
|
33
|
-
}
|
|
34
|
-
return chain[chain.length - 1] ?? CHAINS.standard[0];
|
|
10
|
+
const policy = CATEGORY_POLICY[category] || CATEGORY_POLICY.standard;
|
|
11
|
+
const model = String(opts.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || '').trim();
|
|
12
|
+
const reasoning = opts.lbHealth?.quota_low && policy.reasoning === 'xhigh' ? 'high' : policy.reasoning;
|
|
13
|
+
return { model, reasoning, serviceTier: policy.serviceTier };
|
|
35
14
|
}
|
|
36
15
|
export function categoryForWorkerRole(role) {
|
|
37
16
|
const text = String(role || '').toLowerCase();
|
|
@@ -46,9 +25,10 @@ export function categoryForWorkerRole(role) {
|
|
|
46
25
|
return 'agentic';
|
|
47
26
|
}
|
|
48
27
|
export function modelRouteReason(category, choice, opts = {}) {
|
|
28
|
+
const model = choice.model || 'codex-selected';
|
|
49
29
|
if (opts.explicit)
|
|
50
|
-
return `${category}->${
|
|
51
|
-
const suffix = opts.quotaLow ? 'quota discipline' :
|
|
52
|
-
return `${category}->${
|
|
30
|
+
return `${category}->${model} (explicit model preserved)`;
|
|
31
|
+
const suffix = opts.quotaLow ? 'quota discipline' : 'Codex catalog passthrough';
|
|
32
|
+
return `${category}->${model} (${suffix})`;
|
|
53
33
|
}
|
|
54
34
|
//# sourceMappingURL=model-router.js.map
|
|
@@ -13,7 +13,8 @@ export function isReleaseGateBatchable(gate) {
|
|
|
13
13
|
return gate.resource.every((resource) => resource === 'cpu-light' || resource === 'fs-read') && !gate.resource.some((resource) => DISALLOWED_BATCH_RESOURCES.has(resource));
|
|
14
14
|
}
|
|
15
15
|
export async function runReleaseGateBatch(root, gates, input = {}) {
|
|
16
|
-
const
|
|
16
|
+
const requestedConcurrency = Math.max(1, Math.floor(Number(input.concurrency || process.env.SKS_RELEASE_BATCH_CONCURRENCY || 2)));
|
|
17
|
+
const concurrency = Math.min(requestedConcurrency, 4);
|
|
17
18
|
const runId = `rgb-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
|
|
18
19
|
const reportRoot = input.reportRoot || path.join(root, '.sneakoscope', 'reports', 'release-gate-batches', runId);
|
|
19
20
|
const nonBatchable = gates.filter((gate) => !isReleaseGateBatchable(gate));
|
|
@@ -3,23 +3,23 @@ const EXCLUSIVE_RESOURCES = new Set(['timing-sensitive']);
|
|
|
3
3
|
export function defaultReleaseGateBudget() {
|
|
4
4
|
const cores = Math.max(1, os.cpus().length || 1);
|
|
5
5
|
const base = {
|
|
6
|
-
'cpu-light': Math.min(
|
|
7
|
-
'cpu-heavy': Math.max(1, cores),
|
|
8
|
-
'io-light':
|
|
9
|
-
'io-heavy':
|
|
10
|
-
git:
|
|
11
|
-
'git-worktree':
|
|
12
|
-
python:
|
|
13
|
-
network:
|
|
6
|
+
'cpu-light': Math.max(1, Math.min(4, Math.floor(cores / 2))),
|
|
7
|
+
'cpu-heavy': Math.max(1, Math.min(2, Math.floor(cores / 4))),
|
|
8
|
+
'io-light': 4,
|
|
9
|
+
'io-heavy': 2,
|
|
10
|
+
git: 2,
|
|
11
|
+
'git-worktree': 1,
|
|
12
|
+
python: 2,
|
|
13
|
+
network: 2,
|
|
14
14
|
'zellij-real': 1,
|
|
15
15
|
'browser-real': 1,
|
|
16
16
|
'secret-sensitive': 1,
|
|
17
17
|
'local-llm-real': Math.max(1, Number(process.env.SKS_LOCAL_LLM_MAX_PARALLEL || 1)),
|
|
18
|
-
'remote-model-real':
|
|
18
|
+
'remote-model-real': 2,
|
|
19
19
|
'global-config': 1,
|
|
20
20
|
publish: 1,
|
|
21
|
-
'fs-read':
|
|
22
|
-
'fs-write':
|
|
21
|
+
'fs-read': 4,
|
|
22
|
+
'fs-write': 2,
|
|
23
23
|
'timing-sensitive': 1
|
|
24
24
|
};
|
|
25
25
|
for (const key of Object.keys(base)) {
|
|
@@ -64,7 +64,7 @@ export function pickLaunchableReleaseGates(input) {
|
|
|
64
64
|
}
|
|
65
65
|
export function defaultReleaseGateMaxTotal() {
|
|
66
66
|
const cores = Math.max(1, os.cpus().length || 1);
|
|
67
|
-
return Math.max(
|
|
67
|
+
return Math.max(1, Math.min(4, Math.floor(cores / 2)));
|
|
68
68
|
}
|
|
69
69
|
function envInt(name, fallback, opts = {}) {
|
|
70
70
|
const parsed = Number(process.env[name]);
|
|
@@ -4,11 +4,11 @@ export function computeResourceClassBudget(env = process.env) {
|
|
|
4
4
|
const cpus = Math.max(2, os.cpus().length || 2);
|
|
5
5
|
return {
|
|
6
6
|
schema: RESOURCE_CLASS_BUDGET_SCHEMA,
|
|
7
|
-
cpu_light: readEnvInt(env, 'SKS_RESOURCE_CPU_LIGHT', Math.max(
|
|
8
|
-
cpu_heavy: readEnvInt(env, 'SKS_RESOURCE_CPU_HEAVY', Math.max(1, Math.floor(cpus /
|
|
9
|
-
io_light: readEnvInt(env, 'SKS_RESOURCE_IO_LIGHT',
|
|
7
|
+
cpu_light: readEnvInt(env, 'SKS_RESOURCE_CPU_LIGHT', Math.max(1, Math.min(4, Math.floor(cpus / 2)))),
|
|
8
|
+
cpu_heavy: readEnvInt(env, 'SKS_RESOURCE_CPU_HEAVY', Math.max(1, Math.min(2, Math.floor(cpus / 4)))),
|
|
9
|
+
io_light: readEnvInt(env, 'SKS_RESOURCE_IO_LIGHT', 4),
|
|
10
10
|
io_heavy: readEnvInt(env, 'SKS_RESOURCE_IO_HEAVY', 2),
|
|
11
|
-
fs_read: readEnvInt(env, 'SKS_RESOURCE_FS_READ',
|
|
11
|
+
fs_read: readEnvInt(env, 'SKS_RESOURCE_FS_READ', 4),
|
|
12
12
|
network: readEnvInt(env, 'SKS_RESOURCE_NETWORK', 2),
|
|
13
13
|
remote_model_real: readEnvInt(env, 'SKS_RESOURCE_REMOTE_MODEL_REAL', 1),
|
|
14
14
|
zellij_real: readEnvInt(env, 'SKS_RESOURCE_ZELLIJ_REAL', 1),
|
|
@@ -18,6 +18,6 @@ export function computeResourceClassBudget(env = process.env) {
|
|
|
18
18
|
}
|
|
19
19
|
function readEnvInt(env, key, fallback) {
|
|
20
20
|
const value = Number(env[key]);
|
|
21
|
-
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
21
|
+
return Number.isFinite(value) && value > 0 ? Math.min(Math.floor(value), fallback) : fallback;
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=resource-class-budget.js.map
|
|
@@ -44,9 +44,8 @@ export const RECOMMENDED_SKILLS = [
|
|
|
44
44
|
REFLECTION_SKILL_NAME,
|
|
45
45
|
'honest-mode'
|
|
46
46
|
];
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// this set only gates what values SKS accepts as user/route input.
|
|
47
|
+
// Route-level effort names are scheduling intents, not model compatibility
|
|
48
|
+
// claims. Actual per-model options come from Codex runtime metadata; SKS never
|
|
49
|
+
// derives them from a model slug.
|
|
51
50
|
export const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'ultra']);
|
|
52
51
|
//# sourceMappingURL=constants.js.map
|
package/dist/core/routes.js
CHANGED
|
@@ -296,7 +296,7 @@ export const ROUTES = [
|
|
|
296
296
|
reasoningPolicy: 'high',
|
|
297
297
|
stopGate: 'release-readiness-report.json',
|
|
298
298
|
cliEntrypoint: 'sks agent run "release audit" --route "$Release-Review" --agents <n> --concurrency <n> --mock --json',
|
|
299
|
-
examples: ['$Release-Review agents:10 concurrency:
|
|
299
|
+
examples: ['$Release-Review agents:10 concurrency:4 release audit', 'sks agent run "wide release audit" --route "$Release-Review" --agents 10 --concurrency 4 --mock --json']
|
|
300
300
|
},
|
|
301
301
|
{
|
|
302
302
|
id: 'QALoop',
|
|
@@ -29,9 +29,23 @@ export function ensureStrictObjectSchema(schema) {
|
|
|
29
29
|
return next;
|
|
30
30
|
}
|
|
31
31
|
export async function runOpenAIStructuredOutput(request) {
|
|
32
|
-
const model = request.model || process.env.OPENAI_STRUCTURED_OUTPUT_MODEL || '
|
|
32
|
+
const model = String(request.model || process.env.OPENAI_STRUCTURED_OUTPUT_MODEL || '').trim();
|
|
33
33
|
const apiKey = request.apiKey || process.env.OPENAI_API_KEY || null;
|
|
34
34
|
const sourceSha = request.imagePath ? sha256(await fsp.readFile(path.resolve(request.imagePath))) : null;
|
|
35
|
+
if (!model) {
|
|
36
|
+
return {
|
|
37
|
+
schema: 'sks.structured-output-adapter-result.v1',
|
|
38
|
+
ok: false,
|
|
39
|
+
status: 'integration_optional',
|
|
40
|
+
provider: 'openai_responses_text_format',
|
|
41
|
+
model: '',
|
|
42
|
+
parsed_json: null,
|
|
43
|
+
validation: { ok: false, issues: ['openai_structured_output_model_missing'] },
|
|
44
|
+
blocker: structuredOutputBlocker('openai_structured_output_model_missing', 'Pass request.model or set OPENAI_STRUCTURED_OUTPUT_MODEL; SKS does not invent a model identifier.'),
|
|
45
|
+
setup_guidance: 'Choose any model available to your OpenAI account and pass it explicitly, or use Codex structured output so the current Codex selection is inherited.',
|
|
46
|
+
source_sha256: sourceSha
|
|
47
|
+
};
|
|
48
|
+
}
|
|
35
49
|
if (!apiKey) {
|
|
36
50
|
return {
|
|
37
51
|
schema: 'sks.structured-output-adapter-result.v1',
|
|
@@ -8,7 +8,6 @@ import { COMMANDS } from '../../cli/command-registry.js';
|
|
|
8
8
|
import { reconcileSkills } from '../init/skills.js';
|
|
9
9
|
import { codexHookTrustDoctor } from '../codex-hooks/codex-hook-trust-doctor.js';
|
|
10
10
|
import { writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
|
|
11
|
-
import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
|
|
12
11
|
export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
|
|
13
12
|
export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
|
|
14
13
|
export function installationEpochPath() {
|
|
@@ -528,7 +527,9 @@ function tomlTableString(text, table, key) {
|
|
|
528
527
|
return match?.[1] || null;
|
|
529
528
|
}
|
|
530
529
|
function topLevelTomlString(text, key) {
|
|
531
|
-
const
|
|
530
|
+
const source = String(text || '');
|
|
531
|
+
const firstTable = source.search(/^\s*\[/m);
|
|
532
|
+
const top = firstTable < 0 ? source : source.slice(0, firstTable);
|
|
532
533
|
const match = top.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
|
|
533
534
|
return match?.[1] || null;
|
|
534
535
|
}
|
|
@@ -589,20 +590,6 @@ function upsertTomlTableKeyLocal(text, table, line) {
|
|
|
589
590
|
lines.splice(end, 0, line);
|
|
590
591
|
return `${lines.join('\n')}\n`;
|
|
591
592
|
}
|
|
592
|
-
function upsertTopLevelTomlStringLocal(text, key, value) {
|
|
593
|
-
const raw = String(text || '').trimEnd();
|
|
594
|
-
const line = `${key} = ${JSON.stringify(value)}`;
|
|
595
|
-
const lines = raw ? raw.split(/\r?\n/) : [];
|
|
596
|
-
const firstTable = lines.findIndex((candidate) => /^\s*\[[^\]]+\]\s*$/.test(candidate || ''));
|
|
597
|
-
const end = firstTable < 0 ? lines.length : firstTable;
|
|
598
|
-
for (let index = 0; index < end; index += 1) {
|
|
599
|
-
if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[index] || '')) {
|
|
600
|
-
lines[index] = line;
|
|
601
|
-
return `${lines.join('\n')}\n`;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return insertTopLevelTomlKey(raw, line);
|
|
605
|
-
}
|
|
606
593
|
function tomlTableHasKey(text, table, key) {
|
|
607
594
|
const block = tomlTableBlock(text, table);
|
|
608
595
|
return Boolean(block && new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`, 'm').test(block));
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '6.0.
|
|
1
|
+
export const PACKAGE_VERSION = '6.0.2';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
4
4
|
import { runDynamicPoolFixture } from './agent-dynamic-pool-fixture.js';
|
|
5
|
-
const fixture = await runDynamicPoolFixture({ target:
|
|
5
|
+
const fixture = await runDynamicPoolFixture({ target: 4, total: 8 });
|
|
6
6
|
const backfills = fixture.events.filter((event) => event.event_type === 'backfill_event');
|
|
7
7
|
const firstSlowCompletionIndex = fixture.events.findIndex((event) => event.event_type === 'session_completed' && /work-00[3-5]/.test(String(event.work_item_id || '')));
|
|
8
8
|
const secondBackfillIndex = fixture.events.findIndex((event, index) => index > fixture.events.findIndex((row) => row.event_type === 'backfill_event') && event.event_type === 'backfill_event');
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
4
4
|
import { runDynamicPoolFixture } from './agent-dynamic-pool-fixture.js';
|
|
5
|
-
const fixture = await runDynamicPoolFixture({ target:
|
|
5
|
+
const fixture = await runDynamicPoolFixture({ target: 4, total: 8 });
|
|
6
6
|
const state = fixture.result.state;
|
|
7
7
|
assertGate(fixture.result.ok === true, 'dynamic pool fixture must drain cleanly', state);
|
|
8
|
-
assertGate(state.target_active_slots ===
|
|
9
|
-
assertGate(state.max_observed_active_slots ===
|
|
8
|
+
assertGate(state.target_active_slots === 4, 'target active slots must be desktop-safe (4)', state);
|
|
9
|
+
assertGate(state.max_observed_active_slots === 4, 'scheduler must observe at most 4 active slots', state);
|
|
10
10
|
assertGate(state.pending_queue_drained === true, 'pending queue must drain', state);
|
|
11
11
|
assertGate(state.all_slots_closed_after_drain === true, 'all slots must close after drain', state);
|
|
12
12
|
emitGate('agent:dynamic-pool', { target_active_slots: state.target_active_slots, max_observed_active_slots: state.max_observed_active_slots, total_work_items: state.total_work_items });
|
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { importDist } from './sks-1-18-gate-lib.js';
|
|
7
7
|
export async function runDynamicPoolFixture(opts = {}) {
|
|
8
|
-
const target = opts.target ||
|
|
8
|
+
const target = opts.target || 4;
|
|
9
9
|
const total = opts.total || 8;
|
|
10
10
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-dynamic-pool-'));
|
|
11
11
|
const scheduler = await importDist('core/agents/agent-scheduler.js');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
4
4
|
import { runDynamicPoolFixture } from './agent-dynamic-pool-fixture.js';
|
|
5
|
-
const fixture = await runDynamicPoolFixture({ target:
|
|
5
|
+
const fixture = await runDynamicPoolFixture({ target: 4, total: 8 });
|
|
6
6
|
const allResultsHaveRefs = fixture.result.results.every((result) => result.goal_mode_ref?.artifact === 'goal-mode-applied.json');
|
|
7
7
|
assertGate(allResultsHaveRefs, 'all dynamic generations must propagate Goal mode refs', fixture.result.results);
|
|
8
8
|
emitGate('agent:goal-mode-propagation', { result_count: fixture.result.results.length });
|
|
@@ -37,8 +37,8 @@ export async function runPatchSwarmRouteBlackbox({ gate, route, routeCommand, re
|
|
|
37
37
|
backend: 'fake',
|
|
38
38
|
mock: true,
|
|
39
39
|
agents: 5,
|
|
40
|
-
concurrency:
|
|
41
|
-
targetActiveSlots:
|
|
40
|
+
concurrency: 4,
|
|
41
|
+
targetActiveSlots: 4,
|
|
42
42
|
desiredWorkItemCount: 10,
|
|
43
43
|
minimumWorkItems: 10,
|
|
44
44
|
writeMode: 'parallel',
|
|
@@ -5,8 +5,6 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
7
|
const mod = await importDist('core/agents/agent-role-config.js');
|
|
8
|
-
const modelGuard = await importDist('core/codex-model-guard.js');
|
|
9
|
-
const requiredModel = modelGuard.REQUIRED_CODEX_MODEL;
|
|
10
8
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-role-repair-'));
|
|
11
9
|
fs.mkdirSync(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
|
|
12
10
|
const plan = await mod.repairAgentRoleConfigs({ root, apply: false, codexHome: path.join(root, 'codex-home') });
|
|
@@ -19,19 +17,25 @@ const stalePlan = await mod.repairAgentRoleConfigs({ root: staleRoot, apply: fal
|
|
|
19
17
|
const staleRepair = await mod.repairAgentRoleConfigs({ root: staleRoot, apply: true, codexHome: path.join(staleRoot, 'codex-home') });
|
|
20
18
|
const repairedText = fs.readFileSync(path.join(staleRoot, '.codex', 'agents', 'analysis-scout.toml'), 'utf8');
|
|
21
19
|
const createdText = fs.readFileSync(analysisScout, 'utf8');
|
|
20
|
+
const globalRole = path.join(root, 'codex-home', 'agents', 'analysis-scout.toml');
|
|
21
|
+
fs.mkdirSync(path.dirname(globalRole), { recursive: true });
|
|
22
|
+
fs.writeFileSync(globalRole, 'name = "analysis_scout"\ndescription = "SKS stale global role"\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "low"\nsandbox_mode = "workspace-write"\ndeveloper_instructions = """\nSKS role\n"""\n');
|
|
23
|
+
const globalRepair = await mod.repairAgentRoleConfigs({ root, apply: true, codexHome: path.join(root, 'codex-home') });
|
|
24
|
+
const globalRepairedText = fs.readFileSync(globalRole, 'utf8');
|
|
22
25
|
const report = {
|
|
23
26
|
schema: 'sks.agent-role-config-repair-check.v1',
|
|
24
27
|
plan_ok: plan.ok === true && plan.missing.includes('analysis-scout.toml'),
|
|
25
28
|
repair_ok: repair.ok === true,
|
|
26
29
|
analysis_scout_created: fs.existsSync(analysisScout),
|
|
27
|
-
|
|
30
|
+
created_inherits_model: !/^(?:model|model_reasoning_effort)\s*=/m.test(createdText),
|
|
28
31
|
created_write_capable: createdText.includes('sandbox_mode = "workspace-write"') && !createdText.includes('Do not edit files.'),
|
|
29
32
|
stale_detected: stalePlan.stale.includes('analysis-scout.toml'),
|
|
30
|
-
stale_repaired: staleRepair.repaired.includes('.codex/agents/analysis-scout.toml') && repairedText.includes('name = "analysis_scout"') &&
|
|
33
|
+
stale_repaired: staleRepair.repaired.includes('.codex/agents/analysis-scout.toml') && repairedText.includes('name = "analysis_scout"') && !/^(?:model|model_reasoning_effort)\s*=/m.test(repairedText) && repairedText.includes('sandbox_mode = "workspace-write"'),
|
|
34
|
+
global_stale_repaired_with_valid_project_copy: globalRepair.repaired.some((file) => file.endsWith('codex-home/agents/analysis-scout.toml')) && !/^(?:model|model_reasoning_effort)\s*=/m.test(globalRepairedText),
|
|
31
35
|
warnings_suppressed: repair.warnings_suppressed === true,
|
|
32
36
|
artifact_written: fs.existsSync(path.join(root, '.sneakoscope', 'reports', 'agent-role-config-repair.json'))
|
|
33
37
|
};
|
|
34
|
-
const ok = report.plan_ok && report.repair_ok && report.analysis_scout_created && report.
|
|
38
|
+
const ok = report.plan_ok && report.repair_ok && report.analysis_scout_created && report.created_inherits_model && report.created_write_capable && report.stale_detected && report.stale_repaired && report.global_stale_repaired_with_valid_project_copy && report.warnings_suppressed && report.artifact_written;
|
|
35
39
|
assertGate(ok, 'doctor --fix must repair missing SKS-owned agent role configs', report);
|
|
36
40
|
emitGate('agent:role-config-repair', report);
|
|
37
41
|
//# sourceMappingURL=agent-role-config-repair-check.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
4
4
|
import { runDynamicPoolFixture } from './agent-dynamic-pool-fixture.js';
|
|
5
|
-
const fixture = await runDynamicPoolFixture({ target:
|
|
5
|
+
const fixture = await runDynamicPoolFixture({ target: 4, total: 8 });
|
|
6
6
|
const state = fixture.result.state;
|
|
7
7
|
for (const key of ['target_active_slots', 'max_observed_active_slots', 'pending_queue_drained', 'backfill_count', 'expected_backfill_count', 'slot_count', 'generation_count']) {
|
|
8
8
|
assertGate(Object.prototype.hasOwnProperty.call(state, key) || key === 'slot_count' || key === 'generation_count', `scheduler state/proof missing ${key}`, state);
|
|
@@ -15,7 +15,7 @@ for (const token of [
|
|
|
15
15
|
]) {
|
|
16
16
|
assertGate(proofSource.includes(token), `scheduler proof hardening missing ${token}`);
|
|
17
17
|
}
|
|
18
|
-
const fixture = await runDynamicPoolFixture({ target:
|
|
18
|
+
const fixture = await runDynamicPoolFixture({ target: 4, total: 8 });
|
|
19
19
|
assertGate(fixture.result.state.pending_queue_drained === true, 'positive scheduler fixture must drain', fixture.result.state);
|
|
20
20
|
assertGate(fixture.result.state.backfill_count >= fixture.result.state.expected_backfill_count, 'positive scheduler fixture must satisfy backfill invariant', fixture.result.state);
|
|
21
21
|
emitGate('agent:scheduler-proof-hardening', { checked_blockers: 8, backfill_count: fixture.result.state.backfill_count });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
4
4
|
import { runDynamicPoolFixture } from './agent-dynamic-pool-fixture.js';
|
|
5
|
-
const fixture = await runDynamicPoolFixture({ target:
|
|
5
|
+
const fixture = await runDynamicPoolFixture({ target: 4, total: 8 });
|
|
6
6
|
const allResultsHaveRefs = fixture.result.results.every((result) => result.source_intelligence_refs?.artifact === 'source-intelligence-evidence.json');
|
|
7
7
|
assertGate(allResultsHaveRefs, 'all dynamic generations must propagate source intelligence refs', fixture.result.results);
|
|
8
8
|
emitGate('agent:source-intelligence-propagation', { result_count: fixture.result.results.length });
|
|
@@ -11,10 +11,10 @@ const tmp = makeTempPatchProject('sks-strategy-lease-');
|
|
|
11
11
|
const files = Array.from({ length: 10 }, (_, index) => `file-${index + 1}.txt`);
|
|
12
12
|
const prompt = files.map((file) => `\`${file}\``).join(' ');
|
|
13
13
|
const compiled = strategy.compileStrategy({ prompt, route: '$Agent', agentCount: 5 });
|
|
14
|
-
const roster = rosterMod.buildAgentRoster({ agents: 5, concurrency:
|
|
14
|
+
const roster = rosterMod.buildAgentRoster({ agents: 5, concurrency: 4, prompt });
|
|
15
15
|
const partition = await partitionMod.buildAgentWorkPartition(tmp, roster, prompt, {
|
|
16
16
|
route: '$Agent',
|
|
17
|
-
targetActiveSlots:
|
|
17
|
+
targetActiveSlots: 4,
|
|
18
18
|
desiredWorkItemCount: 10,
|
|
19
19
|
minimumWorkItems: 10,
|
|
20
20
|
strategyOwnershipPlan: compiled.file_ownership_plan,
|
|
@@ -7,7 +7,7 @@ const freshness = ensureDistFresh({ rebuild: true });
|
|
|
7
7
|
if (!freshness.ok)
|
|
8
8
|
fail('dist_not_fresh', { freshness });
|
|
9
9
|
const proof = await import(pathToFileURL(path.join(root, 'dist', 'core', 'agents', 'agent-slot-pane-binding-proof.js')).href);
|
|
10
|
-
const targetActiveSlots =
|
|
10
|
+
const targetActiveSlots = 4;
|
|
11
11
|
const workItems = 10;
|
|
12
12
|
const records = Array.from({ length: workItems }, (_, index) => {
|
|
13
13
|
const slotIndex = (index % targetActiveSlots) + 1;
|
|
@@ -17,12 +17,12 @@ const clean = await evaluateCodexAppFastUiPreservation(tmp, { before, after, cod
|
|
|
17
17
|
const badRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-fast-ui-preservation-bad-'));
|
|
18
18
|
await fs.mkdir(path.join(badRoot, '.codex'), { recursive: true });
|
|
19
19
|
await fs.mkdir(path.join(badRoot, 'home', '.codex'), { recursive: true });
|
|
20
|
-
await fs.writeFile(path.join(badRoot, '.codex', 'config.toml'), '
|
|
20
|
+
await fs.writeFile(path.join(badRoot, '.codex', 'config.toml'), 'openai_base_url = "https://override.invalid"\n');
|
|
21
21
|
await fs.writeFile(path.join(badRoot, 'home', '.codex', 'config.toml'), '');
|
|
22
22
|
const bad = await evaluateCodexAppFastUiPreservation(badRoot, { codexHome: path.join(badRoot, 'home', '.codex') });
|
|
23
23
|
const serialized = JSON.stringify({ clean, bad });
|
|
24
24
|
const secretSafe = !serialized.includes('secret-should-not-leak');
|
|
25
|
-
const ok = clean.ok && !bad.ok && bad.project_local_forbidden_keys.includes('
|
|
25
|
+
const ok = clean.ok && !bad.ok && bad.project_local_forbidden_keys.includes('openai_base_url') && secretSafe;
|
|
26
26
|
emit({ schema: 'sks.codex-app-fast-ui-preservation-check.v1', ok, clean, bad, secret_safe: secretSafe, blockers: ok ? [] : ['codex_app_fast_ui_preservation_check_failed'] });
|
|
27
27
|
function emit(report) {
|
|
28
28
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -44,8 +44,8 @@ delete process.env.SKS_MANAGE_CODEX_APP_PLUGINS;
|
|
|
44
44
|
}
|
|
45
45
|
// 2) Default install must NOT auto-enable any marketplace plugins.
|
|
46
46
|
{
|
|
47
|
-
const out = normalize('model = "
|
|
48
|
-
results.push({ case: 'no_plugins_by_default', ok: !hasPluginTables(out) });
|
|
47
|
+
const out = normalize('model = "future-codex-model"\n');
|
|
48
|
+
results.push({ case: 'no_plugins_by_default', ok: !hasPluginTables(out) && /model = "future-codex-model"/.test(out) });
|
|
49
49
|
}
|
|
50
50
|
// 3) A user's disabled plugin must be left untouched (not reverted to enabled).
|
|
51
51
|
{
|
|
@@ -67,16 +67,17 @@ delete process.env.SKS_MANAGE_CODEX_APP_PLUGINS;
|
|
|
67
67
|
&& featureValue(out, 'multi_agent') === 'true'
|
|
68
68
|
&& featureValue(out, 'fast_mode') === 'true'
|
|
69
69
|
&& !/\[user\.fast_mode\]/.test(out)
|
|
70
|
-
&& !/\[profiles\.sks-fast-high\]/.test(out)
|
|
70
|
+
&& !/\[profiles\.sks-fast-high\]/.test(out)
|
|
71
|
+
&& !/^model\s*=/m.test(out);
|
|
71
72
|
results.push({ case: 'fresh_config_seeds_defaults', ok });
|
|
72
73
|
}
|
|
73
74
|
// 6) Opt-in enables plugins on a fresh config, but still preserves a user-disabled one.
|
|
74
75
|
{
|
|
75
76
|
process.env.SKS_MANAGE_CODEX_APP_PLUGINS = '1';
|
|
76
|
-
const fresh = normalize('model = "
|
|
77
|
+
const fresh = normalize('model = "future-codex-model"\n');
|
|
77
78
|
const userDisabled = normalize('[plugins."chrome@openai-bundled"]\nenabled = false\n');
|
|
78
79
|
delete process.env.SKS_MANAGE_CODEX_APP_PLUGINS;
|
|
79
|
-
const ok = hasPluginTables(fresh) && /\[plugins\."chrome@openai-bundled"\][\s\S]*enabled = false/.test(userDisabled);
|
|
80
|
+
const ok = hasPluginTables(fresh) && /model = "future-codex-model"/.test(fresh) && /\[plugins\."chrome@openai-bundled"\][\s\S]*enabled = false/.test(userDisabled);
|
|
80
81
|
results.push({ case: 'optin_enables_but_preserves_user', ok });
|
|
81
82
|
}
|
|
82
83
|
if (prevEnv === undefined)
|
|
@@ -17,7 +17,7 @@ const oauthAuth = JSON.stringify({
|
|
|
17
17
|
account_id: 'acct-fast-ui-fixture'
|
|
18
18
|
}, null, 2);
|
|
19
19
|
await fs.writeFile(configPath, [
|
|
20
|
-
'model = "
|
|
20
|
+
'model = "future-codex-model"',
|
|
21
21
|
'model_reasoning_effort = "low"',
|
|
22
22
|
'model_provider = "codex-lb"',
|
|
23
23
|
'service_tier = "fast"',
|
|
@@ -79,8 +79,8 @@ function assertConfig(text, label) {
|
|
|
79
79
|
...(tableKey(text, 'features', 'fast_mode_ui') ? ['features_fast_mode_ui_legacy_flag_present'] : []),
|
|
80
80
|
...(hasTable(text, 'model_providers.codex-lb') ? [] : ['codex_lb_provider_table_missing']),
|
|
81
81
|
...(tableKey(text, 'model_providers.codex-lb', 'requires_openai_auth') === 'true' ? [] : ['requires_openai_auth_not_true']),
|
|
82
|
-
...(topLevelKey(text, 'model') ? [
|
|
83
|
-
...(topLevelKey(text, 'model_reasoning_effort') ? [
|
|
82
|
+
...(topLevelKey(text, 'model') === 'future-codex-model' ? [] : ['user_model_not_preserved']),
|
|
83
|
+
...(topLevelKey(text, 'model_reasoning_effort') === 'low' ? [] : ['user_reasoning_effort_not_preserved'])
|
|
84
84
|
];
|
|
85
85
|
return { label, ok: blockers.length === 0, blockers };
|
|
86
86
|
}
|
|
@@ -97,7 +97,7 @@ function tableKey(text, table, key) {
|
|
|
97
97
|
}
|
|
98
98
|
function topLevelKey(text, key) {
|
|
99
99
|
const top = text.split(/\n\s*\[/)[0] || '';
|
|
100
|
-
return new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s
|
|
100
|
+
return top.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*([^\\n#]+)`))?.[2]?.trim().replace(/^"|"$/g, '') || '';
|
|
101
101
|
}
|
|
102
102
|
function escapeRegExp(value) {
|
|
103
103
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|