sneakoscope 2.0.17 → 3.0.0
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 +135 -90
- 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/commands/doctor.js +39 -1
- package/dist/commands/mad-sks.js +2 -0
- package/dist/commands/zellij.js +58 -1
- package/dist/core/agents/agent-effort-policy.js +7 -1
- package/dist/core/agents/agent-scheduler.js +32 -24
- package/dist/core/agents/native-cli-session-swarm.js +22 -2
- package/dist/core/codex-app/codex-app-handoff.js +98 -0
- package/dist/core/codex-app/codex-app-launcher.js +103 -0
- package/dist/core/codex-control/codex-0138-capability.js +102 -0
- package/dist/core/codex-control/codex-model-capabilities.js +62 -0
- package/dist/core/codex-control/codex-model-metadata.js +91 -0
- package/dist/core/codex-control/codex-sdk-config-policy.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +1 -1
- package/dist/core/codex-plugins/codex-plugin-cache.js +38 -0
- package/dist/core/codex-plugins/codex-plugin-diff.js +73 -0
- package/dist/core/codex-plugins/codex-plugin-json.js +176 -0
- package/dist/core/commands/mad-sks-command.js +8 -0
- package/dist/core/commands/naruto-command.js +30 -1
- package/dist/core/commands/qa-loop-command.js +147 -5
- package/dist/core/doctor/codex-0138-doctor.js +104 -0
- package/dist/core/doctor/doctor-readiness-matrix.js +11 -0
- package/dist/core/effort-orchestrator.js +9 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +6 -9
- package/dist/core/image/image-artifact-path-contract.js +101 -0
- package/dist/core/image/image-artifact-registry.js +33 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +49 -17
- package/dist/core/mad-db/mad-db-result-lifecycle.js +71 -0
- package/dist/core/mcp/mcp-plugin-inventory.js +29 -0
- package/dist/core/mcp/mcp-server-policy.js +24 -0
- package/dist/core/qa-loop/qa-loop-app-handoff-confirmation.js +51 -0
- package/dist/core/qa-loop/qa-loop-budget-policy.js +37 -0
- package/dist/core/qa-loop.js +70 -3
- package/dist/core/release/release-gate-cache-v2.js +47 -5
- package/dist/core/usage/codex-account-usage.js +139 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-column-anchor.js +16 -7
- package/dist/core/zellij/zellij-slot-pane-renderer.js +23 -2
- package/dist/core/zellij/zellij-slot-telemetry.js +65 -12
- package/dist/core/zellij/zellij-ui-mode.js +8 -1
- package/dist/core/zellij/zellij-update.js +307 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +211 -145
- package/dist/scripts/release-gate-existence-audit.js +5 -1
- package/package.json +46 -3
- package/schemas/codex-app/codex-app-handoff.schema.json +20 -0
- package/schemas/codex-plugins/codex-plugin-inventory.schema.json +32 -0
- package/schemas/image/image-artifact-path-contract.schema.json +32 -0
- package/schemas/usage/codex-account-usage.schema.json +27 -0
- package/dist/core/naruto/naruto-work-stealing.js +0 -11
- package/dist/core/zellij/zellij-right-column-layout-proof.js +0 -42
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
5
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
6
|
+
export async function runCodex0138Doctor(root, input = {}) {
|
|
7
|
+
const capability = await detectCodex0138Capability();
|
|
8
|
+
const fixed = [];
|
|
9
|
+
const checks = {
|
|
10
|
+
bash_fallback: await bashFallbackCheck(),
|
|
11
|
+
linux_proxy_socket_path: linuxProxySocketCheck(root),
|
|
12
|
+
oauth_mcp_prerefresh: oauthMcpPrerefreshCheck(capability),
|
|
13
|
+
agents_logical_path: await agentsLogicalPathCheck(root),
|
|
14
|
+
plugin_discovery_cache: await pluginDiscoveryCacheCheck(root, input.fix === true, fixed)
|
|
15
|
+
};
|
|
16
|
+
const warnings = [
|
|
17
|
+
...(capability.ok ? [] : ['codex_0_138_not_detected']),
|
|
18
|
+
...Object.values(checks).flatMap((check) => Array.isArray(check.warnings) ? check.warnings : [])
|
|
19
|
+
];
|
|
20
|
+
const blockers = Object.values(checks).flatMap((check) => Array.isArray(check.blockers) ? check.blockers : []);
|
|
21
|
+
const report = {
|
|
22
|
+
schema: 'sks.codex-0138-doctor.v1',
|
|
23
|
+
generated_at: nowIso(),
|
|
24
|
+
ok: blockers.length === 0,
|
|
25
|
+
codex_0138_capability: capability,
|
|
26
|
+
checks,
|
|
27
|
+
fixed,
|
|
28
|
+
warnings,
|
|
29
|
+
blockers
|
|
30
|
+
};
|
|
31
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'codex-0138-doctor.json'), report);
|
|
32
|
+
return report;
|
|
33
|
+
}
|
|
34
|
+
async function bashFallbackCheck() {
|
|
35
|
+
const candidates = ['/bin/bash', '/usr/bin/bash'];
|
|
36
|
+
const existing = [];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
await fs.access(candidate);
|
|
40
|
+
existing.push(candidate);
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
ok: existing.length > 0,
|
|
46
|
+
candidates,
|
|
47
|
+
existing,
|
|
48
|
+
blockers: existing.length ? [] : ['bash_fallback_missing'],
|
|
49
|
+
warnings: []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function linuxProxySocketCheck(root) {
|
|
53
|
+
if (process.platform !== 'linux')
|
|
54
|
+
return { ok: true, status: 'not_linux', warnings: [], blockers: [] };
|
|
55
|
+
const candidate = path.join(os.tmpdir(), 'sks-proxy', path.basename(root), 'proxy.sock');
|
|
56
|
+
return {
|
|
57
|
+
ok: candidate.length < 100,
|
|
58
|
+
candidate,
|
|
59
|
+
length: candidate.length,
|
|
60
|
+
warnings: candidate.length < 100 ? [] : ['linux_proxy_socket_path_long'],
|
|
61
|
+
blockers: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function oauthMcpPrerefreshCheck(capability) {
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
supported: capability.supports_oauth_mcp_prerefresh === true,
|
|
68
|
+
warnings: capability.supports_oauth_mcp_prerefresh ? [] : ['oauth_mcp_prerefresh_requires_codex_0_138'],
|
|
69
|
+
blockers: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function agentsLogicalPathCheck(root) {
|
|
73
|
+
const agents = path.join(root, 'AGENTS.md');
|
|
74
|
+
const realRoot = await fs.realpath(root).catch(() => root);
|
|
75
|
+
const exists = await fs.stat(agents).then((st) => st.isFile()).catch(() => false);
|
|
76
|
+
return {
|
|
77
|
+
ok: exists,
|
|
78
|
+
logical_path: agents,
|
|
79
|
+
real_root: realRoot,
|
|
80
|
+
warnings: exists ? [] : ['agents_md_missing_or_unreadable'],
|
|
81
|
+
blockers: []
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function pluginDiscoveryCacheCheck(root, fix, fixed) {
|
|
85
|
+
const cacheDir = path.join(root, '.sneakoscope', 'cache', 'codex-plugin-discovery');
|
|
86
|
+
const exists = await fs.stat(cacheDir).then((st) => st.isDirectory()).catch(() => false);
|
|
87
|
+
if (!exists && fix) {
|
|
88
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
89
|
+
await writeJsonAtomic(path.join(cacheDir, 'README.json'), {
|
|
90
|
+
schema: 'sks.codex-plugin-discovery-cache.v1',
|
|
91
|
+
repaired_at: nowIso(),
|
|
92
|
+
purpose: 'Codex 0.138 plugin discovery cache placeholder; safe to refresh from codex plugin list --json.'
|
|
93
|
+
});
|
|
94
|
+
fixed.push('plugin_discovery_cache');
|
|
95
|
+
}
|
|
96
|
+
const after = exists || fix;
|
|
97
|
+
return {
|
|
98
|
+
ok: after,
|
|
99
|
+
path: cacheDir,
|
|
100
|
+
warnings: after ? [] : ['plugin_discovery_cache_missing_repair_available'],
|
|
101
|
+
blockers: []
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=codex-0138-doctor.js.map
|
|
@@ -52,6 +52,14 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
52
52
|
blockers.add('codex_app_fast_ui_repair_requires_confirmation');
|
|
53
53
|
if (input.codex_app_ui?.fast_selector === 'repaired')
|
|
54
54
|
warnings.add('codex_app_fast_selector_repaired_restart_app_if_needed');
|
|
55
|
+
const codex0138Doctor = input.codex_0138_doctor || null;
|
|
56
|
+
if (codex0138Doctor?.ok === false)
|
|
57
|
+
for (const blocker of normalizeList(codex0138Doctor.blockers))
|
|
58
|
+
warnings.add(blocker);
|
|
59
|
+
for (const warning of normalizeList(codex0138Doctor?.warnings))
|
|
60
|
+
warnings.add(warning);
|
|
61
|
+
for (const warning of normalizeList(input.codex_plugin_app_template_policy?.doctor_warnings))
|
|
62
|
+
warnings.add(warning);
|
|
55
63
|
if (input.codex_lb?.ok === false)
|
|
56
64
|
warnings.add(`codex_lb_${input.codex_lb?.circuit?.state || 'blocked'}`);
|
|
57
65
|
const localModel = input.local_model || {};
|
|
@@ -107,6 +115,9 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
107
115
|
replacement: 'zellij'
|
|
108
116
|
},
|
|
109
117
|
codex_doctor: codexDoctor || null,
|
|
118
|
+
codex_0138_doctor: codex0138Doctor,
|
|
119
|
+
codex_plugin_inventory: input.codex_plugin_inventory || null,
|
|
120
|
+
codex_plugin_app_template_policy: input.codex_plugin_app_template_policy || null,
|
|
110
121
|
fast_mode_ready: input.fast_mode_ready !== false,
|
|
111
122
|
codex_app_ui: input.codex_app_ui || null,
|
|
112
123
|
hooks_ready: input.hooks_ready !== false,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { nowIso, writeJsonAtomic } from './fsx.js';
|
|
3
3
|
import { ARTIFACT_FILES } from './artifact-schemas.js';
|
|
4
|
+
import { codexModelEffortCapability, modelEffortAtLeast } from './codex-control/codex-model-capabilities.js';
|
|
4
5
|
export const EFFORT_POLICY_VERSION = 1;
|
|
5
6
|
export function selectEffort(task = {}) {
|
|
6
7
|
const route = String(task.route || task.command || '').toLowerCase();
|
|
@@ -39,11 +40,19 @@ export function selectEffort(task = {}) {
|
|
|
39
40
|
selected = 'medium';
|
|
40
41
|
reasonCodes.push('default_medium');
|
|
41
42
|
}
|
|
43
|
+
const modelCapability = codexModelEffortCapability({
|
|
44
|
+
model: task.model || task.model_id,
|
|
45
|
+
advertisedEfforts: task.advertised_efforts || task.model_advertised_efforts,
|
|
46
|
+
defaultEffort: task.model_reasoning_effort || task.default_effort
|
|
47
|
+
});
|
|
48
|
+
const modelReasoningEffort = modelEffortAtLeast(selected, modelCapability);
|
|
42
49
|
return {
|
|
43
50
|
schema_version: EFFORT_POLICY_VERSION,
|
|
44
51
|
mission_id: task.mission_id || 'unassigned',
|
|
45
52
|
task_id: task.task_id || 'TASK-001',
|
|
46
53
|
selected_effort: selected,
|
|
54
|
+
model_reasoning_effort: modelReasoningEffort,
|
|
55
|
+
model_effort_capability: modelCapability,
|
|
47
56
|
reason_codes: reasonCodes,
|
|
48
57
|
risk_scores: risks,
|
|
49
58
|
demotion_allowed_after: demotionPolicy(selected),
|
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 = '
|
|
8
|
+
export const PACKAGE_VERSION = '3.0.0';
|
|
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() {
|
|
@@ -3,7 +3,7 @@ import { projectRoot, readJson, readText, writeJsonAtomic, appendJsonl, readStdi
|
|
|
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';
|
|
6
|
-
import {
|
|
6
|
+
import { maybeRecordMadDbToolResultFromToolUse } from './mad-db/mad-db-result-lifecycle.js';
|
|
7
7
|
import { checkHarnessModification, harnessGuardBlockReason, isHarnessSourceProject } from './harness-guard.js';
|
|
8
8
|
import { isMadSksRouteState } from './permission-gates.js';
|
|
9
9
|
import { classifyMadSksShellCommand } from './mad-sks/write-guard.js';
|
|
@@ -163,6 +163,8 @@ function toolFailed(payload = {}) {
|
|
|
163
163
|
if (Number.isFinite(n))
|
|
164
164
|
return n !== 0;
|
|
165
165
|
}
|
|
166
|
+
if (payload.isError === true || payload.tool_response?.isError === true || payload.toolResponse?.isError === true || payload.result?.isError === true)
|
|
167
|
+
return true;
|
|
166
168
|
if (payload.success === false || payload.tool_response?.success === false || payload.toolResponse?.success === false || payload.result?.success === false)
|
|
167
169
|
return true;
|
|
168
170
|
if (payload.executed === false)
|
|
@@ -450,16 +452,11 @@ async function hookPostTool(root, state, payload, noQuestion) {
|
|
|
450
452
|
async function recordMadDbPostToolLifecycle(root, state = {}, payload = {}) {
|
|
451
453
|
if (!state?.mission_id)
|
|
452
454
|
return null;
|
|
453
|
-
|
|
454
|
-
if (!hook)
|
|
455
|
-
return null;
|
|
456
|
-
return recordMadDbToolResult({
|
|
455
|
+
return maybeRecordMadDbToolResultFromToolUse({
|
|
457
456
|
root,
|
|
458
457
|
missionId: String(state.mission_id),
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
rowCount: extractRowCount(payload),
|
|
462
|
-
error: toolFailed(payload) ? extractToolError(payload) : null
|
|
458
|
+
toolCallPayload: payload,
|
|
459
|
+
toolResult: payload
|
|
463
460
|
});
|
|
464
461
|
}
|
|
465
462
|
function extractRowCount(payload = {}) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { imageDimensions } from '../wiki-image/image-hash.js';
|
|
5
|
+
export async function buildImageArtifactPathContract(root, input) {
|
|
6
|
+
const images = [];
|
|
7
|
+
const blockers = [];
|
|
8
|
+
for (const [index, image] of input.images.entries()) {
|
|
9
|
+
const filePath = path.resolve(root, image.filePath || '');
|
|
10
|
+
const exists = await fileExists(filePath);
|
|
11
|
+
if (!exists)
|
|
12
|
+
blockers.push(`${image.kind}_file_path_missing:${image.id || index + 1}`);
|
|
13
|
+
const dims = exists ? await imageDimensions(filePath).catch(() => null) : null;
|
|
14
|
+
images.push({
|
|
15
|
+
id: image.id || `image-${index + 1}`,
|
|
16
|
+
kind: image.kind,
|
|
17
|
+
file_path: filePath,
|
|
18
|
+
relative_path: path.relative(root, filePath),
|
|
19
|
+
route: image.route || null,
|
|
20
|
+
stage: image.stage || null,
|
|
21
|
+
exists,
|
|
22
|
+
mime_type: mimeForPath(filePath),
|
|
23
|
+
width: dims?.width ?? null,
|
|
24
|
+
height: dims?.height ?? null,
|
|
25
|
+
model_visible_path: filePath,
|
|
26
|
+
followup_edit_hint: exists
|
|
27
|
+
? `Use this saved local path for follow-up image edits: ${filePath}`
|
|
28
|
+
: 'Image file path missing; do not run visual QA until a real saved file path exists.'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (images.some((image) => image.kind === 'generated_image' && !image.exists))
|
|
32
|
+
blockers.push('image_generated_file_path_missing');
|
|
33
|
+
return {
|
|
34
|
+
schema: 'sks.image-artifact-path-contract.v1',
|
|
35
|
+
mission_id: input.missionId,
|
|
36
|
+
generated_at: nowIso(),
|
|
37
|
+
images,
|
|
38
|
+
blockers: [...new Set(blockers)]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function writeImageArtifactPathContract(root, input) {
|
|
42
|
+
const contract = await buildImageArtifactPathContract(root, input);
|
|
43
|
+
const artifactPath = input.artifactPath || path.join(root, '.sneakoscope', 'missions', input.missionId, 'image-artifact-path-contract.json');
|
|
44
|
+
await writeJsonAtomic(artifactPath, contract);
|
|
45
|
+
return { contract, artifact_path: artifactPath };
|
|
46
|
+
}
|
|
47
|
+
export async function discoverImageArtifactsInDir(dir) {
|
|
48
|
+
const out = [];
|
|
49
|
+
await walk(dir, async (file) => {
|
|
50
|
+
if (!/\.(png|jpe?g|webp|gif)$/i.test(file))
|
|
51
|
+
return;
|
|
52
|
+
out.push({
|
|
53
|
+
id: path.basename(file).replace(/[^0-9A-Za-z._-]/g, '_'),
|
|
54
|
+
kind: /generated|gpt-image|callout/i.test(file) ? 'generated_image' : 'visual_qa_snapshot',
|
|
55
|
+
filePath: file
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function mimeForPath(file) {
|
|
61
|
+
const ext = path.extname(file).toLowerCase();
|
|
62
|
+
if (ext === '.png')
|
|
63
|
+
return 'image/png';
|
|
64
|
+
if (ext === '.jpg' || ext === '.jpeg')
|
|
65
|
+
return 'image/jpeg';
|
|
66
|
+
if (ext === '.webp')
|
|
67
|
+
return 'image/webp';
|
|
68
|
+
if (ext === '.gif')
|
|
69
|
+
return 'image/gif';
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
async function fileExists(file) {
|
|
73
|
+
try {
|
|
74
|
+
const st = await fs.stat(file);
|
|
75
|
+
return st.isFile();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function walk(dir, visit) {
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const full = path.join(dir, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
if (['node_modules', '.git', 'dist'].includes(entry.name))
|
|
93
|
+
continue;
|
|
94
|
+
await walk(full, visit);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
await visit(full);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=image-artifact-path-contract.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { buildImageArtifactPathContract } from './image-artifact-path-contract.js';
|
|
4
|
+
export async function registerImageArtifact(root, input) {
|
|
5
|
+
const artifactPath = imageArtifactRegistryPath(root, input.missionId);
|
|
6
|
+
const existing = await readJson(artifactPath, null);
|
|
7
|
+
const id = input.id || path.basename(input.filePath).replace(/[^0-9A-Za-z._-]/g, '_');
|
|
8
|
+
const rows = [
|
|
9
|
+
...(existing?.images || [])
|
|
10
|
+
.filter((image) => image.id !== id)
|
|
11
|
+
.map((image) => ({
|
|
12
|
+
id: image.id,
|
|
13
|
+
kind: image.kind,
|
|
14
|
+
filePath: image.file_path,
|
|
15
|
+
route: image.route || null,
|
|
16
|
+
stage: image.stage || null
|
|
17
|
+
})),
|
|
18
|
+
{
|
|
19
|
+
id,
|
|
20
|
+
kind: input.kind,
|
|
21
|
+
filePath: input.filePath,
|
|
22
|
+
route: input.route,
|
|
23
|
+
stage: input.stage
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
const contract = await buildImageArtifactPathContract(root, { missionId: input.missionId, images: rows });
|
|
27
|
+
await writeJsonAtomic(artifactPath, contract);
|
|
28
|
+
return contract;
|
|
29
|
+
}
|
|
30
|
+
export function imageArtifactRegistryPath(root, missionId) {
|
|
31
|
+
return path.join(root, '.sneakoscope', 'missions', missionId, 'image-artifact-path-contract.json');
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=image-artifact-registry.js.map
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import { parseShellEnvValue } from '../codex-lb/codex-lb-env.js';
|
|
4
|
-
import { ensureDir, exists, nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { ensureDir, exists, nowIso, projectRoot, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
|
|
6
6
|
import { detectImagegenCapability } from '../imagegen/imagegen-capability.js';
|
|
7
7
|
import { validateGptImage2Request } from '../imagegen/gpt-image-2-request-validator.js';
|
|
8
8
|
import { withResponsesRetry } from '../responses-retry-policy.js';
|
|
9
9
|
import { discoverCodexAppGeneratedImage } from './codex-app-generated-image-discovery.js';
|
|
10
|
+
import { writeImageArtifactPathContract } from '../image/image-artifact-path-contract.js';
|
|
11
|
+
import { registerImageArtifact } from '../image/image-artifact-registry.js';
|
|
10
12
|
const DEFAULT_OPENAI_IMAGE_EDITS_ENDPOINT = 'https://api.openai.com/v1/images/edits';
|
|
11
13
|
export function buildCalloutPrompt(sourceScreenId, context = {}) {
|
|
12
14
|
return [
|
|
@@ -124,6 +126,7 @@ export function createCodexAppImagegenAdapter(opts = {}) {
|
|
|
124
126
|
real_generated: true
|
|
125
127
|
});
|
|
126
128
|
const outputSource = manualOutput ? 'manual_attach' : 'auto_discovered_generated_images';
|
|
129
|
+
const imageContract = await writeGeneratedImagePathContract(input, dest, 'codex_app_imagegen').catch(() => null);
|
|
127
130
|
await writeJsonAtomic(responseArtifact, {
|
|
128
131
|
schema: 'sks.image-ux-gpt-image-2-response.v1',
|
|
129
132
|
created_at: nowIso(),
|
|
@@ -135,6 +138,7 @@ export function createCodexAppImagegenAdapter(opts = {}) {
|
|
|
135
138
|
output_image_sha256: meta.sha256,
|
|
136
139
|
output_id: meta.output_id,
|
|
137
140
|
output_source: outputSource,
|
|
141
|
+
image_artifact_path_contract: imageContract?.artifact_path || null,
|
|
138
142
|
discovered_from: discovery?.selected?.path || null,
|
|
139
143
|
discovery: discovery ? { candidates_considered: discovery.candidates_considered, since_ms: discovery.since_ms, max_age_ms: discovery.max_age_ms } : null,
|
|
140
144
|
local_only: true
|
|
@@ -149,6 +153,7 @@ export function createCodexAppImagegenAdapter(opts = {}) {
|
|
|
149
153
|
output_source: outputSource,
|
|
150
154
|
request_artifact: requestArtifact,
|
|
151
155
|
response_artifact: responseArtifact,
|
|
156
|
+
image_artifact_path_contract: imageContract?.artifact_path || null,
|
|
152
157
|
latency_ms: null
|
|
153
158
|
};
|
|
154
159
|
}
|
|
@@ -241,6 +246,7 @@ export function createFakeImagegenAdapter(opts = {}) {
|
|
|
241
246
|
real_generated: false,
|
|
242
247
|
mock: true
|
|
243
248
|
});
|
|
249
|
+
const imageContract = await writeGeneratedImagePathContract(input, out, 'fake_imagegen_adapter').catch(() => null);
|
|
244
250
|
await writeJsonAtomic(responseArtifact, {
|
|
245
251
|
schema: 'sks.image-ux-gpt-image-2-response.v1',
|
|
246
252
|
created_at: nowIso(),
|
|
@@ -251,6 +257,7 @@ export function createFakeImagegenAdapter(opts = {}) {
|
|
|
251
257
|
output_image_path: out,
|
|
252
258
|
output_image_sha256: meta.sha256,
|
|
253
259
|
output_id: meta.output_id,
|
|
260
|
+
image_artifact_path_contract: imageContract?.artifact_path || null,
|
|
254
261
|
dimensions: { width: meta.width, height: meta.height, format: meta.format },
|
|
255
262
|
latency_ms: Date.now() - started,
|
|
256
263
|
fake_adapter: true,
|
|
@@ -259,7 +266,7 @@ export function createFakeImagegenAdapter(opts = {}) {
|
|
|
259
266
|
mock: true,
|
|
260
267
|
local_only: true
|
|
261
268
|
});
|
|
262
|
-
return { ok: true, status: 'generated', generated_image_path: out, output_id: meta.output_id, blocker: null, provider: 'fake_imagegen_adapter', request_artifact: requestArtifact, response_artifact: responseArtifact, latency_ms: Date.now() - started };
|
|
269
|
+
return { ok: true, status: 'generated', generated_image_path: out, output_id: meta.output_id, blocker: null, provider: 'fake_imagegen_adapter', request_artifact: requestArtifact, response_artifact: responseArtifact, image_artifact_path_contract: imageContract?.artifact_path || null, latency_ms: Date.now() - started };
|
|
263
270
|
}
|
|
264
271
|
};
|
|
265
272
|
}
|
|
@@ -385,6 +392,7 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
385
392
|
output_id: generated.id || payload?.id || null,
|
|
386
393
|
real_generated: true
|
|
387
394
|
});
|
|
395
|
+
const imageContract = await writeGeneratedImagePathContract(input, out, 'openai_responses_image_generation').catch(() => null);
|
|
388
396
|
await writeJsonAtomic(responseArtifact, {
|
|
389
397
|
schema: 'sks.image-ux-gpt-image-2-response.v1',
|
|
390
398
|
created_at: nowIso(),
|
|
@@ -397,12 +405,13 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
397
405
|
output_image_path: out,
|
|
398
406
|
output_image_sha256: meta.sha256,
|
|
399
407
|
output_id: meta.output_id,
|
|
408
|
+
image_artifact_path_contract: imageContract?.artifact_path || null,
|
|
400
409
|
dimensions: { width: meta.width, height: meta.height, format: meta.format },
|
|
401
410
|
latency_ms: Date.now() - started,
|
|
402
411
|
token_cost_metadata: payload?.usage || null,
|
|
403
412
|
local_only: true
|
|
404
413
|
});
|
|
405
|
-
return { ok: true, status: 'generated', generated_image_path: out, output_id: meta.output_id, blocker: null, provider: 'openai_responses_image_generation', request_artifact: requestArtifact, response_artifact: responseArtifact, latency_ms: Date.now() - started };
|
|
414
|
+
return { ok: true, status: 'generated', generated_image_path: out, output_id: meta.output_id, blocker: null, provider: 'openai_responses_image_generation', request_artifact: requestArtifact, response_artifact: responseArtifact, image_artifact_path_contract: imageContract?.artifact_path || null, latency_ms: Date.now() - started };
|
|
406
415
|
}
|
|
407
416
|
const sourceBytes = await fsp.readFile(sourcePath);
|
|
408
417
|
const qualityParam = imagegenQualityParam(opts);
|
|
@@ -440,6 +449,7 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
440
449
|
output_id: image?.id || payload?.id || null,
|
|
441
450
|
real_generated: true
|
|
442
451
|
});
|
|
452
|
+
const imageContract = await writeGeneratedImagePathContract(input, out, 'openai_images_api').catch(() => null);
|
|
443
453
|
await writeJsonAtomic(responseArtifact, {
|
|
444
454
|
schema: 'sks.image-ux-gpt-image-2-response.v1',
|
|
445
455
|
created_at: nowIso(),
|
|
@@ -451,12 +461,13 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
451
461
|
output_image_path: out,
|
|
452
462
|
output_image_sha256: meta.sha256,
|
|
453
463
|
output_id: meta.output_id,
|
|
464
|
+
image_artifact_path_contract: imageContract?.artifact_path || null,
|
|
454
465
|
dimensions: { width: meta.width, height: meta.height, format: meta.format },
|
|
455
466
|
latency_ms: Date.now() - started,
|
|
456
467
|
token_cost_metadata: payload?.usage || null,
|
|
457
468
|
local_only: true
|
|
458
469
|
});
|
|
459
|
-
return { ok: true, status: 'generated', generated_image_path: out, output_id: meta.output_id, blocker: null, provider: 'openai_images_api', request_artifact: requestArtifact, response_artifact: responseArtifact, latency_ms: Date.now() - started };
|
|
470
|
+
return { ok: true, status: 'generated', generated_image_path: out, output_id: meta.output_id, blocker: null, provider: 'openai_images_api', request_artifact: requestArtifact, response_artifact: responseArtifact, image_artifact_path_contract: imageContract?.artifact_path || null, latency_ms: Date.now() - started };
|
|
460
471
|
}
|
|
461
472
|
catch (err) {
|
|
462
473
|
const provider = useResponsesImageTool ? 'openai_responses_image_generation' : 'openai_images_api';
|
|
@@ -468,6 +479,37 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
468
479
|
}
|
|
469
480
|
};
|
|
470
481
|
}
|
|
482
|
+
async function writeGeneratedImagePathContract(input, outputPath, provider) {
|
|
483
|
+
const root = await resolveImageArtifactRoot(input);
|
|
484
|
+
if (input.mission_id) {
|
|
485
|
+
await registerImageArtifact(root, {
|
|
486
|
+
missionId: input.mission_id,
|
|
487
|
+
id: `${provider}-${input.source_screen_id || 'screen'}`,
|
|
488
|
+
kind: 'generated_image',
|
|
489
|
+
filePath: outputPath,
|
|
490
|
+
route: '$Image-UX-Review',
|
|
491
|
+
stage: provider
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return writeImageArtifactPathContract(root, {
|
|
495
|
+
missionId: input.mission_id || 'unassigned',
|
|
496
|
+
images: [{
|
|
497
|
+
id: `${provider}-${input.source_screen_id || 'screen'}`,
|
|
498
|
+
kind: 'generated_image',
|
|
499
|
+
filePath: outputPath,
|
|
500
|
+
route: '$Image-UX-Review',
|
|
501
|
+
stage: provider
|
|
502
|
+
}],
|
|
503
|
+
artifactPath: path.join(input.output_dir, 'image-artifact-path-contract.json')
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async function resolveImageArtifactRoot(input) {
|
|
507
|
+
const cwdRoot = await projectRoot(process.cwd()).catch(() => process.cwd());
|
|
508
|
+
const resolvedCwd = path.resolve(process.cwd());
|
|
509
|
+
if (path.resolve(cwdRoot) !== resolvedCwd)
|
|
510
|
+
return cwdRoot;
|
|
511
|
+
return projectRoot(input.output_dir || process.cwd()).catch(() => cwdRoot);
|
|
512
|
+
}
|
|
471
513
|
export async function generateGptImage2CalloutReview(input, opts = {}) {
|
|
472
514
|
if (opts.fake === true || process.env.SKS_TEST_FAKE_IMAGEGEN === '1') {
|
|
473
515
|
return createFakeImagegenAdapter(opts.fakeAdapter || {}).generateCalloutReview(input);
|
|
@@ -479,21 +521,11 @@ export async function generateGptImage2CalloutReview(input, opts = {}) {
|
|
|
479
521
|
// allowApiFallback:false or SKS_IMAGEGEN_ALLOW_API_FALLBACK=0.
|
|
480
522
|
const openAiKeyPresent = Boolean(opts.openai?.apiKey || process.env.OPENAI_API_KEY);
|
|
481
523
|
const explicitDisableApiFallback = opts.allowApiFallback === false || process.env.SKS_IMAGEGEN_ALLOW_API_FALLBACK === '0';
|
|
482
|
-
// codex-lb imagegen
|
|
483
|
-
//
|
|
484
|
-
// image_generation tool call is just another Responses request). When codex-lb
|
|
485
|
-
// is the active, fully-configured auth (selected provider + key + base_url) and
|
|
486
|
-
// there is no direct OPENAI_API_KEY, enable it BY DEFAULT so image generation
|
|
487
|
-
// works for users authenticated only through codex-lb — that is the common case
|
|
488
|
-
// and a hard block here is the bug the user hit. It still never overrides a real
|
|
489
|
-
// OpenAI key, and SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK=0 (or
|
|
490
|
-
// allowCodexLbApiFallback:false) opts out for callers that require Codex App
|
|
491
|
-
// built-in evidence only.
|
|
492
|
-
const codexLbAuthActive = capability?.codex_lb?.available === true;
|
|
524
|
+
// codex-lb imagegen is a direct API fallback, not Codex App imagegen evidence.
|
|
525
|
+
// It must be explicitly enabled by the caller or environment.
|
|
493
526
|
const explicitDisableCodexLbFallback = opts.allowCodexLbApiFallback === false || process.env.SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK === '0';
|
|
494
527
|
const allowCodexLbApiFallback = !explicitDisableCodexLbFallback && (opts.allowCodexLbApiFallback === true
|
|
495
|
-
|| process.env.SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK === '1'
|
|
496
|
-
|| (codexLbAuthActive && !openAiKeyPresent));
|
|
528
|
+
|| process.env.SKS_IMAGEGEN_ALLOW_CODEX_LB_API_FALLBACK === '1');
|
|
497
529
|
const allowApiFallback = !explicitDisableApiFallback && (opts.allowApiFallback === true
|
|
498
530
|
|| process.env.SKS_IMAGEGEN_ALLOW_API_FALLBACK === '1'
|
|
499
531
|
|| openAiKeyPresent
|
|
@@ -74,6 +74,24 @@ export async function recordMadDbToolResult(input) {
|
|
|
74
74
|
event
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
|
+
export async function maybeRecordMadDbToolResultFromToolUse(input) {
|
|
78
|
+
const payload = input.toolResult ?? input.toolCallPayload ?? {};
|
|
79
|
+
const hook = lifecycleHookFromUnknown(input.decision)
|
|
80
|
+
|| lifecycleHookFromUnknown(input.toolCallPayload)
|
|
81
|
+
|| lifecycleHookFromUnknown(input.toolResult)
|
|
82
|
+
|| await readLatestPendingMadDbLifecycleHook(input.root, input.missionId, input.toolCallPayload || payload);
|
|
83
|
+
if (!hook)
|
|
84
|
+
return null;
|
|
85
|
+
const ok = !madDbToolUseFailed(payload);
|
|
86
|
+
return recordMadDbToolResult({
|
|
87
|
+
root: input.root,
|
|
88
|
+
missionId: input.missionId,
|
|
89
|
+
hook,
|
|
90
|
+
ok,
|
|
91
|
+
rowCount: extractRowCount(payload),
|
|
92
|
+
error: ok ? null : extractToolError(payload)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
77
95
|
export function lifecycleHookFromUnknown(value) {
|
|
78
96
|
const candidate = value?.ledger_result_hook || value?.mad_db?.ledger_result_hook || value;
|
|
79
97
|
const missionId = stringOrNull(candidate?.mission_id || candidate?.missionId);
|
|
@@ -107,6 +125,59 @@ function hookMatchesPayload(hook, payload) {
|
|
|
107
125
|
return true;
|
|
108
126
|
return toolText.includes(String(hook.tool_name).toLowerCase()) || String(hook.tool_name).toLowerCase().includes(toolText);
|
|
109
127
|
}
|
|
128
|
+
function madDbToolUseFailed(payload = {}) {
|
|
129
|
+
if (payload?.isError === true || payload?.tool_response?.isError === true || payload?.toolResponse?.isError === true || payload?.result?.isError === true)
|
|
130
|
+
return true;
|
|
131
|
+
const candidates = [
|
|
132
|
+
payload.exit_code,
|
|
133
|
+
payload.exitCode,
|
|
134
|
+
payload.tool_response?.exit_code,
|
|
135
|
+
payload.toolResponse?.exitCode,
|
|
136
|
+
payload.result?.exit_code,
|
|
137
|
+
payload.result?.exitCode
|
|
138
|
+
];
|
|
139
|
+
for (const candidate of candidates) {
|
|
140
|
+
if (candidate === undefined || candidate === null || candidate === '')
|
|
141
|
+
continue;
|
|
142
|
+
const n = Number(candidate);
|
|
143
|
+
if (Number.isFinite(n))
|
|
144
|
+
return n !== 0;
|
|
145
|
+
}
|
|
146
|
+
if (payload.success === false || payload.tool_response?.success === false || payload.toolResponse?.success === false || payload.result?.success === false)
|
|
147
|
+
return true;
|
|
148
|
+
if (payload.executed === false)
|
|
149
|
+
return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
function extractRowCount(payload = {}) {
|
|
153
|
+
const candidates = [
|
|
154
|
+
payload.row_count,
|
|
155
|
+
payload.rowCount,
|
|
156
|
+
payload.tool_response?.row_count,
|
|
157
|
+
payload.tool_response?.rowCount,
|
|
158
|
+
payload.toolResponse?.rowCount,
|
|
159
|
+
payload.result?.row_count,
|
|
160
|
+
payload.result?.rowCount,
|
|
161
|
+
payload.result?.rows_affected,
|
|
162
|
+
payload.tool_response?.rows_affected
|
|
163
|
+
];
|
|
164
|
+
for (const candidate of candidates) {
|
|
165
|
+
if (candidate === undefined || candidate === null || candidate === '')
|
|
166
|
+
continue;
|
|
167
|
+
const parsed = Number(candidate);
|
|
168
|
+
if (Number.isFinite(parsed))
|
|
169
|
+
return parsed;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
function extractToolError(payload = {}) {
|
|
174
|
+
if (payload?.result?.isError === true && Array.isArray(payload.result.content)) {
|
|
175
|
+
const text = payload.result.content.map((entry) => entry?.text || entry?.message || '').filter(Boolean).join('\n');
|
|
176
|
+
if (text.trim())
|
|
177
|
+
return text.trim();
|
|
178
|
+
}
|
|
179
|
+
return String(payload.error || payload.message || payload.stderr || payload.tool_response?.stderr || payload.toolResponse?.stderr || payload.result?.stderr || payload.result?.error || 'tool_failed');
|
|
180
|
+
}
|
|
110
181
|
async function hasTerminalLifecycleEvent(root, missionId, operationId) {
|
|
111
182
|
const ledger = path.join(missionDir(root, missionId), 'mad-db-ledger.jsonl');
|
|
112
183
|
const text = await readText(ledger, '').catch(() => '');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
|
|
3
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { policyForPluginMcpServer } from './mcp-server-policy.js';
|
|
5
|
+
export function buildMcpPluginServerCandidates(inventory) {
|
|
6
|
+
const candidates = inventory.plugins.flatMap((plugin) => plugin.remote_mcp_servers.map((server) => policyForPluginMcpServer({
|
|
7
|
+
pluginId: plugin.id,
|
|
8
|
+
name: server.name,
|
|
9
|
+
url: server.url,
|
|
10
|
+
authType: server.auth_type
|
|
11
|
+
})));
|
|
12
|
+
return {
|
|
13
|
+
schema: 'sks.mcp-plugin-server-candidates.v1',
|
|
14
|
+
generated_at: nowIso(),
|
|
15
|
+
candidates,
|
|
16
|
+
candidate_only: true,
|
|
17
|
+
blockers: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export async function writeMcpPluginInventoryArtifacts(root, input = {}) {
|
|
21
|
+
const inventory = input.inventory || await buildCodexPluginInventory();
|
|
22
|
+
const candidates = buildMcpPluginServerCandidates(inventory);
|
|
23
|
+
const pluginArtifact = path.join(root, '.sneakoscope', 'codex-plugin-inventory.json');
|
|
24
|
+
const candidateArtifact = path.join(root, '.sneakoscope', 'mcp-plugin-server-candidates.json');
|
|
25
|
+
await writeJsonAtomic(pluginArtifact, inventory);
|
|
26
|
+
await writeJsonAtomic(candidateArtifact, candidates);
|
|
27
|
+
return { inventory, candidates, plugin_artifact: pluginArtifact, candidate_artifact: candidateArtifact };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=mcp-plugin-inventory.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function policyForPluginMcpServer(input) {
|
|
2
|
+
const haystack = `${input.name} ${input.url || ''}`.toLowerCase();
|
|
3
|
+
const dbRelated = /supabase|postgres|database|sql|db\b/.test(haystack);
|
|
4
|
+
const oauth = /oauth/i.test(String(input.authType || ''));
|
|
5
|
+
return {
|
|
6
|
+
name: input.name,
|
|
7
|
+
plugin_id: input.pluginId,
|
|
8
|
+
url: input.url || null,
|
|
9
|
+
auth_type: input.authType || null,
|
|
10
|
+
candidate_only: true,
|
|
11
|
+
auto_enable: false,
|
|
12
|
+
destructive_tools_auto_enabled: false,
|
|
13
|
+
db_safety_required: dbRelated,
|
|
14
|
+
mad_db_required_for_destructive: dbRelated,
|
|
15
|
+
oauth_prerefresh_recommended: oauth,
|
|
16
|
+
policy_notes: [
|
|
17
|
+
'Remote MCP servers from plugin detail are candidate only.',
|
|
18
|
+
'Do not auto-enable destructive MCP tools.',
|
|
19
|
+
dbRelated ? 'DB MCP servers require DB safety and Mad-DB for destructive operations.' : 'Non-DB MCP candidate still requires explicit operator enablement.',
|
|
20
|
+
oauth ? 'OAuth-backed MCP should trigger pre-refresh doctor check.' : ''
|
|
21
|
+
].filter(Boolean)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=mcp-server-policy.js.map
|