sneakoscope 2.0.5 → 2.0.6
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 +7 -4
- 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 +13 -8
- package/dist/cli/install-helpers.js +23 -0
- package/dist/commands/codex-app.js +25 -3
- package/dist/commands/doctor.js +19 -4
- package/dist/commands/mad-sks.js +2 -2
- package/dist/core/agents/agent-orchestrator.js +22 -3
- package/dist/core/agents/agent-proof-evidence.js +24 -2
- package/dist/core/agents/agent-worker-pipeline.js +9 -1
- package/dist/core/agents/native-worker-backend-router.js +19 -1
- package/dist/core/codex-app.js +124 -2
- package/dist/core/commands/naruto-command.js +9 -4
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +2 -233
- package/dist/core/init.js +8 -8
- package/dist/core/naruto/naruto-active-pool.js +20 -4
- package/dist/core/pipeline-internals/runtime-core.js +1 -1
- package/dist/core/ppt.js +31 -8
- package/dist/core/product-design-app-server.js +410 -0
- package/dist/core/product-design-plugin.js +139 -0
- package/dist/core/routes.js +8 -8
- package/dist/core/version.js +1 -1
- package/dist/scripts/naruto-active-pool-check.js +13 -1
- package/dist/scripts/naruto-readonly-routing-check.js +116 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +7 -0
- package/dist/scripts/product-design-auto-install-check.js +119 -0
- package/dist/scripts/product-design-plugin-routing-check.js +101 -0
- package/dist/scripts/release-parallel-check.js +15 -1
- package/dist/scripts/release-provenance-check.js +21 -0
- package/package.json +5 -2
package/dist/core/codex-app.js
CHANGED
|
@@ -4,6 +4,8 @@ import fsp from 'node:fs/promises';
|
|
|
4
4
|
import { exists, runProcess } from './fsx.js';
|
|
5
5
|
import { EMPTY_CODEX_INFO, getCodexInfo } from './codex-adapter.js';
|
|
6
6
|
import { CODEX_CHROME_EXTENSION_DOC_URL, DEFAULT_CODEX_APP_PLUGINS as DEFAULT_CODEX_APP_PLUGIN_TUPLES, RESERVED_CODEX_PLUGIN_SKILL_NAMES } from './routes.js';
|
|
7
|
+
import { PRODUCT_DESIGN_PLUGIN, normalizeProductDesignPluginEvidence } from './product-design-plugin.js';
|
|
8
|
+
import { PRODUCT_DESIGN_AUTO_INSTALL_ENV, ensureProductDesignPluginInstalled, productDesignAutoInstallRequested } from './product-design-app-server.js';
|
|
7
9
|
export const CODEX_APP_DOCS_URL = 'https://developers.openai.com/codex/app/features';
|
|
8
10
|
export const CODEX_CHANGELOG_URL = 'https://developers.openai.com/codex/changelog';
|
|
9
11
|
export const CODEX_ACCESS_TOKENS_DOCS_URL = 'https://developers.openai.com/codex/enterprise/access-tokens';
|
|
@@ -128,6 +130,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
|
|
|
128
130
|
const chromePath = await findPluginCache('chrome', opts);
|
|
129
131
|
const computerUsePath = await findPluginCache('computer-use', opts);
|
|
130
132
|
const defaultPlugins = await codexDefaultPluginStatus(opts);
|
|
133
|
+
const productDesignPlugin = await codexProductDesignPluginStatus(opts);
|
|
131
134
|
const pluginSkillShadows = await codexPluginSkillShadowStatus(opts);
|
|
132
135
|
const fastModeConfig = await codexFastModeConfigStatus(opts);
|
|
133
136
|
const computerUseMcpListed = /computer[-_ ]?use/i.test(mcpText);
|
|
@@ -209,6 +212,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
|
|
|
209
212
|
browser_use_cache: browserUsePath,
|
|
210
213
|
chrome_cache: chromePath,
|
|
211
214
|
default_plugins: defaultPlugins,
|
|
215
|
+
design_product: productDesignPlugin,
|
|
212
216
|
skill_shadows: pluginSkillShadows,
|
|
213
217
|
picker: {
|
|
214
218
|
ok: pluginPickerReady,
|
|
@@ -219,7 +223,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
|
|
|
219
223
|
}
|
|
220
224
|
},
|
|
221
225
|
chrome_extension: chromeExtension,
|
|
222
|
-
guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, pluginSkillShadows, fastModeConfig, gitActions, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl })
|
|
226
|
+
guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, productDesignPlugin, pluginSkillShadows, fastModeConfig, gitActions, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl })
|
|
223
227
|
};
|
|
224
228
|
}
|
|
225
229
|
export async function codexChromeExtensionStatus(opts = {}) {
|
|
@@ -438,7 +442,7 @@ export function codexAccessTokenStatus(env = process.env) {
|
|
|
438
442
|
: ['No CODEX_ACCESS_TOKEN detected in the current process environment. This is fine for interactive ChatGPT login or API-key auth.']
|
|
439
443
|
};
|
|
440
444
|
}
|
|
441
|
-
export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, pluginSkillShadows = { ok: true, blocking: [] }, fastModeConfig = { ok: true, blockers: [] }, gitActions = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl }) {
|
|
445
|
+
export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, productDesignPlugin = null, pluginSkillShadows = { ok: true, blocking: [] }, fastModeConfig = { ok: true, blockers: [] }, gitActions = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl }) {
|
|
442
446
|
const lines = [];
|
|
443
447
|
if (!appInstalled) {
|
|
444
448
|
lines.push('Install and open Codex App for first-party MCP/plugin tools. SKS Zellij 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.');
|
|
@@ -474,6 +478,18 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
|
|
|
474
478
|
lines.push(`Codex default plugin source(s) missing: ${defaultPlugins.missing_installed.join(', ')}. The @ plugin picker can hide built-in surfaces when plugin files are absent even if config says enabled.`);
|
|
475
479
|
lines.push('Run: sks doctor --fix, then restart Codex App if the plugin cache was just restored.');
|
|
476
480
|
}
|
|
481
|
+
if (productDesignPlugin?.ok) {
|
|
482
|
+
lines.push(`Product Design plugin is ready for design routes via ${productDesignPlugin.source || 'verified evidence'} (${productDesignPlugin.id}).`);
|
|
483
|
+
}
|
|
484
|
+
else if (productDesignPlugin?.auto_install?.attempted) {
|
|
485
|
+
lines.push(`Product Design auto-install was attempted but did not produce ready evidence: ${(productDesignPlugin.blockers || []).join(', ') || 'unverified'}. Recheck with: ${productDesignPlugin.auto_install.command}`);
|
|
486
|
+
}
|
|
487
|
+
else if (productDesignPlugin?.app_server?.checked && !productDesignPlugin.app_server.ok) {
|
|
488
|
+
lines.push(`Product Design app-server lookup ran but is not ready: ${(productDesignPlugin.blockers || []).join(', ') || 'unverified'}. Run: ${productDesignPlugin.auto_install?.command || 'sks codex-app product-design --json'}`);
|
|
489
|
+
}
|
|
490
|
+
else if (productDesignPlugin?.remote_lookup_required) {
|
|
491
|
+
lines.push(`Product Design is a remote vertical marketplace plugin and may not appear in \`codex plugin list\`; design routes should run ${productDesignPlugin.auto_install?.command || 'sks codex-app product-design --json'} or set ${PRODUCT_DESIGN_AUTO_INSTALL_ENV}=1 before falling back to legacy design.md skills.`);
|
|
492
|
+
}
|
|
477
493
|
if (pluginSkillShadows?.generated?.length) {
|
|
478
494
|
const names = pluginSkillShadows.generated.map((entry) => `${entry.name}:${entry.scope}`).join(', ');
|
|
479
495
|
lines.push(`Codex plugin picker generated skill shadow(s) detected: ${names}. Generated SKS skills with first-party plugin names can hide @ plugin entries after upgrades.`);
|
|
@@ -536,6 +552,7 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
|
|
|
536
552
|
`App Flags: ${status.features?.required_flags_ok ? 'ok' : `missing ${missingRequiredFeatureFlags(status.features?.required_flags).join(', ') || 'required flags'}`}`,
|
|
537
553
|
`Fast UI: ${status.features?.fast_mode_config?.ok ? 'ok' : `locked ${(status.features?.fast_mode_config?.blockers || []).join(', ') || 'config'}`}`,
|
|
538
554
|
`Default Plugins:${status.plugins?.default_plugins?.ok ? ' ok' : ` missing ${defaultPluginMissingSummary(status.plugins?.default_plugins) || 'plugin install/config'}`}`,
|
|
555
|
+
`Product Design:${productDesignStatusSummary(status.plugins?.design_product)}`,
|
|
539
556
|
`Plugin Picker:${status.plugins?.picker?.ok ? ' ok' : ` blocked ${pluginPickerBlockers(status).join(', ') || 'config'}`}`,
|
|
540
557
|
`Git Actions:${status.features?.git_actions?.ok ? ' ok' : ` blocked ${(status.features?.git_actions?.blockers || []).join(', ') || 'config'}`}`,
|
|
541
558
|
`Chrome Ext: ${status.chrome_extension?.ok ? 'ok' : `setup ${(status.chrome_extension?.blockers || []).join(', ') || 'required'}`}`,
|
|
@@ -552,6 +569,30 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
|
|
|
552
569
|
lines.push('', status.features.stdout.trim());
|
|
553
570
|
return lines.join('\n');
|
|
554
571
|
}
|
|
572
|
+
export function formatCodexProductDesignPluginStatus(status) {
|
|
573
|
+
const lines = [
|
|
574
|
+
'Codex App Product Design Plugin',
|
|
575
|
+
'',
|
|
576
|
+
`Ready: ${status.ok ? 'yes' : 'no'}`,
|
|
577
|
+
`Installed: ${status.installed ? 'yes' : 'no'}`,
|
|
578
|
+
`Enabled: ${status.enabled === true ? 'yes' : status.enabled === false ? 'no' : 'unknown'}`,
|
|
579
|
+
`Source: ${status.source || 'unverified'}`,
|
|
580
|
+
`Marketplace: ${status.marketplace}`,
|
|
581
|
+
`Remote ID: ${status.remote_plugin_id}`,
|
|
582
|
+
`Auto Install: ${status.auto_install?.requested ? 'requested' : 'not requested'}${status.auto_install?.attempted ? ' (attempted)' : ''}`,
|
|
583
|
+
`Command: ${status.auto_install?.command || 'sks codex-app product-design --json'}`,
|
|
584
|
+
'',
|
|
585
|
+
...(status.blockers || []).map((blocker) => `- ${blocker}`)
|
|
586
|
+
];
|
|
587
|
+
if (status.remote_evidence?.missing_skills?.length) {
|
|
588
|
+
lines.push(`- missing skills: ${status.remote_evidence.missing_skills.join(', ')}`);
|
|
589
|
+
}
|
|
590
|
+
if (status.ok)
|
|
591
|
+
lines.push('- Product Design is ready for design routes.');
|
|
592
|
+
else if (!status.auto_install?.requested)
|
|
593
|
+
lines.push('- Run `sks codex-app product-design --json`, or set SKS_PRODUCT_DESIGN_AUTO_INSTALL=1 for design-route auto-ensure.');
|
|
594
|
+
return lines.join('\n');
|
|
595
|
+
}
|
|
555
596
|
function summarizeCodexMcpError(text) {
|
|
556
597
|
const cleanLines = String(text || '')
|
|
557
598
|
.split(/\r?\n/)
|
|
@@ -629,6 +670,76 @@ async function codexDefaultPluginStatus(opts = {}) {
|
|
|
629
670
|
missing_enabled: missingEnabled
|
|
630
671
|
};
|
|
631
672
|
}
|
|
673
|
+
export async function codexProductDesignPluginStatus(opts = {}) {
|
|
674
|
+
const home = opts.home || os.homedir();
|
|
675
|
+
const cwd = opts.cwd || process.cwd();
|
|
676
|
+
const globalConfigPath = path.join(home || '', '.codex', 'config.toml');
|
|
677
|
+
const projectConfigPath = path.join(cwd || '', '.codex', 'config.toml');
|
|
678
|
+
const globalConfig = await readTextIfExists(globalConfigPath);
|
|
679
|
+
const projectConfig = path.resolve(projectConfigPath) === path.resolve(globalConfigPath)
|
|
680
|
+
? ''
|
|
681
|
+
: await readTextIfExists(projectConfigPath);
|
|
682
|
+
const configText = `${globalConfig}\n${projectConfig}`;
|
|
683
|
+
const plugin = { name: PRODUCT_DESIGN_PLUGIN.name, marketplace: PRODUCT_DESIGN_PLUGIN.marketplace };
|
|
684
|
+
const autoInstallProductDesign = productDesignAutoInstallRequested(opts);
|
|
685
|
+
const appServerStatus = opts.productDesignAppServerStatus || (autoInstallProductDesign
|
|
686
|
+
? await ensureProductDesignPluginInstalled({
|
|
687
|
+
...opts,
|
|
688
|
+
autoInstallProductDesign
|
|
689
|
+
})
|
|
690
|
+
: null);
|
|
691
|
+
const injectedRemoteEvidence = opts.productDesignPluginReadResponse
|
|
692
|
+
? normalizeProductDesignPluginEvidence(opts.productDesignPluginReadResponse)
|
|
693
|
+
: null;
|
|
694
|
+
const appServerEvidence = appServerStatus?.remote_evidence?.schema === 'sks.product-design-plugin-evidence.v1'
|
|
695
|
+
? appServerStatus.remote_evidence
|
|
696
|
+
: null;
|
|
697
|
+
const remoteEvidence = appServerEvidence || injectedRemoteEvidence;
|
|
698
|
+
const localSource = await findDefaultPluginSource(plugin, { home, configText });
|
|
699
|
+
const configEnabled = codexPluginEnabled(configText, plugin);
|
|
700
|
+
const enabled = remoteEvidence?.enabled === true ? true : configEnabled ? true : null;
|
|
701
|
+
const installed = Boolean(localSource) || remoteEvidence?.installed === true;
|
|
702
|
+
const ok = Boolean(remoteEvidence?.ok || (installed && enabled === true));
|
|
703
|
+
const remoteLookupRequired = !remoteEvidence?.ok && (!localSource || enabled !== true) && !appServerStatus?.checked;
|
|
704
|
+
const blockers = ok ? [] : Array.from(new Set([
|
|
705
|
+
...(!installed ? ['product_design_plugin_not_installed_or_not_locally_visible'] : []),
|
|
706
|
+
...(enabled !== true ? ['product_design_plugin_enabled_state_requires_remote_evidence'] : []),
|
|
707
|
+
...(remoteLookupRequired ? ['product_design_remote_vertical_lookup_required'] : []),
|
|
708
|
+
...(autoInstallProductDesign && appServerStatus && !appServerStatus.ok ? ['product_design_app_server_install_failed'] : []),
|
|
709
|
+
...(appServerStatus?.blockers || [])
|
|
710
|
+
]));
|
|
711
|
+
return {
|
|
712
|
+
schema: 'sks.codex-product-design-plugin-status.v1',
|
|
713
|
+
ok,
|
|
714
|
+
checked: true,
|
|
715
|
+
route_required_only: true,
|
|
716
|
+
id: PRODUCT_DESIGN_PLUGIN.id,
|
|
717
|
+
name: PRODUCT_DESIGN_PLUGIN.name,
|
|
718
|
+
display_name: PRODUCT_DESIGN_PLUGIN.display_name,
|
|
719
|
+
marketplace: PRODUCT_DESIGN_PLUGIN.marketplace,
|
|
720
|
+
marketplace_kind: PRODUCT_DESIGN_PLUGIN.marketplace_kind,
|
|
721
|
+
remote_plugin_id: PRODUCT_DESIGN_PLUGIN.remote_plugin_id,
|
|
722
|
+
installed,
|
|
723
|
+
enabled,
|
|
724
|
+
source: remoteEvidence?.ok
|
|
725
|
+
? appServerStatus?.install_attempted ? 'app_server_plugin_install' : 'app_server_plugin_read'
|
|
726
|
+
: localSource ? 'local_plugin_cache_or_marketplace_source' : null,
|
|
727
|
+
local_source: localSource,
|
|
728
|
+
remote_evidence: remoteEvidence,
|
|
729
|
+
app_server: appServerStatus,
|
|
730
|
+
auto_install: {
|
|
731
|
+
requested: autoInstallProductDesign,
|
|
732
|
+
attempted: Boolean(appServerStatus?.install_attempted),
|
|
733
|
+
command: 'sks codex-app product-design --json',
|
|
734
|
+
env: PRODUCT_DESIGN_AUTO_INSTALL_ENV
|
|
735
|
+
},
|
|
736
|
+
remote_lookup_required: remoteLookupRequired,
|
|
737
|
+
app_server_read_params: PRODUCT_DESIGN_PLUGIN.app_server.read_params,
|
|
738
|
+
app_server_install_params: PRODUCT_DESIGN_PLUGIN.app_server.install_params,
|
|
739
|
+
app_server_list_params: PRODUCT_DESIGN_PLUGIN.app_server.list_params,
|
|
740
|
+
blockers
|
|
741
|
+
};
|
|
742
|
+
}
|
|
632
743
|
async function codexPluginSkillShadowStatus(opts = {}) {
|
|
633
744
|
const home = opts.home || os.homedir();
|
|
634
745
|
const cwd = opts.cwd || process.cwd();
|
|
@@ -754,6 +865,17 @@ function pluginPickerBlockers(status = {}) {
|
|
|
754
865
|
out.push('fast_mode_config');
|
|
755
866
|
return out;
|
|
756
867
|
}
|
|
868
|
+
function productDesignStatusSummary(status = {}) {
|
|
869
|
+
if (status.ok)
|
|
870
|
+
return ' ok';
|
|
871
|
+
if (status.auto_install?.attempted)
|
|
872
|
+
return ' install unverified';
|
|
873
|
+
if (status.remote_lookup_required)
|
|
874
|
+
return ' remote lookup required';
|
|
875
|
+
if (status.app_server?.checked)
|
|
876
|
+
return ' app-server unverified';
|
|
877
|
+
return ' not checked';
|
|
878
|
+
}
|
|
757
879
|
function defaultPluginMissingSummary(defaultPlugins = {}) {
|
|
758
880
|
return [
|
|
759
881
|
...(defaultPlugins?.missing_installed || []),
|
|
@@ -34,10 +34,11 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
34
34
|
async function narutoRun(parsed) {
|
|
35
35
|
const root = await sksRoot();
|
|
36
36
|
const writeCapable = parsed.readonly !== true && parsed.writeMode !== 'off';
|
|
37
|
+
const patchEnvelopeBasePath = '.sneakoscope/naruto/patch-envelopes';
|
|
37
38
|
const placeholderGuard = checkPromptPlaceholders({
|
|
38
39
|
prompt: parsed.prompt,
|
|
39
40
|
writeCapable,
|
|
40
|
-
targetPaths: writeCapable ? [
|
|
41
|
+
targetPaths: writeCapable ? [patchEnvelopeBasePath] : []
|
|
41
42
|
});
|
|
42
43
|
if (!placeholderGuard.ok) {
|
|
43
44
|
return emit(parsed, {
|
|
@@ -72,7 +73,7 @@ async function narutoRun(parsed) {
|
|
|
72
73
|
totalWorkItems: parsed.workItems,
|
|
73
74
|
readonly: parsed.readonly,
|
|
74
75
|
writeCapable,
|
|
75
|
-
|
|
76
|
+
leaseBasePath: patchEnvelopeBasePath,
|
|
76
77
|
maxActiveWorkers: parsed.concurrency || safe.cap
|
|
77
78
|
});
|
|
78
79
|
const roleDistribution = buildNarutoRoleDistribution(workGraph.work_items, { readonly: parsed.readonly });
|
|
@@ -163,7 +164,7 @@ async function narutoRun(parsed) {
|
|
|
163
164
|
fastMode: true,
|
|
164
165
|
serviceTier: 'fast',
|
|
165
166
|
noFast: false,
|
|
166
|
-
|
|
167
|
+
writeMode: writeCapable ? parsed.writeMode || 'parallel' : 'off',
|
|
167
168
|
json: parsed.json
|
|
168
169
|
});
|
|
169
170
|
const clones = result.roster?.agent_count ?? roster.agent_count;
|
|
@@ -185,6 +186,8 @@ async function narutoRun(parsed) {
|
|
|
185
186
|
total_work_items: workGraph.total_work_items,
|
|
186
187
|
mixed_work_kinds: workGraph.mixed_work_kinds,
|
|
187
188
|
write_allowed_count: workGraph.write_allowed_count,
|
|
189
|
+
active_wave_count: workGraph.active_waves.length,
|
|
190
|
+
parallel_write_wave_count: workGraph.active_waves.filter((wave) => wave.write_paths.length > 1).length,
|
|
188
191
|
ok: workGraph.ok
|
|
189
192
|
},
|
|
190
193
|
role_distribution: roleDistribution,
|
|
@@ -251,7 +254,9 @@ async function narutoStatus(parsed) {
|
|
|
251
254
|
work_graph: workGraph ? {
|
|
252
255
|
total_work_items: workGraph.total_work_items,
|
|
253
256
|
mixed_work_kinds: workGraph.mixed_work_kinds,
|
|
254
|
-
write_allowed_count: workGraph.write_allowed_count
|
|
257
|
+
write_allowed_count: workGraph.write_allowed_count,
|
|
258
|
+
active_wave_count: Array.isArray(workGraph.active_waves) ? workGraph.active_waves.length : null,
|
|
259
|
+
parallel_write_wave_count: Array.isArray(workGraph.active_waves) ? workGraph.active_waves.filter((wave) => Array.isArray(wave.write_paths) && wave.write_paths.length > 1).length : null
|
|
255
260
|
} : null,
|
|
256
261
|
concurrency_governor: governor
|
|
257
262
|
};
|
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 = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.6';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { projectRoot, readJson, readText, writeJsonAtomic, appendJsonl, readStdin, nowIso, runProcess,
|
|
2
|
+
import { projectRoot, readJson, readText, writeJsonAtomic, appendJsonl, readStdin, nowIso, runProcess, sha256, packageRoot, tmpdir } from './fsx.js';
|
|
3
3
|
import { looksInteractiveCommand, interactiveCommandReason } from './no-question-guard.js';
|
|
4
4
|
import { missionDir, setCurrent, stateFile } from './mission.js';
|
|
5
5
|
import { checkDbOperation, dbBlockReason, handleMadSksUserConfirmation } from './db-safety.js';
|
|
@@ -12,7 +12,6 @@ import { classifyToolError } from './evaluation.js';
|
|
|
12
12
|
import { REQUIRED_CODEX_MODEL, isForbiddenCodexModel } from './codex-model-guard.js';
|
|
13
13
|
import { dollarCommand, routeRequiresSubagents, stripVisibleDecisionAnswerBlocks } from './routes.js';
|
|
14
14
|
import { appendMissionStatus } from './recallpulse.js';
|
|
15
|
-
import { detectEffectiveSksVersion, runSksUpdateCheck } from './update-check.js';
|
|
16
15
|
import { scanAgentTextForRecursion } from './agents/agent-recursion-guard.js';
|
|
17
16
|
import { buildCompactContinue, buildPermissionRequestAllow, buildPermissionRequestDeny, buildPostToolUseBlock, buildPostToolUseContinue, buildPreToolUseContinue, buildPreToolUseDeny, buildSessionStartContinue, buildStopBlock, buildStopContinue, buildSubagentStartContinue, buildSubagentStopBlock, buildSubagentStopContinue, buildUserPromptSubmitBlock, buildUserPromptSubmitContinue } from './codex-compat/codex-hook-output-builders.js';
|
|
18
17
|
const TEAM_DIGEST_MAX_EVENTS = 4;
|
|
@@ -165,12 +164,6 @@ function toolFailed(payload = {}) {
|
|
|
165
164
|
return true;
|
|
166
165
|
return false;
|
|
167
166
|
}
|
|
168
|
-
function looksLikeUpdateDecline(prompt) {
|
|
169
|
-
return /^(no|nope|skip|later|not now|don't|dont|아니|아니요|안해|안 함|나중에|건너뛰|스킵)/i.test(String(prompt || '').trim());
|
|
170
|
-
}
|
|
171
|
-
function looksLikeUpdateAccept(prompt) {
|
|
172
|
-
return /^(yes|y|ok|okay|update|upgrade|do it|go ahead|응|네|예|업데이트|해줘|진행)/i.test(String(prompt || '').trim());
|
|
173
|
-
}
|
|
174
167
|
export async function hookMain(name) {
|
|
175
168
|
const payload = await loadHookPayload();
|
|
176
169
|
return evaluateHookPayload(name, payload);
|
|
@@ -281,14 +274,7 @@ async function hookUserPrompt(root, state, payload, noQuestion) {
|
|
|
281
274
|
const additionalContext = [madSksConfirmation.additionalContext, teamDigest?.context].filter(Boolean).join('\n\n');
|
|
282
275
|
return { continue: true, additionalContext, systemMessage: joinSystemMessages(visibleHookMessage('user-prompt-submit', additionalContext), teamDigest?.system) };
|
|
283
276
|
}
|
|
284
|
-
const
|
|
285
|
-
const updateContext = updateCheck.text;
|
|
286
|
-
if (updateCheck.blocksRouting) {
|
|
287
|
-
const teamDigest = updateCheck.resumeActiveRoute ? await teamLiveDigest(root, state) : null;
|
|
288
|
-
const activeContext = updateCheck.resumeActiveRoute ? await activeRouteContext(root, state) : '';
|
|
289
|
-
const additionalContext = [updateContext, activeContext, teamDigest?.context].filter(Boolean).join('\n\n');
|
|
290
|
-
return { continue: true, additionalContext, systemMessage: joinSystemMessages(visibleHookMessage('user-prompt-submit', additionalContext), teamDigest?.system) };
|
|
291
|
-
}
|
|
277
|
+
const updateContext = '';
|
|
292
278
|
const command = dollarCommand(prompt);
|
|
293
279
|
const route = routePrompt(prompt);
|
|
294
280
|
if (routeIsGitOnly(route)) {
|
|
@@ -871,221 +857,6 @@ function pruneStopRepeatEntries(entries = {}, now = nowIso()) {
|
|
|
871
857
|
.sort((a, b) => Date.parse(b[1]?.last_seen || '') - Date.parse(a[1]?.last_seen || ''))
|
|
872
858
|
.slice(0, STOP_REPEAT_GUARD_MAX_ENTRIES));
|
|
873
859
|
}
|
|
874
|
-
async function updateCheckContext(root, payload, prompt) {
|
|
875
|
-
if (process.env.SKS_DISABLE_UPDATE_CHECK === '1')
|
|
876
|
-
return updateCheckResult('');
|
|
877
|
-
const statePath = path.join(root, '.sneakoscope', 'state', 'update-check.json');
|
|
878
|
-
const updateState = await readJson(statePath, {});
|
|
879
|
-
const conv = conversationId(payload);
|
|
880
|
-
const pending = updateState.pending_offer;
|
|
881
|
-
let effective = null;
|
|
882
|
-
async function effectiveVersion() {
|
|
883
|
-
if (!effective) {
|
|
884
|
-
const installed = await detectEffectiveSksVersion({ timeoutMs: 2500 });
|
|
885
|
-
effective = {
|
|
886
|
-
installed,
|
|
887
|
-
current: highestVersion([PACKAGE_VERSION, installed.current])
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
return effective;
|
|
891
|
-
}
|
|
892
|
-
if (pending?.latest) {
|
|
893
|
-
const currentCheck = await effectiveVersion();
|
|
894
|
-
if (compareVersions(pending.latest, currentCheck.current) <= 0) {
|
|
895
|
-
await writeJsonAtomic(statePath, {
|
|
896
|
-
...updateState,
|
|
897
|
-
current: currentCheck.current,
|
|
898
|
-
runtime_current: PACKAGE_VERSION,
|
|
899
|
-
installed_current: currentCheck.installed.current || null,
|
|
900
|
-
installed_sources: currentCheck.installed.candidates || [],
|
|
901
|
-
latest: pending.latest,
|
|
902
|
-
checked_at: nowIso(),
|
|
903
|
-
pending_offer: null,
|
|
904
|
-
check_error: null
|
|
905
|
-
});
|
|
906
|
-
return updateCheckResult('');
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
if (updateState.skipped?.latest) {
|
|
910
|
-
const currentCheck = await effectiveVersion();
|
|
911
|
-
if (compareVersions(updateState.skipped.latest, currentCheck.current) <= 0) {
|
|
912
|
-
await writeJsonAtomic(statePath, {
|
|
913
|
-
...updateState,
|
|
914
|
-
current: currentCheck.current,
|
|
915
|
-
runtime_current: PACKAGE_VERSION,
|
|
916
|
-
installed_current: currentCheck.installed.current || null,
|
|
917
|
-
installed_sources: currentCheck.installed.candidates || [],
|
|
918
|
-
latest: updateState.skipped.latest,
|
|
919
|
-
checked_at: nowIso(),
|
|
920
|
-
pending_offer: null,
|
|
921
|
-
skipped: null,
|
|
922
|
-
check_error: null
|
|
923
|
-
});
|
|
924
|
-
return updateCheckResult('');
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
if (updateState.accepted?.latest) {
|
|
928
|
-
const currentCheck = await effectiveVersion();
|
|
929
|
-
if (compareVersions(updateState.accepted.latest, currentCheck.current) <= 0) {
|
|
930
|
-
await writeJsonAtomic(statePath, {
|
|
931
|
-
...updateState,
|
|
932
|
-
current: currentCheck.current,
|
|
933
|
-
runtime_current: PACKAGE_VERSION,
|
|
934
|
-
installed_current: currentCheck.installed.current || null,
|
|
935
|
-
installed_sources: currentCheck.installed.candidates || [],
|
|
936
|
-
latest: updateState.accepted.latest,
|
|
937
|
-
checked_at: nowIso(),
|
|
938
|
-
pending_offer: null,
|
|
939
|
-
accepted: null,
|
|
940
|
-
check_error: null
|
|
941
|
-
});
|
|
942
|
-
return updateCheckResult('');
|
|
943
|
-
}
|
|
944
|
-
if (updateState.accepted.conversation_id === conv) {
|
|
945
|
-
return updateCheckResult(`SKS update check: update ${updateState.accepted.latest} was already accepted for this conversation. Run exactly this command and nothing else: ${sksUpdateInstallCommand(updateState.accepted.latest)}. Do not ask again or start a pipeline route until the update command has completed.`, {
|
|
946
|
-
blocksRouting: true
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
if (pending?.conversation_id === conv && pending?.latest && looksLikeUpdateDecline(prompt)) {
|
|
951
|
-
await writeJsonAtomic(statePath, {
|
|
952
|
-
...updateState,
|
|
953
|
-
pending_offer: null,
|
|
954
|
-
skipped: { conversation_id: conv, latest: pending.latest, skipped_at: nowIso() }
|
|
955
|
-
});
|
|
956
|
-
return updateCheckResult(`SKS update check: user skipped update to ${pending.latest} for this conversation only. Continue the previous task without updating. Check again on the next conversation.`, {
|
|
957
|
-
blocksRouting: true,
|
|
958
|
-
resumeActiveRoute: true
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
if (pending?.conversation_id === conv && pending?.latest && looksLikeUpdateAccept(prompt)) {
|
|
962
|
-
const command = sksUpdateInstallCommand(pending.latest);
|
|
963
|
-
await writeJsonAtomic(statePath, {
|
|
964
|
-
...updateState,
|
|
965
|
-
pending_offer: null,
|
|
966
|
-
accepted: { conversation_id: conv, latest: pending.latest, accepted_at: nowIso() }
|
|
967
|
-
});
|
|
968
|
-
return updateCheckResult(`SKS update check: user accepted update to ${pending.latest}. Before doing other work, run exactly this command and nothing else: ${command}. Do not start a pipeline route, run setup, or run doctor for this accepted update command.`, {
|
|
969
|
-
blocksRouting: true
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
if (pending?.conversation_id === conv && pending?.latest) {
|
|
973
|
-
// Don't re-inject the full update choice on EVERY prompt — that is the "tiny
|
|
974
|
-
// update text keeps appearing" nag. After the choice has been shown, stay quiet
|
|
975
|
-
// for a short window so the user can keep working; we re-surface it once the
|
|
976
|
-
// window elapses (or next conversation). Accept/decline are handled above and
|
|
977
|
-
// still take effect immediately. SKS_UPDATE_OFFER_THROTTLE_MS=0 restores the
|
|
978
|
-
// old always-repeat behavior.
|
|
979
|
-
const throttleMs = updateOfferThrottleMs();
|
|
980
|
-
const lastOfferedMs = Date.parse(pending.offered_at || '') || 0;
|
|
981
|
-
if (throttleMs > 0 && lastOfferedMs > 0 && Date.now() - lastOfferedMs < throttleMs) {
|
|
982
|
-
return updateCheckResult('');
|
|
983
|
-
}
|
|
984
|
-
await writeJsonAtomic(statePath, { ...updateState, pending_offer: { ...pending, offered_at: nowIso() } });
|
|
985
|
-
return updateCheckResult(copyStableUpdateChoiceText(pending.latest), {
|
|
986
|
-
blocksRouting: true
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
if (updateState.skipped?.conversation_id === conv && updateState.skipped?.latest) {
|
|
990
|
-
return updateCheckResult(`SKS update check: update ${updateState.skipped.latest} was skipped for this conversation only. Do not ask again in this conversation; check again next conversation.`);
|
|
991
|
-
}
|
|
992
|
-
const check = await checkLatestVersion();
|
|
993
|
-
const { installed, current } = await effectiveVersion();
|
|
994
|
-
const isCurrent = check.latest && compareVersions(check.latest, current) <= 0;
|
|
995
|
-
await writeJsonAtomic(statePath, {
|
|
996
|
-
...updateState,
|
|
997
|
-
current,
|
|
998
|
-
runtime_current: PACKAGE_VERSION,
|
|
999
|
-
installed_current: installed.current || null,
|
|
1000
|
-
installed_sources: installed.candidates || [],
|
|
1001
|
-
latest: check.latest || null,
|
|
1002
|
-
checked_at: nowIso(),
|
|
1003
|
-
pending_offer: isCurrent ? null : updateState.pending_offer || null,
|
|
1004
|
-
check_error: check.error || null
|
|
1005
|
-
});
|
|
1006
|
-
if (!check.latest || check.error || isCurrent)
|
|
1007
|
-
return updateCheckResult('');
|
|
1008
|
-
await writeJsonAtomic(statePath, {
|
|
1009
|
-
...updateState,
|
|
1010
|
-
current,
|
|
1011
|
-
runtime_current: PACKAGE_VERSION,
|
|
1012
|
-
installed_current: installed.current || null,
|
|
1013
|
-
installed_sources: installed.candidates || [],
|
|
1014
|
-
latest: check.latest,
|
|
1015
|
-
checked_at: nowIso(),
|
|
1016
|
-
pending_offer: { conversation_id: conv, latest: check.latest, offered_at: nowIso() },
|
|
1017
|
-
skipped: updateState.skipped?.conversation_id === conv ? null : updateState.skipped || null
|
|
1018
|
-
});
|
|
1019
|
-
return updateCheckResult(copyStableUpdateChoiceText(check.latest, current), {
|
|
1020
|
-
blocksRouting: true
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
function updateCheckResult(text, opts = {}) {
|
|
1024
|
-
return {
|
|
1025
|
-
text: String(text || ''),
|
|
1026
|
-
blocksRouting: opts.blocksRouting === true,
|
|
1027
|
-
resumeActiveRoute: opts.resumeActiveRoute === true
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
// How long (ms) to stay quiet after showing the update choice before re-surfacing
|
|
1031
|
-
// it in the same conversation. Default 8 minutes; set 0 to always repeat (legacy).
|
|
1032
|
-
function updateOfferThrottleMs() {
|
|
1033
|
-
const raw = Number(process.env.SKS_UPDATE_OFFER_THROTTLE_MS);
|
|
1034
|
-
if (Number.isFinite(raw) && raw >= 0)
|
|
1035
|
-
return Math.floor(raw);
|
|
1036
|
-
return 8 * 60 * 1000;
|
|
1037
|
-
}
|
|
1038
|
-
function sksUpdateInstallCommand(version) {
|
|
1039
|
-
return `sks update now --version ${version}`;
|
|
1040
|
-
}
|
|
1041
|
-
function copyStableUpdateChoiceText(latest, current = null) {
|
|
1042
|
-
const installed = current ? `installed ${current}, latest ${latest}` : `latest ${latest}`;
|
|
1043
|
-
return `SKS update check: ${installed}. Before any other work, ask the user to choose exactly one copy-stable option: "Update SKS now" or "Skip update for this conversation". If the user sends anything else while this update choice is pending, repeat this same choice and do not start a pipeline route. If they choose update, run exactly this command and nothing else: ${sksUpdateInstallCommand(latest)}. Do not start a pipeline route, run setup, or run doctor for this accepted update command. If they skip, do not ask again in this conversation, but check again next conversation.`;
|
|
1044
|
-
}
|
|
1045
|
-
async function checkLatestVersion() {
|
|
1046
|
-
const check = await runSksUpdateCheck({ timeoutMs: 3500 });
|
|
1047
|
-
return { latest: check.latest, error: check.error || undefined };
|
|
1048
|
-
}
|
|
1049
|
-
async function detectInstalledSksVersion() {
|
|
1050
|
-
const override = parseVersionText(process.env.SKS_INSTALLED_SKS_VERSION || '');
|
|
1051
|
-
if (override)
|
|
1052
|
-
return { version: override, source: 'env' };
|
|
1053
|
-
const candidates = [];
|
|
1054
|
-
const pkg = await readJson(path.join(packageRoot(), 'package.json'), {}).catch(() => ({}));
|
|
1055
|
-
if (parseVersionText(pkg.version))
|
|
1056
|
-
candidates.push({ version: parseVersionText(pkg.version), source: 'package.json' });
|
|
1057
|
-
const sks = await which('sks').catch(() => null);
|
|
1058
|
-
if (!sks)
|
|
1059
|
-
return candidates[0] || { version: null, source: null };
|
|
1060
|
-
const result = await runProcess(sks, ['--version'], {
|
|
1061
|
-
timeoutMs: 2000,
|
|
1062
|
-
maxOutputBytes: 4096,
|
|
1063
|
-
env: { SKS_DISABLE_UPDATE_CHECK: '1' }
|
|
1064
|
-
}).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
1065
|
-
if (result.code === 0 && parseVersionText(result.stdout))
|
|
1066
|
-
candidates.push({ version: parseVersionText(result.stdout), source: sks });
|
|
1067
|
-
if (candidates.length)
|
|
1068
|
-
return candidates.reduce((best, candidate) => compareVersions(candidate.version, best.version) > 0 ? candidate : best);
|
|
1069
|
-
return { version: null, source: sks, error: `${result.stderr || result.stdout || 'sks --version failed'}`.trim() };
|
|
1070
|
-
}
|
|
1071
|
-
function parseVersionText(text) {
|
|
1072
|
-
const match = String(text || '').match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/);
|
|
1073
|
-
return match ? match[0] : null;
|
|
1074
|
-
}
|
|
1075
|
-
function highestVersion(versions = []) {
|
|
1076
|
-
return versions.filter(Boolean).reduce((best, candidate) => compareVersions(candidate, best) > 0 ? candidate : best, '0.0.0');
|
|
1077
|
-
}
|
|
1078
|
-
function compareVersions(a, b) {
|
|
1079
|
-
const pa = String(a || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
1080
|
-
const pb = String(b || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
1081
|
-
for (let i = 0; i < Math.max(pa.length, pb.length, 3); i++) {
|
|
1082
|
-
if ((pa[i] || 0) > (pb[i] || 0))
|
|
1083
|
-
return 1;
|
|
1084
|
-
if ((pa[i] || 0) < (pb[i] || 0))
|
|
1085
|
-
return -1;
|
|
1086
|
-
}
|
|
1087
|
-
return 0;
|
|
1088
|
-
}
|
|
1089
860
|
function hasHonestMode(text) {
|
|
1090
861
|
const s = String(text || '');
|
|
1091
862
|
return /(SKS Honest Mode|솔직모드|Honest Mode)/i.test(s)
|
|
@@ -1417,8 +1188,6 @@ function codexHookEventName(name) {
|
|
|
1417
1188
|
function visibleHookMessage(name, text = '') {
|
|
1418
1189
|
const body = String(text || '');
|
|
1419
1190
|
if (name === 'user-prompt-submit') {
|
|
1420
|
-
if (body.includes('SKS update check:'))
|
|
1421
|
-
return 'SKS: update check control-plane prompt injected; no pipeline route started.';
|
|
1422
1191
|
if (body.includes('DFix ultralight pipeline active'))
|
|
1423
1192
|
return 'SKS: DFix ultralight task list injected.';
|
|
1424
1193
|
if (body.includes('SKS answer-only pipeline active'))
|