sneakoscope 0.8.0 → 0.8.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 +3 -3
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +38 -9
- package/src/cli/main.mjs +41 -22
- package/src/cli/maintenance-commands.mjs +98 -6
- package/src/core/codex-app.mjs +181 -11
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +82 -1
- package/src/core/init.mjs +42 -7
- package/src/core/pipeline.mjs +3 -3
- package/src/core/research.mjs +85 -15
- package/src/core/routes.mjs +1 -1
package/src/core/codex-app.mjs
CHANGED
|
@@ -7,7 +7,30 @@ import { getCodexInfo } from './codex-adapter.mjs';
|
|
|
7
7
|
export const CODEX_APP_DOCS_URL = 'https://developers.openai.com/codex/app/features';
|
|
8
8
|
export const CODEX_CHANGELOG_URL = 'https://developers.openai.com/codex/changelog';
|
|
9
9
|
export const CODEX_REMOTE_CONTROL_MIN_VERSION = '0.130.0';
|
|
10
|
-
const REQUIRED_CODEX_APP_FEATURE_FLAGS = [
|
|
10
|
+
const REQUIRED_CODEX_APP_FEATURE_FLAGS = [
|
|
11
|
+
'codex_git_commit',
|
|
12
|
+
'hooks',
|
|
13
|
+
'remote_control',
|
|
14
|
+
'fast_mode',
|
|
15
|
+
'computer_use',
|
|
16
|
+
'browser_use',
|
|
17
|
+
'browser_use_external',
|
|
18
|
+
'image_generation',
|
|
19
|
+
'in_app_browser',
|
|
20
|
+
'guardian_approval',
|
|
21
|
+
'tool_suggest',
|
|
22
|
+
'apps',
|
|
23
|
+
'plugins'
|
|
24
|
+
];
|
|
25
|
+
const DEFAULT_CODEX_APP_PLUGINS = [
|
|
26
|
+
{ name: 'browser', marketplace: 'openai-bundled' },
|
|
27
|
+
{ name: 'chrome', marketplace: 'openai-bundled' },
|
|
28
|
+
{ name: 'computer-use', marketplace: 'openai-bundled' },
|
|
29
|
+
{ name: 'latex', marketplace: 'openai-bundled' },
|
|
30
|
+
{ name: 'documents', marketplace: 'openai-primary-runtime' },
|
|
31
|
+
{ name: 'presentations', marketplace: 'openai-primary-runtime' },
|
|
32
|
+
{ name: 'spreadsheets', marketplace: 'openai-primary-runtime' }
|
|
33
|
+
];
|
|
11
34
|
|
|
12
35
|
export function codexAppCandidatePaths(home = os.homedir(), env = process.env) {
|
|
13
36
|
const candidates = [];
|
|
@@ -104,15 +127,20 @@ export async function codexAppIntegrationStatus(opts = {}) {
|
|
|
104
127
|
const featureText = `${featureList.stdout}\n${featureList.stderr}`;
|
|
105
128
|
const browserUsePath = await findPluginCache('browser-use', opts);
|
|
106
129
|
const computerUsePath = await findPluginCache('computer-use', opts);
|
|
130
|
+
const defaultPlugins = await codexDefaultPluginStatus(opts);
|
|
131
|
+
const fastModeConfig = await codexFastModeConfigStatus(opts);
|
|
107
132
|
const computerUseMcpListed = /computer[-_ ]?use/i.test(mcpText);
|
|
108
133
|
const browserUseMcpListed = /browser[-_ ]?use/i.test(mcpText);
|
|
109
134
|
const imageGenerationReady = codexFeatureEnabled(featureText, 'image_generation');
|
|
135
|
+
const inAppBrowserReady = codexFeatureEnabled(featureText, 'in_app_browser');
|
|
136
|
+
const browserUseFeatureReady = codexFeatureEnabled(featureText, 'browser_use');
|
|
110
137
|
const requiredFeatureFlags = Object.fromEntries(REQUIRED_CODEX_APP_FEATURE_FLAGS.map((name) => [name, codexFeatureEnabled(featureText, name)]));
|
|
111
138
|
const requiredFeatureFlagsOk = Object.values(requiredFeatureFlags).every(Boolean);
|
|
112
139
|
const computerUseReady = computerUseMcpListed || Boolean(computerUsePath);
|
|
113
140
|
const browserUseReady = browserUseMcpListed || Boolean(browserUsePath);
|
|
141
|
+
const browserToolReady = inAppBrowserReady || browserUseFeatureReady || browserUseReady;
|
|
114
142
|
const appInstalled = Boolean(appPath);
|
|
115
|
-
const ready = appInstalled && Boolean(codex.bin) && mcpList.ok && featureList.ok && requiredFeatureFlagsOk && imageGenerationReady && computerUseReady &&
|
|
143
|
+
const ready = appInstalled && Boolean(codex.bin) && mcpList.ok && featureList.ok && requiredFeatureFlagsOk && defaultPlugins.ok && fastModeConfig.ok && imageGenerationReady && computerUseReady && browserToolReady;
|
|
116
144
|
return {
|
|
117
145
|
ok: ready,
|
|
118
146
|
app: {
|
|
@@ -142,16 +170,30 @@ export async function codexAppIntegrationStatus(opts = {}) {
|
|
|
142
170
|
...requiredFeatureFlags,
|
|
143
171
|
required_flags: requiredFeatureFlags,
|
|
144
172
|
required_flags_ok: requiredFeatureFlagsOk,
|
|
173
|
+
fast_mode_config: fastModeConfig,
|
|
145
174
|
image_generation: imageGenerationReady,
|
|
146
175
|
image_generation_source: imageGenerationReady ? 'codex_features_list' : 'missing',
|
|
176
|
+
in_app_browser: inAppBrowserReady,
|
|
177
|
+
browser_use: browserUseFeatureReady,
|
|
178
|
+
browser_tool_ready: browserToolReady,
|
|
179
|
+
browser_tool_source: inAppBrowserReady
|
|
180
|
+
? 'codex_features_list:in_app_browser'
|
|
181
|
+
: browserUseFeatureReady
|
|
182
|
+
? 'codex_features_list:browser_use'
|
|
183
|
+
: browserUseMcpListed
|
|
184
|
+
? 'mcp_list:browser_use'
|
|
185
|
+
: browserUsePath
|
|
186
|
+
? 'plugin_cache:browser-use'
|
|
187
|
+
: 'missing',
|
|
147
188
|
stdout: featureList.stdout,
|
|
148
189
|
stderr: featureList.stderr
|
|
149
190
|
},
|
|
150
191
|
plugins: {
|
|
151
192
|
computer_use_cache: computerUsePath,
|
|
152
|
-
browser_use_cache: browserUsePath
|
|
193
|
+
browser_use_cache: browserUsePath,
|
|
194
|
+
default_plugins: defaultPlugins
|
|
153
195
|
},
|
|
154
|
-
guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, imageGenerationReady, computerUseReady, browserUseReady, computerUseMcpListed, browserUseMcpListed, remoteControl })
|
|
196
|
+
guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, fastModeConfig, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, remoteControl })
|
|
155
197
|
};
|
|
156
198
|
}
|
|
157
199
|
|
|
@@ -206,7 +248,7 @@ export function formatCodexRemoteControlStatus(status) {
|
|
|
206
248
|
return lines.filter(Boolean).join('\n');
|
|
207
249
|
}
|
|
208
250
|
|
|
209
|
-
export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, imageGenerationReady, computerUseReady, browserUseReady, computerUseMcpListed, browserUseMcpListed, remoteControl }) {
|
|
251
|
+
export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, fastModeConfig = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, remoteControl }) {
|
|
210
252
|
const lines = [];
|
|
211
253
|
if (!appInstalled) {
|
|
212
254
|
lines.push('Install and open Codex App for first-party MCP/plugin tools. SKS tmux launch can still run with Codex CLI alone, but Codex Computer Use and imagegen/gpt-image-2 evidence will be unavailable until Codex App is ready.');
|
|
@@ -229,13 +271,21 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
|
|
|
229
271
|
}
|
|
230
272
|
if (featureList?.checked && featureList.ok && !requiredFeatureFlagsOk) {
|
|
231
273
|
const missing = missingRequiredFeatureFlags(requiredFeatureFlags);
|
|
232
|
-
lines.push(`Codex App feature flag(s) disabled or missing: ${missing.join(', ')}. Commit message generation and app-only tool paths can fail even when CLI chat works.`);
|
|
233
|
-
lines.push('Verify with: codex features list | rg "codex_git_commit|hooks|fast_mode|computer_use|apps|plugins"');
|
|
274
|
+
lines.push(`Codex App feature flag(s) disabled or missing: ${missing.join(', ')}. Commit message generation, mobile/remote-control, and app-only tool paths can fail even when CLI chat works.`);
|
|
275
|
+
lines.push('Verify with: codex features list | rg "codex_git_commit|hooks|remote_control|fast_mode|computer_use|browser_use|browser_use_external|image_generation|in_app_browser|guardian_approval|tool_suggest|apps|plugins"');
|
|
234
276
|
}
|
|
235
|
-
if (
|
|
277
|
+
if (defaultPlugins?.missing_enabled?.length) {
|
|
278
|
+
lines.push(`Codex default plugin(s) installed but not enabled: ${defaultPlugins.missing_enabled.join(', ')}. Composer/tool UI can hide built-in surfaces even while feature flags look green.`);
|
|
279
|
+
lines.push('Run: sks doctor --fix');
|
|
280
|
+
}
|
|
281
|
+
if (fastModeConfig?.blockers?.length) {
|
|
282
|
+
lines.push(`Codex App speed selector can be hidden or locked by config: ${fastModeConfig.blockers.join(', ')}.`);
|
|
283
|
+
lines.push('Run: sks doctor --fix');
|
|
284
|
+
}
|
|
285
|
+
if (appInstalled && (!computerUseReady || !browserToolReady)) {
|
|
236
286
|
lines.push('Open Codex App settings and enable recommended MCP/plugin tools. Codex CLI 0.130.0+ remote-control/app-server sessions can pick up config changes live; restart older CLI/TUI sessions.');
|
|
237
|
-
lines.push('Required for SKS QA-LOOP UI/browser evidence: Codex Computer Use only. Browser
|
|
238
|
-
lines.push('Verify with: codex mcp list');
|
|
287
|
+
lines.push('Required for SKS QA-LOOP UI/browser evidence: Codex Computer Use only. Browser tools can support browsing context, but they do not satisfy UI-level E2E verification.');
|
|
288
|
+
lines.push('Verify with: codex features list; codex mcp list');
|
|
239
289
|
}
|
|
240
290
|
if (imageGenerationReady) {
|
|
241
291
|
lines.push('Image generation is enabled; required raster assets and generated image-review evidence must invoke $imagegen/gpt-image-2 and record real output.');
|
|
@@ -245,6 +295,10 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
|
|
|
245
295
|
if (computerUseReady && !computerUseMcpListed) {
|
|
246
296
|
lines.push('Computer Use plugin files are installed, but this check cannot prove the current thread exposes the live Computer Use tools. Start a new Codex App thread and invoke @Computer or @AppName for the actual target app or screen; Codex App readiness itself should stay on `codex features list`, `codex mcp list`, and `sks codex-app check`.');
|
|
247
297
|
}
|
|
298
|
+
if (browserToolReady) {
|
|
299
|
+
const source = inAppBrowserReady ? 'in-app browser feature' : browserUseFeatureReady ? 'browser_use feature' : 'Browser Use plugin';
|
|
300
|
+
lines.push(`Browser tooling is visible via ${source}; prefer the first-party in-app browser for local web apps, and keep Codex Computer Use as the only accepted UI verification evidence source.`);
|
|
301
|
+
}
|
|
248
302
|
if (browserUseReady && !browserUseMcpListed) {
|
|
249
303
|
lines.push('Browser Use plugin files are installed, but `codex mcp list` does not list a browser-use MCP server. Treat Browser Use as plugin-scoped, not as SKS UI verification evidence.');
|
|
250
304
|
}
|
|
@@ -260,8 +314,10 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
|
|
|
260
314
|
`Codex CLI: ${status.codex_cli.ok ? 'ok' : 'missing'}${status.codex_cli.version ? ` ${status.codex_cli.version}` : ''}`,
|
|
261
315
|
`Remote Ctrl: ${status.remote_control?.ok ? 'ok' : 'missing'}${status.remote_control?.codex_cli?.version_number ? ` min ${status.remote_control.min_version}` : ''}`,
|
|
262
316
|
`App Flags: ${status.features?.required_flags_ok ? 'ok' : `missing ${missingRequiredFeatureFlags(status.features?.required_flags).join(', ') || 'required flags'}`}`,
|
|
317
|
+
`Fast UI: ${status.features?.fast_mode_config?.ok ? 'ok' : `locked ${(status.features?.fast_mode_config?.blockers || []).join(', ') || 'config'}`}`,
|
|
318
|
+
`Default Plugins:${status.plugins?.default_plugins?.ok ? ' ok' : ` missing ${(status.plugins?.default_plugins?.missing_enabled || []).join(', ') || 'enabled plugin config'}`}`,
|
|
263
319
|
`Computer Use:${status.mcp.has_computer_use ? status.mcp.computer_use_source === 'plugin_cache' ? ' installed (verify @Computer in thread)' : ' ok' : ' missing'}`,
|
|
264
|
-
`Browser
|
|
320
|
+
`Browser: ${status.features?.browser_tool_ready ? `ok (${status.features.browser_tool_source})` : status.mcp.has_browser_use ? status.mcp.browser_use_source === 'plugin_cache' ? 'installed (plugin scoped)' : 'ok' : 'missing'}`,
|
|
265
321
|
`Image Gen: ${status.features?.image_generation ? 'ok ($imagegen/gpt-image-2)' : status.features?.checked ? 'missing' : 'not checked'}`,
|
|
266
322
|
`Ready: ${status.ok ? 'yes' : 'no'}`,
|
|
267
323
|
'',
|
|
@@ -296,6 +352,120 @@ function missingRequiredFeatureFlags(flags = {}) {
|
|
|
296
352
|
return REQUIRED_CODEX_APP_FEATURE_FLAGS.filter((name) => flags?.[name] !== true);
|
|
297
353
|
}
|
|
298
354
|
|
|
355
|
+
async function codexDefaultPluginStatus(opts = {}) {
|
|
356
|
+
const home = opts.home || os.homedir();
|
|
357
|
+
const cwd = opts.cwd || process.cwd();
|
|
358
|
+
const globalConfigPath = path.join(home || '', '.codex', 'config.toml');
|
|
359
|
+
const projectConfigPath = path.join(cwd || '', '.codex', 'config.toml');
|
|
360
|
+
const globalConfig = await readTextIfExists(globalConfigPath);
|
|
361
|
+
const projectConfig = path.resolve(projectConfigPath) === path.resolve(globalConfigPath)
|
|
362
|
+
? ''
|
|
363
|
+
: await readTextIfExists(projectConfigPath);
|
|
364
|
+
const configText = `${globalConfig}\n${projectConfig}`;
|
|
365
|
+
const entries = [];
|
|
366
|
+
for (const plugin of DEFAULT_CODEX_APP_PLUGINS) {
|
|
367
|
+
const source = await findDefaultPluginSource(plugin, { home, configText });
|
|
368
|
+
const enabled = codexPluginEnabled(configText, plugin);
|
|
369
|
+
entries.push({
|
|
370
|
+
id: `${plugin.name}@${plugin.marketplace}`,
|
|
371
|
+
name: plugin.name,
|
|
372
|
+
marketplace: plugin.marketplace,
|
|
373
|
+
installed: Boolean(source),
|
|
374
|
+
source,
|
|
375
|
+
enabled
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const installed = entries.filter((entry) => entry.installed);
|
|
379
|
+
const missingEnabled = installed.filter((entry) => !entry.enabled).map((entry) => entry.id);
|
|
380
|
+
return {
|
|
381
|
+
ok: missingEnabled.length === 0,
|
|
382
|
+
checked: true,
|
|
383
|
+
entries,
|
|
384
|
+
missing_enabled: missingEnabled
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function codexFastModeConfigStatus(opts = {}) {
|
|
389
|
+
const home = opts.home || os.homedir();
|
|
390
|
+
const cwd = opts.cwd || process.cwd();
|
|
391
|
+
const globalConfigPath = path.join(home || '', '.codex', 'config.toml');
|
|
392
|
+
const projectConfigPath = path.join(cwd || '', '.codex', 'config.toml');
|
|
393
|
+
const configs = [
|
|
394
|
+
{ scope: 'global', path: globalConfigPath, text: await readTextIfExists(globalConfigPath) }
|
|
395
|
+
];
|
|
396
|
+
if (path.resolve(projectConfigPath) !== path.resolve(globalConfigPath)) {
|
|
397
|
+
configs.push({ scope: 'project', path: projectConfigPath, text: await readTextIfExists(projectConfigPath) });
|
|
398
|
+
}
|
|
399
|
+
const blockers = [];
|
|
400
|
+
for (const config of configs) {
|
|
401
|
+
if (!config.text) continue;
|
|
402
|
+
const topLevel = topLevelToml(config.text);
|
|
403
|
+
if (/(^|\n)\s*model_reasoning_effort\s*=/.test(topLevel)) blockers.push(`${config.scope}:top_level_model_reasoning_effort`);
|
|
404
|
+
if (/(^|\n)\s*fast_default_opt_out\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(tomlTable(config.text, 'notice'))) blockers.push(`${config.scope}:fast_default_opt_out`);
|
|
405
|
+
}
|
|
406
|
+
const merged = configs.map((config) => config.text).join('\n');
|
|
407
|
+
const fastMode = tomlTable(merged, 'user.fast_mode');
|
|
408
|
+
if (!/(^|\n)\s*visible\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(fastMode)) blockers.push('user.fast_mode.visible_missing');
|
|
409
|
+
if (!/(^|\n)\s*enabled\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(fastMode)) blockers.push('user.fast_mode.enabled_missing');
|
|
410
|
+
if (!/(^|\n)\s*default_profile\s*=\s*"sks-fast-high"\s*(?:#.*)?(?=\n|$)/.test(fastMode)) blockers.push('user.fast_mode.default_profile_missing');
|
|
411
|
+
return {
|
|
412
|
+
ok: blockers.length === 0,
|
|
413
|
+
checked: true,
|
|
414
|
+
blockers
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function readTextIfExists(file) {
|
|
419
|
+
try {
|
|
420
|
+
return await fsp.readFile(file, 'utf8');
|
|
421
|
+
} catch {
|
|
422
|
+
return '';
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function findDefaultPluginSource(plugin, { home, configText }) {
|
|
427
|
+
const cached = await findPluginCache(plugin.name, { home });
|
|
428
|
+
if (cached) return cached;
|
|
429
|
+
for (const source of marketplaceSources(configText, plugin.marketplace)) {
|
|
430
|
+
const candidate = path.join(source, 'plugins', plugin.name, '.codex-plugin', 'plugin.json');
|
|
431
|
+
if (await exists(candidate)) return path.dirname(path.dirname(candidate));
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function marketplaceSources(configText = '', marketplaceName = '') {
|
|
437
|
+
const table = `marketplaces.${marketplaceName}`;
|
|
438
|
+
const re = new RegExp(`(?:^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`, 'g');
|
|
439
|
+
const sources = [];
|
|
440
|
+
for (const match of String(configText || '').matchAll(re)) {
|
|
441
|
+
const source = match[1].match(/(?:^|\n)\s*source\s*=\s*"([^"]+)"/)?.[1];
|
|
442
|
+
if (source) sources.push(source);
|
|
443
|
+
}
|
|
444
|
+
return Array.from(new Set(sources));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function codexPluginEnabled(configText = '', plugin = {}) {
|
|
448
|
+
const table = `plugins."${plugin.name}@${plugin.marketplace}"`;
|
|
449
|
+
const re = new RegExp(`(?:^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`);
|
|
450
|
+
const block = String(configText || '').match(re)?.[1] || '';
|
|
451
|
+
return /(?:^|\n)\s*enabled\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(block);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function topLevelToml(text = '') {
|
|
455
|
+
const lines = String(text || '').split('\n');
|
|
456
|
+
const firstTable = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
|
|
457
|
+
return (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function tomlTable(text = '', table = '') {
|
|
461
|
+
const re = new RegExp(`(?:^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`);
|
|
462
|
+
return String(text || '').match(re)?.[1] || '';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function escapeRegExp(text = '') {
|
|
466
|
+
return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
467
|
+
}
|
|
468
|
+
|
|
299
469
|
function remoteControlGuidance(status = {}) {
|
|
300
470
|
if (!status.codex_cli?.ok) return 'Codex remote-control requires Codex CLI 0.130.0+. Install with: npm i -g @openai/codex@latest';
|
|
301
471
|
if (status.reason === 'codex_cli_version_unknown') return 'Codex remote-control requires Codex CLI 0.130.0+, but the installed CLI version could not be parsed. Check: codex --version';
|
package/src/core/fsx.mjs
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
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.8.
|
|
8
|
+
export const PACKAGE_VERSION = '0.8.2';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ const CODEX_GIT_ACTION_STOP_ARTIFACT = 'codex-git-action-stop-bypass.json';
|
|
|
20
20
|
const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
|
|
21
21
|
const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
|
|
22
22
|
const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
|
|
23
|
-
const CODEX_GIT_ACTION_STOP_TTL_MS =
|
|
23
|
+
const CODEX_GIT_ACTION_STOP_TTL_MS = 15 * 60 * 1000;
|
|
24
24
|
|
|
25
25
|
async function loadHookPayload() {
|
|
26
26
|
const raw = await readStdin();
|
|
@@ -106,12 +106,39 @@ export async function hookMain(name) {
|
|
|
106
106
|
function blockForbiddenClientModel(payload = {}) {
|
|
107
107
|
const model = forbiddenClientModelFromPayload(payload);
|
|
108
108
|
if (!model || !isForbiddenCodexModel(model)) return null;
|
|
109
|
+
if (looksLikeCodexUiSettingsEvent(payload)) return null;
|
|
109
110
|
return {
|
|
110
111
|
decision: 'block',
|
|
111
112
|
reason: `SKS requires ${REQUIRED_CODEX_MODEL}; client payload requested ${model}. Switch the Codex client/session model to ${REQUIRED_CODEX_MODEL} and retry.`
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
function looksLikeCodexUiSettingsEvent(payload = {}) {
|
|
117
|
+
const prompt = stripVisibleDecisionAnswerBlocks(extractUserPrompt(payload));
|
|
118
|
+
const haystack = [
|
|
119
|
+
payload.action,
|
|
120
|
+
payload.intent,
|
|
121
|
+
payload.operation,
|
|
122
|
+
payload.permission,
|
|
123
|
+
payload.description,
|
|
124
|
+
payload.kind,
|
|
125
|
+
payload.type,
|
|
126
|
+
payload.feature,
|
|
127
|
+
payload.source,
|
|
128
|
+
payload.event,
|
|
129
|
+
payload.hook,
|
|
130
|
+
payload.hook_name,
|
|
131
|
+
payload.metadata?.action,
|
|
132
|
+
payload.metadata?.intent,
|
|
133
|
+
payload.metadata?.operation,
|
|
134
|
+
payload.metadata?.feature,
|
|
135
|
+
payload.metadata?.source,
|
|
136
|
+
payload.context?.surface,
|
|
137
|
+
payload.session?.surface
|
|
138
|
+
].filter(Boolean).join(' ');
|
|
139
|
+
return !prompt && /\b(?:settings|preferences|profile|speed|fast[_\s-]*mode|reasoning|model[_\s-]*select|codex[_\s-]*app)\b/i.test(haystack);
|
|
140
|
+
}
|
|
141
|
+
|
|
115
142
|
function forbiddenClientModelFromPayload(payload = {}) {
|
|
116
143
|
const candidates = [
|
|
117
144
|
payload.model,
|
|
@@ -148,6 +175,12 @@ async function hookUserPrompt(root, state, payload, noQuestion) {
|
|
|
148
175
|
systemMessage: 'SKS: Codex App git action bypassed route gates.'
|
|
149
176
|
};
|
|
150
177
|
}
|
|
178
|
+
if (looksLikeCodexUiSettingsEvent(payload)) {
|
|
179
|
+
return {
|
|
180
|
+
continue: true,
|
|
181
|
+
systemMessage: 'SKS: Codex App settings/profile event ignored; route gates unchanged.'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
151
184
|
if (!noQuestion) {
|
|
152
185
|
const prompt = stripVisibleDecisionAnswerBlocks(extractUserPrompt(payload));
|
|
153
186
|
const madSksConfirmation = await handleMadSksUserConfirmation(root, state, prompt);
|
|
@@ -359,6 +392,12 @@ async function hookStop(root, state, payload, noQuestion) {
|
|
|
359
392
|
systemMessage: 'SKS: Codex App git action accepted without route finalization gates.'
|
|
360
393
|
};
|
|
361
394
|
}
|
|
395
|
+
if (looksLikeCodexGitActionStopCompletion(last, payload)) {
|
|
396
|
+
return {
|
|
397
|
+
continue: true,
|
|
398
|
+
systemMessage: 'SKS: Codex App git action completion accepted without route finalization gates.'
|
|
399
|
+
};
|
|
400
|
+
}
|
|
362
401
|
if (!noQuestion && (hasDfixLightCompletion(last) || await consumeLightRouteStop(root, payload))) {
|
|
363
402
|
return {
|
|
364
403
|
continue: true,
|
|
@@ -469,12 +508,42 @@ function looksLikeCodexGitAction(payload = {}) {
|
|
|
469
508
|
|| /커밋\s*메시지\s*생성/i.test(haystack);
|
|
470
509
|
const promptSignal = /\bgenerate(?:\s+a)?(?:\s+git)?\s+commit\s+message\b/i.test(prompt)
|
|
471
510
|
|| /\bcommit\s+message\b[\s\S]{0,80}\b(?:staged|diff|changes?|git)\b/i.test(prompt)
|
|
511
|
+
|| looksLikeStockCodexGitActionPrompt(prompt)
|
|
472
512
|
|| /커밋\s*메시지\s*생성/i.test(prompt);
|
|
473
513
|
if (!appSignal && !promptSignal) return false;
|
|
514
|
+
if (looksLikeStockCodexGitActionPrompt(prompt)) return true;
|
|
474
515
|
if (appSignal) return true;
|
|
475
516
|
return !looksLikeUserImplementationRequest(prompt);
|
|
476
517
|
}
|
|
477
518
|
|
|
519
|
+
function looksLikeStockCodexGitActionPrompt(prompt = '') {
|
|
520
|
+
const text = String(prompt || '').trim();
|
|
521
|
+
if (!text || text.length > 120) return false;
|
|
522
|
+
return /^(?:generate\s+(?:a\s+)?git\s+commit\s+message(?:\s+for\s+(?:the\s+)?(?:staged\s+)?diff)?|commit\s+changes|commit\s+and\s+push\s+changes|push\s+changes|create\s+(?:a\s+)?commit|create\s+(?:a\s+)?pull\s+request)\.?$/i.test(text);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function looksLikeCodexGitActionStopCompletion(last = '', payload = {}) {
|
|
526
|
+
const text = String(last || '').trim();
|
|
527
|
+
const haystack = [
|
|
528
|
+
payload.action,
|
|
529
|
+
payload.intent,
|
|
530
|
+
payload.operation,
|
|
531
|
+
payload.kind,
|
|
532
|
+
payload.type,
|
|
533
|
+
payload.feature,
|
|
534
|
+
payload.source,
|
|
535
|
+
payload.event,
|
|
536
|
+
payload.metadata?.action,
|
|
537
|
+
payload.metadata?.intent,
|
|
538
|
+
payload.metadata?.operation,
|
|
539
|
+
payload.metadata?.feature,
|
|
540
|
+
payload.metadata?.source
|
|
541
|
+
].filter(Boolean).join(' ');
|
|
542
|
+
if (/\bcodex[_\s-]*app\b[\s\S]{0,80}\bgit\b[\s\S]{0,80}\b(?:action|commit|push|pr)\b/i.test(haystack)) return true;
|
|
543
|
+
if (!text || text.length > 180) return false;
|
|
544
|
+
return /^(?:commit(?:ted)?(?:\s+and\s+pushed)?(?:\s+changes)?(?:\s+complete[.!]?)?|push(?:ed)?(?:\s+changes)?(?:\s+complete[.!]?)?|created\s+(?:a\s+)?pull\s+request[.!]?)$/i.test(text);
|
|
545
|
+
}
|
|
546
|
+
|
|
478
547
|
function looksLikeUserImplementationRequest(text = '') {
|
|
479
548
|
return /(fix|bug|broken|error|issue|implement|change|update|repair|수정|버그|오류|에러|문제|고쳐|고치|해결|변경|수리|패치|안생기|안\s*생기)/i.test(String(text || ''));
|
|
480
549
|
}
|
|
@@ -941,6 +1010,18 @@ export async function selftestCodexCommitHooks() {
|
|
|
941
1010
|
const appCommitPushStop = await runHook('stop', { conversation_id: commitPushId, last_assistant_message: 'Commit and push complete.' });
|
|
942
1011
|
if (appCommitPushStop.code !== 0) throw new Error(`selftest failed: app commit-push stop ${appCommitPushStop.code}: ${appCommitPushStop.stderr}`);
|
|
943
1012
|
if (JSON.parse(appCommitPushStop.stdout).decision === 'block') throw new Error('selftest failed: app commit-push stop bypass');
|
|
1013
|
+
const metadataLightId = 'metadata-light-commit-push-selftest';
|
|
1014
|
+
const metadataLightHook = await runHook('user-prompt-submit', { conversation_id: metadataLightId, prompt: 'Commit and push changes.' });
|
|
1015
|
+
if (metadataLightHook.code !== 0) throw new Error(`selftest failed: metadata-light commit-push hook ${metadataLightHook.code}: ${metadataLightHook.stderr}`);
|
|
1016
|
+
const metadataLightJson = JSON.parse(metadataLightHook.stdout);
|
|
1017
|
+
if (metadataLightJson.decision === 'block' || metadataLightJson.hookSpecificOutput?.additionalContext || !String(metadataLightJson.systemMessage || '').includes('git action')) throw new Error('selftest failed: metadata-light app commit-push route bypass');
|
|
1018
|
+
const metadataLightStop = await runHook('stop', { conversation_id: metadataLightId, last_assistant_message: 'Commit and push complete.' });
|
|
1019
|
+
if (metadataLightStop.code !== 0) throw new Error(`selftest failed: metadata-light commit-push stop ${metadataLightStop.code}: ${metadataLightStop.stderr}`);
|
|
1020
|
+
if (JSON.parse(metadataLightStop.stdout).decision === 'block') throw new Error('selftest failed: metadata-light commit-push stop bypass');
|
|
1021
|
+
const settingsHook = await runHook('user-prompt-submit', { model: 'gpt-5.0-forbidden', metadata: { source: 'codex_app_settings', feature: 'speed profile' } });
|
|
1022
|
+
if (settingsHook.code !== 0) throw new Error(`selftest failed: settings hook ${settingsHook.code}: ${settingsHook.stderr}`);
|
|
1023
|
+
const settingsJson = JSON.parse(settingsHook.stdout);
|
|
1024
|
+
if (settingsJson.decision === 'block' || settingsJson.hookSpecificOutput?.additionalContext || !String(settingsJson.systemMessage || '').includes('settings/profile event ignored')) throw new Error('selftest failed: settings/profile event should not route or block');
|
|
944
1025
|
const userHook = await runHook('user-prompt-submit', { prompt: '[커밋 메시지를 생성하지 못했습니다.] 코덱스 앱에서 이 버그 수정해줘' });
|
|
945
1026
|
if (userHook.code !== 0) throw new Error(`selftest failed: user commit hook ${userHook.code}: ${userHook.stderr}`);
|
|
946
1027
|
if (!JSON.parse(userHook.stdout).hookSpecificOutput?.additionalContext?.includes('$Team route prepared')) throw new Error('selftest failed: user prompt route');
|
package/src/core/init.mjs
CHANGED
|
@@ -17,15 +17,32 @@ const GENERATED_PRUNE_POLICY = 'remove_previous_sks_generated_paths_absent_from_
|
|
|
17
17
|
|
|
18
18
|
export const REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS = [
|
|
19
19
|
'hooks',
|
|
20
|
+
'remote_control',
|
|
20
21
|
'multi_agent',
|
|
21
22
|
'fast_mode',
|
|
22
23
|
'fast_mode_ui',
|
|
23
24
|
'codex_git_commit',
|
|
24
25
|
'computer_use',
|
|
26
|
+
'browser_use',
|
|
27
|
+
'browser_use_external',
|
|
28
|
+
'image_generation',
|
|
29
|
+
'in_app_browser',
|
|
30
|
+
'guardian_approval',
|
|
31
|
+
'tool_suggest',
|
|
25
32
|
'apps',
|
|
26
33
|
'plugins'
|
|
27
34
|
];
|
|
28
35
|
|
|
36
|
+
const DEFAULT_CODEX_APP_PLUGINS = [
|
|
37
|
+
['browser', 'openai-bundled'],
|
|
38
|
+
['chrome', 'openai-bundled'],
|
|
39
|
+
['computer-use', 'openai-bundled'],
|
|
40
|
+
['latex', 'openai-bundled'],
|
|
41
|
+
['documents', 'openai-primary-runtime'],
|
|
42
|
+
['presentations', 'openai-primary-runtime'],
|
|
43
|
+
['spreadsheets', 'openai-primary-runtime']
|
|
44
|
+
];
|
|
45
|
+
|
|
29
46
|
export function hasTopLevelCodexModeLock(text = '') {
|
|
30
47
|
const lines = String(text || '').split('\n');
|
|
31
48
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
@@ -491,11 +508,18 @@ function mergeManagedCodexConfigToml(existingContent = '') {
|
|
|
491
508
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
492
509
|
next = upsertTopLevelTomlBoolean(next, 'suppress_unstable_features_warning', true);
|
|
493
510
|
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
511
|
+
next = upsertTomlTableKey(next, 'features', 'remote_control = true');
|
|
494
512
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
495
513
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
496
514
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
497
515
|
next = upsertTomlTableKey(next, 'features', 'codex_git_commit = true');
|
|
498
516
|
next = upsertTomlTableKey(next, 'features', 'computer_use = true');
|
|
517
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use = true');
|
|
518
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use_external = true');
|
|
519
|
+
next = upsertTomlTableKey(next, 'features', 'image_generation = true');
|
|
520
|
+
next = upsertTomlTableKey(next, 'features', 'in_app_browser = true');
|
|
521
|
+
next = upsertTomlTableKey(next, 'features', 'guardian_approval = true');
|
|
522
|
+
next = upsertTomlTableKey(next, 'features', 'tool_suggest = true');
|
|
499
523
|
next = upsertTomlTableKey(next, 'features', 'apps = true');
|
|
500
524
|
next = upsertTomlTableKey(next, 'features', 'plugins = true');
|
|
501
525
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
|
|
@@ -506,6 +530,10 @@ function mergeManagedCodexConfigToml(existingContent = '') {
|
|
|
506
530
|
for (const block of managedCodexConfigBlocks()) {
|
|
507
531
|
next = upsertTomlTable(next, block.table, block.text);
|
|
508
532
|
}
|
|
533
|
+
for (const [name, marketplace] of DEFAULT_CODEX_APP_PLUGINS) {
|
|
534
|
+
const table = `plugins."${name}@${marketplace}"`;
|
|
535
|
+
next = upsertTomlTable(next, table, `[${table}]\nenabled = true`);
|
|
536
|
+
}
|
|
509
537
|
return `${next.trim()}\n`;
|
|
510
538
|
}
|
|
511
539
|
|
|
@@ -517,6 +545,7 @@ async function mergeGlobalCodexConfigIfAvailable(configText = '', configPath = '
|
|
|
517
545
|
if (configPath && path.resolve(configPath) === path.resolve(globalConfigPath)) return configText;
|
|
518
546
|
const globalConfig = await readText(globalConfigPath, '');
|
|
519
547
|
let next = mergeGlobalMcpServers(configText, globalConfig);
|
|
548
|
+
next = mergeGlobalCodexAppRuntimeTables(next, globalConfig);
|
|
520
549
|
if (selectedRe.test(next) && /\[model_providers\.codex-lb\]/.test(next)) return `${String(next || '').trim()}\n`;
|
|
521
550
|
const envPath = path.join(home, '.codex', 'sks-codex-lb.env');
|
|
522
551
|
if (!(await exists(envPath))) return next;
|
|
@@ -562,18 +591,24 @@ function mergeGlobalMcpServers(configText = '', globalConfig = '') {
|
|
|
562
591
|
return next;
|
|
563
592
|
}
|
|
564
593
|
|
|
594
|
+
function mergeGlobalCodexAppRuntimeTables(configText = '', globalConfig = '') {
|
|
595
|
+
let next = configText;
|
|
596
|
+
const re = /(?:^|\n)(\[((?:marketplaces|plugins)\.[^\]\r\n]+)\][\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/g;
|
|
597
|
+
for (const match of String(globalConfig || '').matchAll(re)) {
|
|
598
|
+
const block = match[1].trim();
|
|
599
|
+
const table = match[2].trim();
|
|
600
|
+
if (!new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\]`).test(next)) next = upsertTomlTable(next, table, block);
|
|
601
|
+
}
|
|
602
|
+
return next;
|
|
603
|
+
}
|
|
604
|
+
|
|
565
605
|
function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
566
|
-
const legacy = {
|
|
567
|
-
model_reasoning_effort: new Set(['high'])
|
|
568
|
-
};
|
|
569
606
|
const lines = String(text || '').split('\n');
|
|
570
607
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
571
608
|
const end = firstTable === -1 ? lines.length : firstTable;
|
|
572
609
|
return lines.filter((line, index) => {
|
|
573
610
|
if (index >= end) return true;
|
|
574
|
-
|
|
575
|
-
if (!match) return true;
|
|
576
|
-
return !legacy[match[1]]?.has(match[2]);
|
|
611
|
+
return !/^\s*model_reasoning_effort\s*=/.test(line);
|
|
577
612
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
578
613
|
}
|
|
579
614
|
|
|
@@ -874,7 +909,7 @@ export async function installSkills(root) {
|
|
|
874
909
|
'computer-use-fast': `---\nname: computer-use-fast\ndescription: Alias for the maximum-speed $Computer-Use/$CU Codex Computer Use lane.\n---\n\nUse the same rules as computer-use: skip Team debate, QA-LOOP clarification, upfront TriWiki refresh, Context7, subagents, and reflection unless explicitly requested. Use Codex Computer Use directly; never substitute Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or other browser automation for UI/browser evidence. At the end only, refresh/pack TriWiki, validate it, then provide a concise completion summary plus Honest Mode.\n`,
|
|
875
910
|
'cu': `---\nname: cu\ndescription: Short alias for the maximum-speed $Computer-Use Codex Computer Use lane.\n---\n\nUse the same rules as computer-use. This is a speed lane for focused UI/browser/visual tasks that require Codex Computer Use evidence, with TriWiki refresh/validate and Honest Mode deferred to final closeout.\n`,
|
|
876
911
|
'goal': `---\nname: goal\ndescription: Fast $Goal/$goal bridge overlay for Codex native persisted /goal workflows.\n---\n\nUse when the user invokes $Goal/$goal or asks to persist a workflow with Codex native /goal continuation. Prepare with sks goal create or the $Goal route, write only the lightweight bridge artifacts, then use native Codex /goal create, pause, resume, and clear controls where available. Goal does not replace Team, QA, DB, or other SKS execution routes; continue implementation through the selected route and use Context7 only when external API/library docs are involved. Do not recreate the old no-question loop.\n`,
|
|
877
|
-
'research': `---\nname: research\ndescription: Dollar-command route for $Research or $research frontier discovery workflows.\n---\n\nUse when the user invokes $Research/$research or asks for research, hypotheses, new mechanisms, falsification, or testable predictions. Prefer sks research prepare and sks research run. Run the genius-lens scout council with named persona-inspired cognitive roles: Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout. These are lenses only; do not impersonate the historical people. Every Research scout ledger row must include display_name, persona, persona_boundary, effort=xhigh, reasoning_effort=xhigh, service_tier when available, one literal "Eureka!" idea, falsifiers, cheap_probes, and challenge_or_response before synthesis. Create research-source-skill.md as a route-local Skill Creator artifact, then maximize layered public web/source search across papers, official/government or leading-institution data, standards/primary docs, current news, public discourse, developer/practitioner sources, and counterevidence before synthesis. Record research-source-skill.md, source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and research-gate.json. Context7 is optional and only needed when the research topic depends on external package/API/framework docs; do not use it as the default research evidence layer. Normal Research may take one or two hours when needed; favor real source collection, cross-layer comparison, falsification, and a concise paper manuscript over speed. Do not use --mock except for selftests or dry harness checks; if live source execution is unavailable, record a blocker and keep the gate unpassed. Do not use for ordinary code edits.\n`,
|
|
912
|
+
'research': `---\nname: research\ndescription: Dollar-command route for $Research or $research frontier discovery workflows.\n---\n\nUse when the user invokes $Research/$research or asks for research, hypotheses, new mechanisms, falsification, or testable predictions. Prefer sks research prepare and sks research run. Research is not an implementation route: do not edit repository source, docs, package metadata, generated skills, or harness files; write only route-local mission artifacts under .sneakoscope/missions/<mission-id>/. Run the genius-lens scout council with named persona-inspired cognitive roles: Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout. These are lenses only; do not impersonate the historical people. Every Research scout ledger row must include display_name, persona, persona_boundary, effort=xhigh, reasoning_effort=xhigh, service_tier when available, one literal "Eureka!" idea, falsifiers, cheap_probes, and challenge_or_response before synthesis. This is not a fixed three-cycle route: repeat source gathering, Eureka ideas, evidence-bound debate, falsification, and synthesis pressure until every scout records final agreement, or until the explicit max-cycle safety cap pauses with an unpassed gate. Create research-source-skill.md as a route-local Skill Creator artifact, then maximize layered public web/source search across latest papers, official/government or leading-institution data, standards/primary docs, current news, public discourse, developer/practitioner sources, traditional background sources, and counterevidence before synthesis. Record research-source-skill.md, source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and research-gate.json. debate-ledger.json must include consensus_iterations, unanimous_consensus, and per-scout agreements; research-gate.json cannot pass until unanimous_consensus=true with every scout agreement recorded. Context7 is optional and only needed when the research topic depends on external package/API/framework docs; do not use it as the default research evidence layer. Normal Research may take one or two hours when needed; favor real source collection, cross-layer comparison, falsification, and a concise paper manuscript over speed. Do not use --mock except for selftests or dry harness checks; if live source execution is unavailable, record a blocker and keep the gate unpassed. Do not use for ordinary code edits.\n`,
|
|
878
913
|
'autoresearch': `---\nname: autoresearch\ndescription: Dollar-command route for $AutoResearch or $autoresearch iterative experiment loops.\n---\n\nUse for $AutoResearch, iterative improvement, SEO/GEO, ranking, workflow, benchmark, or experiments. Define program, hypothesis, experiment, metric, keep/discard, falsification, next step, and Honest Mode. Load seo-geo-optimizer for README/npm/GitHub/schema/AI-search work.\n`,
|
|
879
914
|
'db': `---\nname: db\ndescription: Dollar-command route for $DB or $db database and Supabase safety checks.\n---\n\nUse when the user invokes $DB/$db or the task touches SQL, Supabase, Postgres, migrations, Prisma, Drizzle, Knex, MCP database tools, or production data. Run or follow sks db policy, sks db scan, sks db classify, and sks db check. Destructive database operations remain forbidden.\n`,
|
|
880
915
|
'mad-sks': `---\nname: mad-sks\ndescription: Explicit high-risk authorization modifier for $MAD-SKS scoped Supabase MCP DB permission widening.\n---\n\nUse only when the user explicitly invokes $MAD-SKS or top-level sks --mad. It can be combined with another route, such as $MAD-SKS $Team or $DB ... $MAD-SKS; in that case the other command remains the primary workflow and MAD-SKS is only the temporary permission grant. The widened permission applies only while the active mission gate is open, must be deactivated when the task ends, and opens live server work, Supabase MCP database writes, column/schema cleanup, direct execute SQL, migration application when required, and normal targeted DB writes. Keep only catastrophic safeguards: whole database/schema/table removal, truncate, all-row delete/update, reset, dangerous project/branch management, credential exfiltration, persistent security weakening, and unrequested fallback implementation remain blocked. Do not carry MAD-SKS permission into later prompts or routes. The permission profile is centralized in src/core/permission-gates.mjs so skill/hook/MCP-style gates share one decision function.\n`,
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -921,7 +921,7 @@ async function prepareResearch(root, route, task, required) {
|
|
|
921
921
|
await writeResearchPlan(dir, task, {});
|
|
922
922
|
const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task, required, ambiguity: { required: false, status: 'direct_route' } });
|
|
923
923
|
await setCurrent(root, routeState(id, route, 'RESEARCH_PREPARED', required, { prompt: task, pipeline_plan_ready: validatePipelinePlan(pipelinePlan).ok, pipeline_plan_path: PIPELINE_PLAN_ARTIFACT }));
|
|
924
|
-
return routeContext(route, id, task, required, 'Run sks research run latest as a real long-running source-gathering pass, never an automatic mock fallback; create research-source-skill.md, maximize layered public source search, require every scout effort=xhigh plus one Eureka! idea, fill source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and pass research-gate.json.');
|
|
924
|
+
return routeContext(route, id, task, required, 'Run sks research run latest as a real long-running source-gathering pass, never an automatic mock fallback; do not modify repository source code; create research-source-skill.md, maximize layered public source search, require every scout effort=xhigh plus one Eureka! idea, repeat scout/debate/falsification cycles until unanimous_consensus=true for every scout or the explicit safety cap pauses the run, fill source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and pass research-gate.json.');
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
async function prepareAutoResearch(root, route, task, required) {
|
|
@@ -1400,6 +1400,8 @@ function normalizeComplianceReason(reason = '') {
|
|
|
1400
1400
|
async function passedActiveGate(root, state) {
|
|
1401
1401
|
const id = state?.mission_id;
|
|
1402
1402
|
if (!id) return { ok: false, file: null };
|
|
1403
|
+
const hardBlocker = await passedHardBlocker(root, state);
|
|
1404
|
+
if (hardBlocker.ok) return hardBlocker;
|
|
1403
1405
|
const files = gateFilesForState(state);
|
|
1404
1406
|
for (const file of files) {
|
|
1405
1407
|
const p = path.join(missionDir(root, id), file);
|
|
@@ -1414,8 +1416,6 @@ async function passedActiveGate(root, state) {
|
|
|
1414
1416
|
return { ok: false, file };
|
|
1415
1417
|
}
|
|
1416
1418
|
}
|
|
1417
|
-
const hardBlocker = await passedHardBlocker(root, state);
|
|
1418
|
-
if (hardBlocker.ok) return hardBlocker;
|
|
1419
1419
|
return { ok: false, file: files[0] || null };
|
|
1420
1420
|
}
|
|
1421
1421
|
|