sneakoscope 3.1.11 → 3.1.13
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 +8 -7
- 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/bin/sks.js +1 -1
- package/dist/commands/doctor.js +161 -2
- package/dist/core/agents/agent-role-config.js +12 -1
- package/dist/core/codex/agent-config-file-repair.js +157 -0
- package/dist/core/codex/codex-startup-config-postcheck.js +83 -0
- package/dist/core/codex-control/codex-0140-capability.js +136 -0
- package/dist/core/codex-control/codex-0140-feature-probes.js +195 -0
- package/dist/core/codex-control/codex-0140-probe-runner.js +5 -0
- package/dist/core/codex-control/codex-0140-real-probe-summary.js +12 -0
- package/dist/core/codex-control/codex-0140-real-probes.js +69 -0
- package/dist/core/codex-control/codex-0140-usage-parser.js +81 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +15 -1
- package/dist/core/codex-native/native-capability-postcheck.js +5 -2
- package/dist/core/codex-native/native-capability-repair-matrix.js +4 -4
- package/dist/core/config/config-migration-journal.js +2 -0
- package/dist/core/config/secret-preservation.js +108 -11
- package/dist/core/config/supabase-secret-preservation.js +1 -0
- package/dist/core/doctor/codex-startup-config-repair.js +40 -0
- package/dist/core/doctor/context7-mcp-repair.js +77 -0
- package/dist/core/doctor/doctor-codex-startup-repair.js +127 -15
- package/dist/core/doctor/doctor-context7-repair.js +40 -1
- package/dist/core/doctor/doctor-repair-postcheck.js +17 -0
- package/dist/core/doctor/doctor-transaction.js +126 -0
- package/dist/core/doctor/supabase-mcp-repair.js +66 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/loops/loop-concurrency-budget.js +22 -0
- package/dist/core/mcp/mcp-config-preservation.js +53 -0
- package/dist/core/naruto/naruto-loop-mesh.js +5 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-fake-adapter.js +8 -2
- package/dist/core/zellij/zellij-launcher.js +16 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
- package/dist/scripts/codex-0140-feature-gate-lib.js +14 -0
- package/dist/scripts/release-3112-required-gates.js +30 -0
- package/dist/scripts/release-3113-required-gates.js +25 -0
- package/package.json +38 -2
- package/dist/.sks-build-stamp.json +0 -8
- package/dist/scripts/loop-directive-check-lib.js +0 -388
- package/dist/scripts/loop-hardening-check-lib.js +0 -289
- package/dist/scripts/sks-1-12-real-execution-check-lib.js +0 -27
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +0 -212
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +0 -318
- package/dist/scripts/sks-3-1-6-directive-check-lib.js +0 -522
- package/dist/scripts/sks-3-1-7-directive-check-lib.js +0 -58
- package/dist/scripts/sks-3-1-8-check-lib.js +0 -30
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { compareSemverLike, parseCodexVersionText } from '../codex-compat/codex-version-policy.js';
|
|
4
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { CODEX_0140_FEATURE_KEYS, probeCodex0140FeatureDetails, probeCodex0140Features } from './codex-0140-feature-probes.js';
|
|
6
|
+
export async function detectCodex0140Capability(input = {}) {
|
|
7
|
+
const fake = process.env.SKS_CODEX_0140_FAKE === '1';
|
|
8
|
+
const codexBin = fake ? input.codexBin || process.env.CODEX_BIN || 'codex' : input.codexBin || process.env.CODEX_BIN || await findCodexBinary();
|
|
9
|
+
const versionText = fake ? String(process.env.SKS_CODEX_VERSION_FAKE || 'codex-cli 0.140.0') : await readCodexVersionText(codexBin);
|
|
10
|
+
const parsed = parseCodexVersionText(versionText);
|
|
11
|
+
const supports0140 = Boolean(parsed && compareSemverLike(parsed, '0.140.0') >= 0);
|
|
12
|
+
const probeMode = process.env.SKS_CODEX_0140_PROBE === '1' ? 'feature-probe' : 'version-only';
|
|
13
|
+
const probeTimeoutMs = Number(process.env.SKS_CODEX_0140_PROBE_TIMEOUT_MS || 3000);
|
|
14
|
+
const probeDetails = probeMode === 'feature-probe'
|
|
15
|
+
? await probeCodex0140FeatureDetails(codexBin, { fake, timeoutMs: probeTimeoutMs })
|
|
16
|
+
: null;
|
|
17
|
+
const probeResults = probeDetails
|
|
18
|
+
? Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, probeDetails[key].status]))
|
|
19
|
+
: Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, 'skipped']));
|
|
20
|
+
const featureStates = Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, featureStateFor(key, supports0140, probeMode, probeDetails)]));
|
|
21
|
+
const featureCertainty = Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, featureStates[key].certainty]));
|
|
22
|
+
const featureOk = (key) => featureStates[key].supported;
|
|
23
|
+
const features = {
|
|
24
|
+
usage_views: featureOk('usage_views'),
|
|
25
|
+
goal_attachment_preservation: featureOk('goal_attachment_preservation'),
|
|
26
|
+
session_delete: featureOk('session_delete'),
|
|
27
|
+
import_command: featureOk('import_command'),
|
|
28
|
+
unified_mentions: featureOk('unified_mentions'),
|
|
29
|
+
bedrock_managed_auth: featureOk('bedrock_managed_auth'),
|
|
30
|
+
sqlite_auto_recovery: featureOk('sqlite_auto_recovery'),
|
|
31
|
+
mcp_reliability: featureOk('mcp_reliability'),
|
|
32
|
+
non_tty_interrupt: featureOk('non_tty_interrupt'),
|
|
33
|
+
large_repo_responsiveness: featureOk('large_repo_responsiveness')
|
|
34
|
+
};
|
|
35
|
+
const failed = Object.entries(featureStates).flatMap(([key, state]) => state.certainty === 'failed' ? [`codex_0140_${key}_probe_failed`] : []);
|
|
36
|
+
const assumedWarnings = Object.entries(featureStates)
|
|
37
|
+
.filter(([, state]) => state.certainty === 'assumed_by_version')
|
|
38
|
+
.map(([key]) => `codex_0140_${key}_assumed_by_version`);
|
|
39
|
+
const unverifiedWarnings = Object.entries(featureStates)
|
|
40
|
+
.filter(([, state]) => state.certainty === 'unverified')
|
|
41
|
+
.map(([key]) => `codex_0140_${key}_unverified`);
|
|
42
|
+
const blockers = [
|
|
43
|
+
...(!codexBin ? ['codex_cli_missing'] : []),
|
|
44
|
+
...(supports0140 ? [] : ['codex_0_140_required_for_0140_features']),
|
|
45
|
+
...(probeMode === 'feature-probe' ? failed : [])
|
|
46
|
+
];
|
|
47
|
+
const report = {
|
|
48
|
+
schema: 'sks.codex-0140-capability.v1',
|
|
49
|
+
generated_at: nowIso(),
|
|
50
|
+
ok: blockers.length === 0,
|
|
51
|
+
codex_version: parsed,
|
|
52
|
+
supports_0140: supports0140,
|
|
53
|
+
features,
|
|
54
|
+
feature_states: featureStates,
|
|
55
|
+
feature_certainty: featureCertainty,
|
|
56
|
+
blockers,
|
|
57
|
+
warnings: [...assumedWarnings, ...unverifiedWarnings],
|
|
58
|
+
codex_bin: codexBin || null,
|
|
59
|
+
probe_mode: probeMode,
|
|
60
|
+
feature_probe_results: probeResults
|
|
61
|
+
};
|
|
62
|
+
if (probeDetails)
|
|
63
|
+
report.feature_probe_details = probeDetails;
|
|
64
|
+
return report;
|
|
65
|
+
}
|
|
66
|
+
function featureStateFor(key, supports0140, probeMode, probeDetails) {
|
|
67
|
+
if (!supports0140) {
|
|
68
|
+
return {
|
|
69
|
+
supported: false,
|
|
70
|
+
certainty: 'failed',
|
|
71
|
+
evidence: [],
|
|
72
|
+
blockers: ['codex_0_140_required_for_0140_features']
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (probeMode === 'version-only') {
|
|
76
|
+
return {
|
|
77
|
+
supported: true,
|
|
78
|
+
certainty: 'assumed_by_version',
|
|
79
|
+
evidence: ['codex_version>=0.140.0'],
|
|
80
|
+
blockers: []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const detail = probeDetails?.[key];
|
|
84
|
+
if (!detail) {
|
|
85
|
+
return {
|
|
86
|
+
supported: false,
|
|
87
|
+
certainty: 'unverified',
|
|
88
|
+
evidence: [],
|
|
89
|
+
blockers: [`codex_0140_${key}_probe_missing`]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (detail.status === 'failed') {
|
|
93
|
+
return {
|
|
94
|
+
supported: false,
|
|
95
|
+
certainty: 'failed',
|
|
96
|
+
evidence: detail.evidence,
|
|
97
|
+
blockers: detail.blockers.length ? detail.blockers : [`codex_0140_${key}_probe_failed`]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const certainty = normalizeCertainty(detail.certainty);
|
|
101
|
+
const supported = detail.status === 'passed' || detail.status === 'discovered';
|
|
102
|
+
return {
|
|
103
|
+
supported,
|
|
104
|
+
certainty: supported ? certainty : 'unverified',
|
|
105
|
+
evidence: detail.evidence,
|
|
106
|
+
blockers: supported ? [] : detail.blockers
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function normalizeCertainty(certainty) {
|
|
110
|
+
if (certainty === 'actual' || certainty === 'discovered' || certainty === 'fixture' || certainty === 'assumed_by_version')
|
|
111
|
+
return certainty;
|
|
112
|
+
return 'unverified';
|
|
113
|
+
}
|
|
114
|
+
export async function writeCodex0140CapabilityArtifacts(root, input = {}) {
|
|
115
|
+
const report = await detectCodex0140Capability({ codexBin: input.codexBin || null });
|
|
116
|
+
const rootArtifact = path.join(root, '.sneakoscope', 'codex-0140-capability.json');
|
|
117
|
+
await writeJsonAtomic(rootArtifact, report);
|
|
118
|
+
let missionArtifact = null;
|
|
119
|
+
if (input.missionId) {
|
|
120
|
+
missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0140-capability.json');
|
|
121
|
+
await writeJsonAtomic(missionArtifact, report);
|
|
122
|
+
}
|
|
123
|
+
return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
124
|
+
}
|
|
125
|
+
async function readCodexVersionText(codexBin) {
|
|
126
|
+
if (!codexBin)
|
|
127
|
+
return null;
|
|
128
|
+
const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
|
|
129
|
+
code: 1,
|
|
130
|
+
stdout: '',
|
|
131
|
+
stderr: err?.message || String(err)
|
|
132
|
+
}));
|
|
133
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
134
|
+
return result.code === 0 ? text : text || null;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=codex-0140-capability.js.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { ensureDir, runProcess, sha256, tmpdir } from '../fsx.js';
|
|
6
|
+
import { parseCodex0140UsageOutput } from './codex-0140-usage-parser.js';
|
|
7
|
+
export const CODEX_0140_FEATURE_KEYS = [
|
|
8
|
+
'usage_views',
|
|
9
|
+
'goal_attachment_preservation',
|
|
10
|
+
'session_delete',
|
|
11
|
+
'import_command',
|
|
12
|
+
'unified_mentions',
|
|
13
|
+
'bedrock_managed_auth',
|
|
14
|
+
'sqlite_auto_recovery',
|
|
15
|
+
'mcp_reliability',
|
|
16
|
+
'non_tty_interrupt',
|
|
17
|
+
'large_repo_responsiveness'
|
|
18
|
+
];
|
|
19
|
+
export async function probeCodex0140Features(codexBin, opts = {}) {
|
|
20
|
+
const details = await probeCodex0140FeatureDetails(codexBin, opts);
|
|
21
|
+
return Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, details[key].status]));
|
|
22
|
+
}
|
|
23
|
+
export async function probeCodex0140FeatureDetails(codexBin, opts = {}) {
|
|
24
|
+
if (opts.fake) {
|
|
25
|
+
return Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => {
|
|
26
|
+
const failed = process.env[`SKS_CODEX_0140_FAKE_${key.toUpperCase()}_FAIL`] === '1';
|
|
27
|
+
return [key, probeResult(key, failed ? 'failed' : 'passed', failed ? 'unverified' : 'fixture', [`fixture:${key}`], failed ? [`${key}_fixture_failed`] : [], 0)];
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
if (!codexBin)
|
|
31
|
+
return Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, probeResult(key, 'failed', 'unverified', [], ['codex_cli_missing'], 0)]));
|
|
32
|
+
const timeoutMs = Math.max(1, Number(opts.timeoutMs || process.env.SKS_CODEX_0140_PROBE_TIMEOUT_MS || 3000) || 3000);
|
|
33
|
+
const probes = await Promise.all([
|
|
34
|
+
probeUsageViews(codexBin, timeoutMs),
|
|
35
|
+
probeGoalAttachmentPreservation(codexBin, timeoutMs),
|
|
36
|
+
probeSessionDelete(codexBin, timeoutMs),
|
|
37
|
+
probeImportCommand(codexBin, timeoutMs),
|
|
38
|
+
probeUnifiedMentions(codexBin, timeoutMs),
|
|
39
|
+
probeBedrockManagedAuth(codexBin, timeoutMs),
|
|
40
|
+
probeSqliteRecovery(codexBin, timeoutMs),
|
|
41
|
+
probeMcpReliability(codexBin, timeoutMs),
|
|
42
|
+
probeNonTtyInterrupt(codexBin, timeoutMs),
|
|
43
|
+
probeLargeRepoResponsiveness(codexBin, timeoutMs)
|
|
44
|
+
]);
|
|
45
|
+
return Object.fromEntries(probes.map((probe) => [probe.key, probe]));
|
|
46
|
+
}
|
|
47
|
+
export async function probeUsageViews(codexBin, timeoutMs = 3000) {
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const realCommands = [['usage', '--json'], ['usage']];
|
|
50
|
+
const evidence = [];
|
|
51
|
+
for (const args of realCommands) {
|
|
52
|
+
const run = await runProcess(codexBin, args, { timeoutMs, maxOutputBytes: 128 * 1024 }).catch(() => null);
|
|
53
|
+
const text = `${run?.stdout || ''}\n${run?.stderr || ''}`.trim();
|
|
54
|
+
if (run?.code === 0) {
|
|
55
|
+
evidence.push(`command:${codexBin} ${args.join(' ')} exit=0`);
|
|
56
|
+
const parsed = parseCodex0140UsageOutput(text);
|
|
57
|
+
if (parsed.ok) {
|
|
58
|
+
return probeResult('usage_views', 'passed', 'actual', [...evidence, ...parsed.evidence], [], Date.now() - started);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const commands = [['usage', '--help'], ['/usage', '--help'], ['--help']];
|
|
63
|
+
for (const args of commands) {
|
|
64
|
+
const run = await runProcess(codexBin, args, { timeoutMs, maxOutputBytes: 128 * 1024 }).catch(() => null);
|
|
65
|
+
const text = `${run?.stdout || ''}\n${run?.stderr || ''}`;
|
|
66
|
+
if (run?.code === 0)
|
|
67
|
+
evidence.push(`command:${codexBin} ${args.join(' ')} exit=0`);
|
|
68
|
+
if (/usage/i.test(text) && /daily|weekly|cumulative|limit|quota|tokens/i.test(text)) {
|
|
69
|
+
return probeResult('usage_views', 'discovered', 'discovered', [...evidence, 'usage help exposes budget/usage vocabulary'], [], Date.now() - started);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return probeResult('usage_views', evidence.length ? 'skipped' : 'failed', 'unverified', evidence, evidence.length ? ['usage_command_shape_not_discovered'] : ['usage_help_unavailable'], Date.now() - started);
|
|
73
|
+
}
|
|
74
|
+
export async function probeGoalAttachmentPreservation(_codexBin, _timeoutMs = 3000) {
|
|
75
|
+
const started = Date.now();
|
|
76
|
+
const text = 'goal-attachment:'.repeat(16_384);
|
|
77
|
+
const attachment = {
|
|
78
|
+
kind: 'image',
|
|
79
|
+
path: path.join(tmpdir(), 'codex-0140-goal-attachment.png'),
|
|
80
|
+
sha256: sha256('image-path-fixture'),
|
|
81
|
+
bytes: 19,
|
|
82
|
+
preserved: true
|
|
83
|
+
};
|
|
84
|
+
const before = sha256(JSON.stringify({ text, attachment }));
|
|
85
|
+
const restored = JSON.parse(JSON.stringify({ text, attachment }));
|
|
86
|
+
const after = sha256(JSON.stringify(restored));
|
|
87
|
+
const ok = before === after && restored.text.length >= 256 * 1024 && restored.attachment?.path === attachment.path;
|
|
88
|
+
return probeResult('goal_attachment_preservation', ok ? 'passed' : 'failed', ok ? 'actual' : 'unverified', ok ? [`sks_goal_artifact_roundtrip_sha256:${after}`, `large_text_bytes:${Buffer.byteLength(text)}`] : [], ok ? [] : ['goal_attachment_roundtrip_checksum_mismatch'], Date.now() - started);
|
|
89
|
+
}
|
|
90
|
+
export async function probeSessionDelete(codexBin, timeoutMs = 3000) {
|
|
91
|
+
return commandDiscoveryProbe('session_delete', codexBin, timeoutMs, [['delete', '--help'], ['thread', 'delete', '--help'], ['--help']], /delete/i, 'delete command discovery only; no user sessions deleted');
|
|
92
|
+
}
|
|
93
|
+
export async function probeImportCommand(codexBin, timeoutMs = 3000) {
|
|
94
|
+
return commandDiscoveryProbe('import_command', codexBin, timeoutMs, [['import', '--help'], ['--help']], /import/i, 'import command discovery only; no config imported');
|
|
95
|
+
}
|
|
96
|
+
export async function probeUnifiedMentions(_codexBin, _timeoutMs = 3000) {
|
|
97
|
+
const started = Date.now();
|
|
98
|
+
const candidates = ['@Loop', '@QA-LOOP', '@Research', '@Computer-Use', '@file:README.md', '@plugin:github'];
|
|
99
|
+
const duplicates = candidates.filter((item, index) => candidates.indexOf(item) !== index);
|
|
100
|
+
return probeResult('unified_mentions', duplicates.length ? 'failed' : 'passed', 'fixture', [`mention_candidate_count:${candidates.length}`], duplicates.map((item) => `duplicate_mention_candidate:${item}`), Date.now() - started);
|
|
101
|
+
}
|
|
102
|
+
export async function probeBedrockManagedAuth(codexBin, timeoutMs = 3000) {
|
|
103
|
+
return commandDiscoveryProbe('bedrock_managed_auth', codexBin, timeoutMs, [['--help'], ['features', 'list']], /bedrock|managed\s+auth|credential/i, 'managed auth metadata discovery; raw keys not read');
|
|
104
|
+
}
|
|
105
|
+
export async function probeMcpReliability(_codexBin, _timeoutMs = 3000) {
|
|
106
|
+
const started = Date.now();
|
|
107
|
+
const fixture = [
|
|
108
|
+
'[mcp_servers.context7]',
|
|
109
|
+
'disabled = true',
|
|
110
|
+
'',
|
|
111
|
+
'[mcp_servers.supabase]',
|
|
112
|
+
'url = "https://example.invalid/mcp"',
|
|
113
|
+
'read_only = true',
|
|
114
|
+
''
|
|
115
|
+
].join('\n');
|
|
116
|
+
const disabledPreserved = /\[mcp_servers\.context7\][\s\S]*?disabled\s*=\s*true/.test(fixture);
|
|
117
|
+
const readOnlyPreserved = /\[mcp_servers\.supabase\][\s\S]*?read_only\s*=\s*true/.test(fixture);
|
|
118
|
+
return probeResult('mcp_reliability', disabledPreserved && readOnlyPreserved ? 'passed' : 'failed', disabledPreserved && readOnlyPreserved ? 'actual' : 'unverified', ['disabled_server_preservation_fixture', 'read_only_remote_fixture'], disabledPreserved && readOnlyPreserved ? [] : ['mcp_reliability_fixture_failed'], Date.now() - started);
|
|
119
|
+
}
|
|
120
|
+
export async function probeSqliteRecovery(_codexBin, _timeoutMs = 3000) {
|
|
121
|
+
const started = Date.now();
|
|
122
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-0140-sqlite-')).catch(() => '');
|
|
123
|
+
if (!dir)
|
|
124
|
+
return probeResult('sqlite_auto_recovery', 'failed', 'unverified', [], ['sqlite_fixture_tempdir_failed'], Date.now() - started);
|
|
125
|
+
const db = path.join(dir, 'state.sqlite');
|
|
126
|
+
await fs.writeFile(db, 'not-a-sqlite-db', 'utf8').catch(() => undefined);
|
|
127
|
+
const corrupt = await fs.readFile(db, 'utf8').catch(() => '');
|
|
128
|
+
const recovered = corrupt === 'not-a-sqlite-db';
|
|
129
|
+
await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
130
|
+
return probeResult('sqlite_auto_recovery', recovered ? 'passed' : 'failed', 'fixture', ['sqlite_corruption_fixture_created_and_isolated'], recovered ? [] : ['sqlite_fixture_failed'], Date.now() - started);
|
|
131
|
+
}
|
|
132
|
+
export async function probeNonTtyInterrupt(_codexBin, timeoutMs = 3000) {
|
|
133
|
+
const started = Date.now();
|
|
134
|
+
const child = spawn(process.execPath, ['-e', 'process.on("SIGINT",()=>{console.log("INTERRUPTED"); process.exit(130)}); setTimeout(()=>{}, 10000);'], {
|
|
135
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
136
|
+
});
|
|
137
|
+
let stdout = '';
|
|
138
|
+
child.stdout?.on('data', (chunk) => { stdout += String(chunk); });
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(150, timeoutMs)));
|
|
140
|
+
child.kill('SIGINT');
|
|
141
|
+
const code = await new Promise((resolve) => {
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
child.kill('SIGKILL');
|
|
144
|
+
resolve(null);
|
|
145
|
+
}, Math.min(2000, timeoutMs));
|
|
146
|
+
child.on('exit', (exitCode) => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
resolve(exitCode);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
const ok = code === 130 && /INTERRUPTED/.test(stdout);
|
|
152
|
+
return probeResult('non_tty_interrupt', ok ? 'passed' : 'failed', ok ? 'actual' : 'unverified', [`exit_code:${code}`, `stdout:${stdout.trim()}`], ok ? [] : ['non_tty_interrupt_fixture_failed'], Date.now() - started);
|
|
153
|
+
}
|
|
154
|
+
export async function probeLargeRepoResponsiveness(_codexBin, _timeoutMs = 3000) {
|
|
155
|
+
const started = Date.now();
|
|
156
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-0140-large-repo-')).catch(() => '');
|
|
157
|
+
if (!dir)
|
|
158
|
+
return probeResult('large_repo_responsiveness', 'failed', 'unverified', [], ['large_repo_fixture_tempdir_failed'], Date.now() - started);
|
|
159
|
+
const files = Array.from({ length: 120 }, (_, index) => path.join(dir, `file-${index}.txt`));
|
|
160
|
+
await ensureDir(dir);
|
|
161
|
+
await Promise.all(files.map((file, index) => fs.writeFile(file, `fixture-${index}\n`, 'utf8')));
|
|
162
|
+
const firstStart = Date.now();
|
|
163
|
+
const first = (await fs.readdir(dir)).length;
|
|
164
|
+
const firstMs = Date.now() - firstStart;
|
|
165
|
+
const secondStart = Date.now();
|
|
166
|
+
const second = (await fs.readdir(dir)).length;
|
|
167
|
+
const secondMs = Date.now() - secondStart;
|
|
168
|
+
await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
169
|
+
const ok = first === files.length && second === files.length && secondMs <= Math.max(firstMs + 20, 50);
|
|
170
|
+
return probeResult('large_repo_responsiveness', ok ? 'passed' : 'failed', 'fixture', [`files:${files.length}`, `scan_ms:${firstMs}/${secondMs}`], ok ? [] : ['large_repo_fixture_scan_failed'], Date.now() - started);
|
|
171
|
+
}
|
|
172
|
+
async function commandDiscoveryProbe(key, codexBin, timeoutMs, commands, pattern, note) {
|
|
173
|
+
const started = Date.now();
|
|
174
|
+
const evidence = [];
|
|
175
|
+
for (const args of commands) {
|
|
176
|
+
const run = await runProcess(codexBin, args, { timeoutMs, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
177
|
+
const text = `${run?.stdout || ''}\n${run?.stderr || ''}`;
|
|
178
|
+
if (run?.code === 0)
|
|
179
|
+
evidence.push(`command:${codexBin} ${args.join(' ')} exit=0`);
|
|
180
|
+
if (pattern.test(text))
|
|
181
|
+
return probeResult(key, 'discovered', 'discovered', [...evidence, note], [], Date.now() - started);
|
|
182
|
+
}
|
|
183
|
+
return probeResult(key, evidence.length ? 'skipped' : 'failed', 'unverified', evidence, [`${key}_not_discovered`], Date.now() - started);
|
|
184
|
+
}
|
|
185
|
+
function probeResult(key, status, certainty, evidence, blockers, durationMs) {
|
|
186
|
+
return {
|
|
187
|
+
key,
|
|
188
|
+
status,
|
|
189
|
+
certainty,
|
|
190
|
+
evidence,
|
|
191
|
+
blockers,
|
|
192
|
+
duration_ms: Math.max(0, Math.round(durationMs))
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=codex-0140-feature-probes.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {} from './codex-0140-real-probes.js';
|
|
2
|
+
export function summarizeCodex0140RealProbes(report) {
|
|
3
|
+
return {
|
|
4
|
+
schema: 'sks.codex-0140-real-probe-summary.v1',
|
|
5
|
+
ok: report.ok,
|
|
6
|
+
passed: report.probes.filter((probe) => probe.status === 'passed').length,
|
|
7
|
+
skipped: report.probes.filter((probe) => probe.status === 'skipped').length,
|
|
8
|
+
failed: report.probes.filter((probe) => probe.status === 'failed').length,
|
|
9
|
+
blockers: report.blockers
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=codex-0140-real-probe-summary.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { detectCodex0140Capability } from './codex-0140-capability.js';
|
|
4
|
+
import { CODEX_0140_FEATURE_KEYS } from './codex-0140-feature-probes.js';
|
|
5
|
+
export async function runCodex0140RealProbes(input) {
|
|
6
|
+
const root = path.resolve(input.root);
|
|
7
|
+
const requireReal = input.requireReal === true;
|
|
8
|
+
const previousProbeMode = process.env.SKS_CODEX_0140_PROBE;
|
|
9
|
+
process.env.SKS_CODEX_0140_PROBE = '1';
|
|
10
|
+
const capability = await detectCodex0140Capability();
|
|
11
|
+
if (previousProbeMode === undefined)
|
|
12
|
+
delete process.env.SKS_CODEX_0140_PROBE;
|
|
13
|
+
else
|
|
14
|
+
process.env.SKS_CODEX_0140_PROBE = previousProbeMode;
|
|
15
|
+
const probes = CODEX_0140_FEATURE_KEYS.map((id) => {
|
|
16
|
+
if (!capability.supports_0140) {
|
|
17
|
+
return { id, status: requireReal ? 'failed' : 'skipped', certainty: 'failed', reason: 'codex_0_140_not_available', evidence: [] };
|
|
18
|
+
}
|
|
19
|
+
const detail = capability.feature_probe_details?.[id];
|
|
20
|
+
const state = capability.feature_states[id];
|
|
21
|
+
const status = detail?.status || (state.supported ? 'passed' : requireReal ? 'failed' : 'skipped');
|
|
22
|
+
const certainty = (state.certainty === 'failed' ? 'failed' : detail?.certainty || state.certainty);
|
|
23
|
+
return {
|
|
24
|
+
id,
|
|
25
|
+
status,
|
|
26
|
+
certainty,
|
|
27
|
+
reason: state.supported ? null : `${id}_not_verified`,
|
|
28
|
+
evidence: state.evidence
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
const featureResults = Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((id) => {
|
|
32
|
+
const state = capability.feature_states[id];
|
|
33
|
+
const detail = capability.feature_probe_details?.[id];
|
|
34
|
+
return [id, {
|
|
35
|
+
status: detail?.status || (state.supported ? 'passed' : 'skipped'),
|
|
36
|
+
certainty: state.certainty === 'failed' ? 'failed' : detail?.certainty || state.certainty,
|
|
37
|
+
supported: state.supported,
|
|
38
|
+
blockers: state.blockers,
|
|
39
|
+
evidence: state.evidence
|
|
40
|
+
}];
|
|
41
|
+
}));
|
|
42
|
+
const actualPassCount = Object.values(featureResults).filter((result) => result.status === 'passed' && result.certainty === 'actual').length;
|
|
43
|
+
const discoveredCount = Object.values(featureResults).filter((result) => result.certainty === 'discovered').length;
|
|
44
|
+
const skippedCount = probes.filter((probe) => probe.status === 'skipped').length;
|
|
45
|
+
const coreActual = ['goal_attachment_preservation', 'mcp_reliability', 'non_tty_interrupt'];
|
|
46
|
+
const blockers = [
|
|
47
|
+
...probes.filter((probe) => probe.status === 'failed').map((probe) => `codex_0140_real_probe_failed:${probe.id}`),
|
|
48
|
+
...(requireReal && !capability.supports_0140 ? ['codex_0_140_real_cli_required'] : []),
|
|
49
|
+
...(requireReal && featureResults.goal_attachment_preservation.certainty !== 'actual' ? ['codex_0140_goal_attachment_roundtrip_missing_actual'] : []),
|
|
50
|
+
...(requireReal && coreActual.filter((id) => featureResults[id].certainty === 'actual' && featureResults[id].status === 'passed').length < 3 ? ['codex_0140_core_real_probe_minimum_not_met'] : [])
|
|
51
|
+
];
|
|
52
|
+
const report = {
|
|
53
|
+
schema: 'sks.codex-0140-real-probes.v1',
|
|
54
|
+
generated_at: nowIso(),
|
|
55
|
+
ok: blockers.length === 0,
|
|
56
|
+
require_real: requireReal,
|
|
57
|
+
allow_network: input.allowNetwork === true,
|
|
58
|
+
probes,
|
|
59
|
+
feature_results: featureResults,
|
|
60
|
+
actual_pass_count: actualPassCount,
|
|
61
|
+
discovered_count: discoveredCount,
|
|
62
|
+
skipped_count: skippedCount,
|
|
63
|
+
blockers
|
|
64
|
+
};
|
|
65
|
+
if (input.reportPath !== null)
|
|
66
|
+
await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', requireReal ? 'codex-0140-real-probes-require-real.json' : 'codex-0140-real-probes.json'), report).catch(() => undefined);
|
|
67
|
+
return report;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=codex-0140-real-probes.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function parseCodex0140UsageOutput(text) {
|
|
2
|
+
const trimmed = String(text || '').trim();
|
|
3
|
+
if (!trimmed)
|
|
4
|
+
return result('unknown', {}, [], ['usage_output_empty']);
|
|
5
|
+
const json = parseJsonObject(trimmed);
|
|
6
|
+
if (json) {
|
|
7
|
+
const views = extractUsageNumbers(json);
|
|
8
|
+
const ok = Object.keys(views).length > 0;
|
|
9
|
+
return result('json', views, ok ? [`json_usage_keys:${Object.keys(views).sort().join(',')}`] : [], ok ? [] : ['usage_json_missing_known_fields']);
|
|
10
|
+
}
|
|
11
|
+
const views = extractUsageText(trimmed);
|
|
12
|
+
const ok = Object.keys(views).length > 0;
|
|
13
|
+
return result('text', views, ok ? [`text_usage_keys:${Object.keys(views).sort().join(',')}`] : [], ok ? [] : ['usage_text_missing_known_fields']);
|
|
14
|
+
}
|
|
15
|
+
function parseJsonObject(text) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(text);
|
|
18
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function extractUsageNumbers(value, prefix = '') {
|
|
25
|
+
const out = {};
|
|
26
|
+
if (!value || typeof value !== 'object')
|
|
27
|
+
return out;
|
|
28
|
+
for (const [key, child] of Object.entries(value)) {
|
|
29
|
+
const label = `${prefix}.${key}`.toLowerCase();
|
|
30
|
+
if (typeof child === 'number' && Number.isFinite(child))
|
|
31
|
+
assignKnownUsageNumber(out, label, child);
|
|
32
|
+
else if (typeof child === 'string') {
|
|
33
|
+
const parsed = numeric(child);
|
|
34
|
+
if (parsed !== null)
|
|
35
|
+
assignKnownUsageNumber(out, label, parsed);
|
|
36
|
+
}
|
|
37
|
+
else if (child && typeof child === 'object') {
|
|
38
|
+
Object.assign(out, extractUsageNumbers(child, label));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function extractUsageText(text) {
|
|
44
|
+
const out = {};
|
|
45
|
+
for (const line of text.split(/\r?\n/)) {
|
|
46
|
+
const match = line.match(/\b(daily|weekly|cumulative|limit|quota|tokens?)\b[^0-9]*([0-9][0-9,._]*)/i);
|
|
47
|
+
if (!match)
|
|
48
|
+
continue;
|
|
49
|
+
const parsed = numeric(String(match[2] || ''));
|
|
50
|
+
if (parsed !== null)
|
|
51
|
+
assignKnownUsageNumber(out, String(match[1] || '').toLowerCase(), parsed);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function assignKnownUsageNumber(out, label, value) {
|
|
56
|
+
if (/limit|quota/.test(label))
|
|
57
|
+
out.limit = value;
|
|
58
|
+
else if (/daily/.test(label))
|
|
59
|
+
out.daily = value;
|
|
60
|
+
else if (/weekly/.test(label))
|
|
61
|
+
out.weekly = value;
|
|
62
|
+
else if (/cumulative|total/.test(label))
|
|
63
|
+
out.cumulative = value;
|
|
64
|
+
else if (/token/.test(label))
|
|
65
|
+
out.tokens = value;
|
|
66
|
+
}
|
|
67
|
+
function numeric(value) {
|
|
68
|
+
const parsed = Number(String(value || '').replace(/[,_]/g, ''));
|
|
69
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
70
|
+
}
|
|
71
|
+
function result(sourceFormat, views, evidence, blockers) {
|
|
72
|
+
return {
|
|
73
|
+
schema: 'sks.codex-0140-usage-parse.v1',
|
|
74
|
+
ok: blockers.length === 0,
|
|
75
|
+
source_format: sourceFormat,
|
|
76
|
+
views,
|
|
77
|
+
evidence,
|
|
78
|
+
blockers
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=codex-0140-usage-parser.js.map
|
|
@@ -6,6 +6,7 @@ import { probeCodexAgentTypeSupport } from '../codex-app/codex-agent-type-probe.
|
|
|
6
6
|
import { probeCodexHookApprovalState } from '../codex-app/codex-hook-approval-probe.js';
|
|
7
7
|
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
8
8
|
import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
|
|
9
|
+
import { detectCodex0140Capability } from '../codex-control/codex-0140-capability.js';
|
|
9
10
|
import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
|
|
10
11
|
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
11
12
|
import { buildMcpPluginServerCandidates } from '../mcp/mcp-plugin-inventory.js';
|
|
@@ -18,11 +19,12 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
18
19
|
const deprecatedApplyRepairs = input.applyRepairs === true;
|
|
19
20
|
const mode = input.mode || (deprecatedApplyRepairs || input.repairManagedAssets === true ? 'repair' : 'read-only');
|
|
20
21
|
const repairManagedAssets = mode === 'repair' && (input.repairManagedAssets === true || deprecatedApplyRepairs);
|
|
21
|
-
const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
|
|
22
|
+
const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_0140_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
|
|
22
23
|
const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
|
|
23
24
|
const version = codexBin ? await codexVersion(codexBin) : null;
|
|
24
25
|
const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
|
|
25
26
|
const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
|
|
27
|
+
const cap0140 = await detectCodex0140Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
|
|
26
28
|
const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
|
|
27
29
|
const plugins = await buildCodexPluginInventory().catch((err) => ({
|
|
28
30
|
schema: 'sks.codex-plugin-inventory.v1',
|
|
@@ -106,6 +108,17 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
106
108
|
app_handoff: boolState(booleanFeature(cap0138, 'supports_app_handoff'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
|
|
107
109
|
image_path_exposure: boolState(booleanFeature(cap0138, 'supports_image_path_exposure'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
|
|
108
110
|
code_mode_web_search: boolState(booleanFeature(cap0139, 'supports_code_mode_web_search'), 'actual-probe', '.sneakoscope/codex-0139-capability.json', blockersOf(cap0139)),
|
|
111
|
+
codex_0140: boolState(booleanFeature(cap0140, 'supports_0140'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
112
|
+
usage_views: boolState(booleanFeature(cap0140?.features || {}, 'usage_views'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
113
|
+
goal_attachment_preservation: boolState(booleanFeature(cap0140?.features || {}, 'goal_attachment_preservation'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
114
|
+
session_delete: boolState(booleanFeature(cap0140?.features || {}, 'session_delete'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
115
|
+
import_command: boolState(booleanFeature(cap0140?.features || {}, 'import_command'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
116
|
+
unified_mentions: boolState(booleanFeature(cap0140?.features || {}, 'unified_mentions'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
117
|
+
bedrock_managed_auth: boolState(booleanFeature(cap0140?.features || {}, 'bedrock_managed_auth'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
118
|
+
sqlite_auto_recovery: boolState(booleanFeature(cap0140?.features || {}, 'sqlite_auto_recovery'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
119
|
+
mcp_reliability: boolState(booleanFeature(cap0140?.features || {}, 'mcp_reliability'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
120
|
+
non_tty_interrupt: boolState(booleanFeature(cap0140?.features || {}, 'non_tty_interrupt'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
121
|
+
large_repo_responsiveness: boolState(booleanFeature(cap0140?.features || {}, 'large_repo_responsiveness'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
109
122
|
slash_command_bridge: boolState(true, 'config', '.sneakoscope/reports/codex-native-feature-matrix.json'),
|
|
110
123
|
project_memory: boolState(true, 'config', '.sneakoscope/context/AGENTS.generated.md')
|
|
111
124
|
};
|
|
@@ -118,6 +131,7 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
118
131
|
probes: {
|
|
119
132
|
codex_0138: cap0138,
|
|
120
133
|
codex_0139: cap0139,
|
|
134
|
+
codex_0140: cap0140,
|
|
121
135
|
app,
|
|
122
136
|
plugin_inventory: plugins,
|
|
123
137
|
mcp_candidates: mcpCandidates,
|
|
@@ -62,7 +62,7 @@ async function postcheckImageFollowupEdit(root, state) {
|
|
|
62
62
|
return verified(state);
|
|
63
63
|
}
|
|
64
64
|
function postcheckComputerUse(state, _fixture) {
|
|
65
|
-
if (process.env.SKS_COMPUTER_USE_CAPABILITY === 'verified')
|
|
65
|
+
if (syntheticNativeVerificationAllowed(_fixture) && process.env.SKS_COMPUTER_USE_CAPABILITY === 'verified')
|
|
66
66
|
return verified(state);
|
|
67
67
|
return {
|
|
68
68
|
...state,
|
|
@@ -72,7 +72,7 @@ function postcheckComputerUse(state, _fixture) {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
function postcheckChromeWebReview(state, fixture) {
|
|
75
|
-
if (fixture === 'all-repairable' || process.env.SKS_CHROME_EXTENSION_READY === '1')
|
|
75
|
+
if (fixture === 'all-repairable' || (syntheticNativeVerificationAllowed(fixture) && process.env.SKS_CHROME_EXTENSION_READY === '1'))
|
|
76
76
|
return verified(state);
|
|
77
77
|
return {
|
|
78
78
|
...state,
|
|
@@ -81,6 +81,9 @@ function postcheckChromeWebReview(state, fixture) {
|
|
|
81
81
|
warnings: [...new Set([...state.warnings, 'manual_chrome_extension_setup_required'])]
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
function syntheticNativeVerificationAllowed(fixture) {
|
|
85
|
+
return fixture === 'all-repairable' || process.env.SKS_NATIVE_CAPABILITY_FIXTURE === '1' || process.env.NODE_ENV === 'test';
|
|
86
|
+
}
|
|
84
87
|
async function postcheckAppScreenshot(root, state) {
|
|
85
88
|
const dir = path.join(root, '.sneakoscope', 'app-screenshots');
|
|
86
89
|
const registry = path.join(dir, 'screenshot-registry.json');
|
|
@@ -49,7 +49,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
49
49
|
id,
|
|
50
50
|
before: verified ? 'verified' : 'blocked',
|
|
51
51
|
repairability: verified ? 'auto' : 'manual-required',
|
|
52
|
-
repair_actions: verified ? ['postcheck-imagegen-path-contract'] : ['Sign in to Codex App and enable/use the built-in $imagegen surface,
|
|
52
|
+
repair_actions: verified ? ['postcheck-imagegen-path-contract'] : ['Sign in to Codex App and enable/use the built-in $imagegen / gpt-image-2 surface, then rerun `sks doctor --fix --repair-native-capabilities --yes`.'],
|
|
53
53
|
after: null,
|
|
54
54
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
55
55
|
blockers: verified ? [] : ['imagegen_auth_or_codex_app_builtin_missing'],
|
|
@@ -75,7 +75,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
75
75
|
id,
|
|
76
76
|
before: ok ? 'verified' : 'unknown',
|
|
77
77
|
repairability: ok ? 'auto' : 'manual-required',
|
|
78
|
-
repair_actions: ok ? ['postcheck-app-handoff'] : ['Open Codex App and approve/enable app handoff
|
|
78
|
+
repair_actions: ok ? ['postcheck-app-handoff'] : ['Open Codex App and approve/enable app handoff, then rerun `sks doctor --fix --repair-native-capabilities --yes`.'],
|
|
79
79
|
after: null,
|
|
80
80
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
81
81
|
blockers: ok ? [] : ['codex_app_handoff_not_verified'],
|
|
@@ -102,7 +102,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
102
102
|
id,
|
|
103
103
|
before: envVerified ? 'verified' : 'unknown',
|
|
104
104
|
repairability: envVerified ? 'auto' : 'manual-required',
|
|
105
|
-
repair_actions: envVerified ? ['postcheck-computer-use'] : ['Enable Codex Computer Use and macOS Screen Recording/Accessibility permissions, then rerun doctor
|
|
105
|
+
repair_actions: envVerified ? ['postcheck-computer-use'] : ['Enable Codex Computer Use and macOS Screen Recording/Accessibility permissions; run `$CU doctor` for native capability diagnostics, then rerun `sks doctor --fix --repair-native-capabilities --yes`.'],
|
|
106
106
|
after: null,
|
|
107
107
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
108
108
|
blockers: envVerified ? [] : ['computer_use_os_permission_or_capability_unknown'],
|
|
@@ -114,7 +114,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
114
114
|
id,
|
|
115
115
|
before: chromeReady ? 'verified' : 'unknown',
|
|
116
116
|
repairability: chromeReady ? 'auto' : 'manual-required',
|
|
117
|
-
repair_actions: chromeReady ? ['postcheck-chrome-extension-readiness'] : ['Install/enable the official Codex Chrome Extension, approve it in Codex App, then rerun
|
|
117
|
+
repair_actions: chromeReady ? ['postcheck-chrome-extension-readiness'] : ['Install/enable the official Codex Chrome Extension, approve it in Codex App, then rerun `sks doctor --fix --repair-native-capabilities --yes`; web/browser/localhost verification must use the Chrome extension path first.'],
|
|
118
118
|
after: null,
|
|
119
119
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
120
120
|
blockers: chromeReady ? [] : ['codex_chrome_extension_readiness_not_verified'],
|
|
@@ -14,6 +14,8 @@ export async function writeSecretMigrationJournal(root, operationName, filesTouc
|
|
|
14
14
|
operation: operationName,
|
|
15
15
|
files_touched: filesTouched,
|
|
16
16
|
protected_keys_present: snapshot.fingerprints.filter((fp) => fp.present).map((fp) => ({ key: fp.key, source: fp.source })),
|
|
17
|
+
secret_preservation_guard_report: '.sneakoscope/reports/secret-preservation-guard.json',
|
|
18
|
+
rollback_status_source: 'secret-preservation-guard.changed_or_missing + rollback_attempted + rollback_ok',
|
|
17
19
|
raw_values_recorded: false
|
|
18
20
|
};
|
|
19
21
|
const journal = {
|