sneakoscope 2.0.17 → 2.0.18
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 +19 -28
- 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/core/agents/agent-effort-policy.js +7 -1
- package/dist/core/codex-app/codex-app-handoff.js +77 -0
- package/dist/core/codex-control/codex-0138-capability.js +64 -0
- package/dist/core/codex-control/codex-model-capabilities.js +41 -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-json.js +152 -0
- package/dist/core/commands/mad-sks-command.js +4 -0
- package/dist/core/commands/naruto-command.js +3 -1
- package/dist/core/commands/qa-loop-command.js +111 -4
- 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 +99 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +24 -3
- 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-budget-policy.js +37 -0
- package/dist/core/qa-loop.js +28 -2
- package/dist/core/usage/codex-account-usage.js +78 -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 +18 -0
- package/dist/scripts/release-gate-existence-audit.js +5 -1
- package/package.json +25 -2
- 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/README.md
CHANGED
|
@@ -16,43 +16,34 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
|
|
|
16
16
|
|
|
17
17
|
## Current Release
|
|
18
18
|
|
|
19
|
-
SKS **2.0.
|
|
19
|
+
SKS **2.0.18** is the Codex 0.138 integration release: capability artifacts, Desktop `/app` handoff, plugin JSON inventory, image saved-path contracts, model-advertised effort order, account usage budget policy, and startup doctor checks.
|
|
20
20
|
|
|
21
21
|
What changed:
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- Release audit, dynamic selection, and stamp hashing use `release-gates.v2.json` as the manifest source of truth.
|
|
32
|
-
- Git capability checks detect repo roots, Git dirs, worktree support, and safe cache roots while blocking in-repo worktree roots unless `SKS_ALLOW_IN_REPO_WORKTREES=1` is explicit.
|
|
33
|
-
- Worker worktree allocation creates isolated branches/paths under `$SKS_WORKTREE_ROOT`, `$XDG_CACHE_HOME/sks/worktrees`, or `~/.cache/sks/worktrees`; main checkouts stay untouched until integration.
|
|
34
|
-
- Product Design plugin readiness now checks both local and remote Codex App catalogs, auto-installs the remote plugin when needed, and records the installed/enabled skill surface.
|
|
35
|
-
- UI/design/PPT runtime routes prefer Product Design for research, ideation, audit, design QA, prototype, URL-to-code, image-to-code, share, and user-context steps.
|
|
36
|
-
- Naruto read-only runs force write mode off, propagate no-patch reasons through worker proof, and skip changed-file lease checks when no write-capable patch envelope exists.
|
|
37
|
-
- `codex-sdk` is the default native agent backend for Team, QA, Research, Naruto, MAD-SKS, and direct agent runs, with every runtime task entering through `runCodexTask`.
|
|
38
|
-
- Codex App UI snapshot, preservation, clobber guard, and doctor repair checks protect host-owned Fast UI/profile settings around `sks --mad`.
|
|
39
|
-
- Provider context resolves `openai`, `codex-lb`, and `codex-app` with badge/fallback surfaces while avoiding private Codex App UI mutation.
|
|
40
|
-
- UltraRouter writes `ultra-router-proof.json` decisions with tier, scores, hard filters, cache state, and cheapest-good-enough profile selection.
|
|
41
|
-
- Reliability Shield writes `codex-reliability-shield.json` for empty-result retry, stream-idle blocking, tool-result repair, and no-CoT keepalive heartbeats.
|
|
42
|
-
- Raw `codex exec` execution is removed from runtime fallback paths; explicit legacy requests are blocked with `legacy_codex_exec_runtime_removed`.
|
|
43
|
-
- SDK runs write `codex-control-proof.json`, `codex-thread-registry.json`, `codex-sdk-events.jsonl`, and schema-validated worker results.
|
|
44
|
-
- Zellij proof now links `pane_id`, `slot_id`, `generation_index`, `session_id`, `sdk_thread_id`, provider, and `service_tier`.
|
|
45
|
-
- Production runtime scripts are TypeScript source under `src/scripts` and build to `dist/scripts`; Python remains optional diagnostics under `pytools`.
|
|
46
|
-
- Release gates include `codex-control:*`, `ultra-router:*`, `codex-sdk:*`, Codex App Fast UI preservation, provider badges, Zellij spawn-on-demand, slot/pane binding, release truth, and real smoke checks.
|
|
47
|
-
- Research synthesis is now evidence-bound in non-mock runs; deterministic report generation is mock/fallback only.
|
|
48
|
-
- Research quality checks reject repeated paragraphs, template-like prose, low source density, low claim density, and thin implementation sections.
|
|
49
|
-
- Research handoffs now include context, key claims, evidence summary, blueprint sections, parallel work items, acceptance tests, rollback, and source appendix for `$Team` or `$Naruto`.
|
|
23
|
+
- `sks doctor` now reports Codex 0.138 feature readiness, plugin JSON inventory, candidate-only remote MCP servers, unavailable app templates, and repairable plugin discovery cache state.
|
|
24
|
+
- QA-LOOP can write a Codex Desktop `/app` handoff artifact with `--app-handoff` or require it with `--app-handoff-required`; this never substitutes for Codex Chrome Extension web UI evidence.
|
|
25
|
+
- Zellij slot panes and the right-column anchor surface pending QA `/app` handoffs so desktop review is visible during long native-agent runs.
|
|
26
|
+
- Codex plugin detail JSON is normalized into `.sneakoscope/codex-plugin-inventory.json`, and plugin-provided remote MCP servers remain candidate-only until explicitly enabled under DB/Mad-DB safety policy.
|
|
27
|
+
- Imagegen and QA image flows write `image-artifact-path-contract.json` with exact saved file paths and follow-up edit hints.
|
|
28
|
+
- Effort routing now understands the fallback order `minimal < low < medium < high < xhigh`, records model capability, and escalates QA effort after repeated failures.
|
|
29
|
+
- Codex account token usage can be recorded from an app-server usage endpoint, and QA budget policy reduces remote concurrency near limits while preserving GPT final review.
|
|
30
|
+
- Naruto final pass status now depends on the parallel runtime proof, and Mad-DB post-tool lifecycle recording handles MCP `isError` failures.
|
|
50
31
|
|
|
51
32
|
Quick checks:
|
|
52
33
|
|
|
53
34
|
```bash
|
|
54
35
|
npm run typecheck
|
|
55
36
|
npm run build
|
|
37
|
+
npm run codex:0138-capability
|
|
38
|
+
npm run codex-sdk:version-compat
|
|
39
|
+
npm run codex-app:handoff
|
|
40
|
+
npm run codex-plugin:inventory
|
|
41
|
+
npm run qa-loop:app-handoff
|
|
42
|
+
npm run image:artifact-path-contract
|
|
43
|
+
npm run codex:effort-order
|
|
44
|
+
npm run codex:account-usage
|
|
45
|
+
npm run codex:0138-doctor
|
|
46
|
+
npm run doctor:codex-0138-fix
|
|
56
47
|
npm run codex-control:capability
|
|
57
48
|
npm run codex-control:structured-output
|
|
58
49
|
npm run codex-control:event-stream-ledger
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 2.0.
|
|
7
|
+
Some("--version") => println!("sks-rs 2.0.18"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build-stamp.v1",
|
|
3
3
|
"package_name": "sneakoscope",
|
|
4
|
-
"package_version": "2.0.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "2.0.18",
|
|
5
|
+
"source_digest": "3c246288e22bf5f29b5ba20a2a05b3f15a6afb9e6d680c531f7e78ef996c8c33",
|
|
6
|
+
"source_file_count": 2238,
|
|
7
|
+
"built_at_source_time": 1780992531184
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/commands/doctor.js
CHANGED
|
@@ -19,6 +19,10 @@ import { repairCodexAppFastUi } from '../core/codex-app/codex-app-fast-ui-repair
|
|
|
19
19
|
import { resolveProviderContext } from '../core/provider/provider-context.js';
|
|
20
20
|
import { readLocalModelConfig } from '../core/agents/ollama-worker-config.js';
|
|
21
21
|
import { repairAgentRoleConfigs } from '../core/agents/agent-role-config.js';
|
|
22
|
+
import { writeCodex0138CapabilityArtifacts } from '../core/codex-control/codex-0138-capability.js';
|
|
23
|
+
import { runCodex0138Doctor } from '../core/doctor/codex-0138-doctor.js';
|
|
24
|
+
import { writeCodexPluginInventoryArtifacts, pluginAppTemplatePolicy } from '../core/codex-plugins/codex-plugin-json.js';
|
|
25
|
+
import { writeMcpPluginInventoryArtifacts } from '../core/mcp/mcp-plugin-inventory.js';
|
|
22
26
|
export async function run(_command, args = []) {
|
|
23
27
|
const doctorFix = flag(args, '--fix');
|
|
24
28
|
let setupRepair = null;
|
|
@@ -167,6 +171,13 @@ export async function run(_command, args = []) {
|
|
|
167
171
|
: null;
|
|
168
172
|
const { detectImagegenCapability } = await import('../core/imagegen/imagegen-capability.js');
|
|
169
173
|
const imagegen = await detectImagegenCapability({ codexBin: codexBin || undefined }).catch((err) => ({ ok: false, error: err.message, auth_readiness: null }));
|
|
174
|
+
const codex0138Capability = await writeCodex0138CapabilityArtifacts(root, { codexBin: codexBin || null }).catch((err) => ({ error: err?.message || String(err), report: null }));
|
|
175
|
+
const codex0138Doctor = await runCodex0138Doctor(root, { fix: doctorFix }).catch((err) => ({ schema: 'sks.codex-0138-doctor.v1', ok: false, error: err?.message || String(err), blockers: ['codex_0138_doctor_exception'], warnings: [] }));
|
|
176
|
+
const pluginInventory = await writeCodexPluginInventoryArtifacts(root).catch((err) => ({ error: err?.message || String(err), report: null, artifact: null }));
|
|
177
|
+
const pluginPolicy = pluginInventory?.report ? pluginAppTemplatePolicy(pluginInventory.report) : null;
|
|
178
|
+
const mcpPluginInventory = pluginInventory?.report
|
|
179
|
+
? await writeMcpPluginInventoryArtifacts(root, { inventory: pluginInventory.report }).catch((err) => ({ error: err?.message || String(err), candidates: null }))
|
|
180
|
+
: null;
|
|
170
181
|
const pkgBytes = await dirSize(root).catch(() => 0);
|
|
171
182
|
const ready = await writeDoctorReadinessMatrix(root, {
|
|
172
183
|
codex,
|
|
@@ -180,10 +191,14 @@ export async function run(_command, args = []) {
|
|
|
180
191
|
agent_role_config: agentRoleConfigRepair,
|
|
181
192
|
repair: configRepair,
|
|
182
193
|
codex_app_ui: codexAppUi,
|
|
194
|
+
codex_0138_doctor: codex0138Doctor,
|
|
195
|
+
codex_plugin_inventory: pluginInventory?.report || null,
|
|
196
|
+
codex_plugin_app_template_policy: pluginPolicy,
|
|
183
197
|
require_codex_cli_config_load: flag(args, '--fix') || flag(args, '--require-actual-codex'),
|
|
184
198
|
operator_actions: [
|
|
185
199
|
...(codexConfig.operator_actions || []),
|
|
186
|
-
...(configRepair?.operator_actions || [])
|
|
200
|
+
...(configRepair?.operator_actions || []),
|
|
201
|
+
...(pluginPolicy?.doctor_warnings || [])
|
|
187
202
|
]
|
|
188
203
|
});
|
|
189
204
|
const zellijReadiness = buildZellijReadiness(root, zellij, ready);
|
|
@@ -211,6 +226,13 @@ export async function run(_command, args = []) {
|
|
|
211
226
|
auth_readiness: imagegen.auth_readiness || null,
|
|
212
227
|
codex_app_builtin_available: imagegen.codex_app?.available === true
|
|
213
228
|
},
|
|
229
|
+
codex_0138: {
|
|
230
|
+
capability: codex0138Capability.report || null,
|
|
231
|
+
doctor: codex0138Doctor,
|
|
232
|
+
plugins: pluginInventory?.report || null,
|
|
233
|
+
plugin_app_template_policy: pluginPolicy,
|
|
234
|
+
mcp_plugin_inventory: mcpPluginInventory?.candidates || null
|
|
235
|
+
},
|
|
214
236
|
ready,
|
|
215
237
|
sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
|
|
216
238
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
|
|
@@ -262,6 +284,22 @@ export async function run(_command, args = []) {
|
|
|
262
284
|
console.log(` - ${action}`);
|
|
263
285
|
}
|
|
264
286
|
}
|
|
287
|
+
const codex0138 = codex0138Capability.report || {};
|
|
288
|
+
console.log('Codex 0.138 features:');
|
|
289
|
+
console.log(` /app handoff: ${codex0138.supports_app_handoff ? 'ok' : 'unavailable'}`);
|
|
290
|
+
console.log(` plugin JSON: ${codex0138.supports_plugin_json ? 'ok' : 'unavailable'}`);
|
|
291
|
+
console.log(` image path exposure: ${codex0138.supports_image_path_exposure ? 'ok' : 'unavailable'}`);
|
|
292
|
+
console.log(` OAuth MCP pre-refresh: ${codex0138.supports_oauth_mcp_prerefresh ? 'ok' : 'unavailable'}`);
|
|
293
|
+
const plugins = pluginInventory?.report?.plugins || [];
|
|
294
|
+
const remoteMcpCount = plugins.flatMap((plugin) => plugin.remote_mcp_servers || []).length;
|
|
295
|
+
const unavailableTemplates = pluginPolicy?.unavailable_app_templates?.length || 0;
|
|
296
|
+
console.log(`Codex plugins: ${pluginInventory?.report ? 'ok' : 'warning'}`);
|
|
297
|
+
console.log(` Remote MCP servers: ${remoteMcpCount} candidates`);
|
|
298
|
+
console.log(` Unavailable app templates: ${unavailableTemplates}`);
|
|
299
|
+
for (const warning of pluginPolicy?.doctor_warnings || [])
|
|
300
|
+
console.log(` warning: ${warning}`);
|
|
301
|
+
if (codex0138Doctor?.fixed?.length)
|
|
302
|
+
console.log(` doctor --fix repaired: ${codex0138Doctor.fixed.join(', ')}`);
|
|
265
303
|
console.log(`codex-lb: ${codexLb.ok ? 'ok' : `warning ${codexLb.circuit?.state || 'unknown'}`}`);
|
|
266
304
|
if (localModel) {
|
|
267
305
|
console.log('Local LLM:');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
|
|
1
2
|
const XHIGH_SIGNAL_RE = /(frontier|autoresearch|novelty|hypothesis|falsif|forensic|from-chat-img|image\s*work\s*order|새로운\s*연구|가설|포렌식)/i;
|
|
2
3
|
const HIGH_SIGNAL_RE = /(database|supabase|sql|migration|security|permission|mad|release|publish|deploy|architecture|policy|schema|hook|rollback|db|보안|배포|마이그레이션|데이터베이스|권한|릴리즈)/i;
|
|
3
4
|
const MEDIUM_SIGNAL_RE = /(tmux|terminal|cli|tool(?:\s|-)?call|router|routing|orchestrat|pipeline|multi[-\s]?session|multi[-\s]?agent|lease|ledger|proof|검증|파이프라인|오케스트레이션|병렬|에이전트)/i;
|
|
@@ -26,6 +27,7 @@ export function decideAgentEffort(input = {}) {
|
|
|
26
27
|
effort = 'high';
|
|
27
28
|
reason = 'implementation_lane_capped_at_high';
|
|
28
29
|
}
|
|
30
|
+
const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
|
|
29
31
|
return {
|
|
30
32
|
schema: 'sks.agent-effort-decision.v1',
|
|
31
33
|
policy_version: 1,
|
|
@@ -33,6 +35,7 @@ export function decideAgentEffort(input = {}) {
|
|
|
33
35
|
role,
|
|
34
36
|
reasoning_effort: effort,
|
|
35
37
|
model_reasoning_effort: effort,
|
|
38
|
+
model_effort_capability: modelCapability,
|
|
36
39
|
reasoning_profile: reasoningProfileName(effort),
|
|
37
40
|
service_tier: 'fast',
|
|
38
41
|
reason,
|
|
@@ -73,6 +76,7 @@ export function decideNarutoCloneEffort(input = {}) {
|
|
|
73
76
|
const writes = !readonly || /write|edit|route-local|workspace|patch|integrat/i.test(writePolicy) || hasActionTool;
|
|
74
77
|
const toolUse = writes || NARUTO_ACTION_TOOL_RE.test(prompt);
|
|
75
78
|
const effort = toolUse ? 'medium' : 'low';
|
|
79
|
+
const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
|
|
76
80
|
return {
|
|
77
81
|
schema: 'sks.agent-effort-decision.v1',
|
|
78
82
|
policy_version: 1,
|
|
@@ -80,6 +84,7 @@ export function decideNarutoCloneEffort(input = {}) {
|
|
|
80
84
|
role,
|
|
81
85
|
reasoning_effort: effort,
|
|
82
86
|
model_reasoning_effort: effort,
|
|
87
|
+
model_effort_capability: modelCapability,
|
|
83
88
|
reasoning_profile: reasoningProfileName(effort),
|
|
84
89
|
service_tier: 'fast',
|
|
85
90
|
reason: toolUse ? 'naruto_tool_use_medium' : 'naruto_simple_no_tool_low',
|
|
@@ -107,7 +112,8 @@ export function buildAgentEffortPolicy(roster = {}) {
|
|
|
107
112
|
policy_version: 1,
|
|
108
113
|
dynamic: true,
|
|
109
114
|
service_tier: 'fast',
|
|
110
|
-
allowed_efforts:
|
|
115
|
+
allowed_efforts: codexModelEffortCapability().advertised_efforts,
|
|
116
|
+
model_effort_capability: codexModelEffortCapability(),
|
|
111
117
|
max_agents: roster.max_agents || 20,
|
|
112
118
|
agent_count: roster.agent_count || decisions.length,
|
|
113
119
|
concurrency: roster.concurrency || decisions.length,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
3
|
+
import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
export function buildCodexAppHandoffPrompt(request) {
|
|
5
|
+
return [
|
|
6
|
+
'SKS Codex Desktop /app handoff request',
|
|
7
|
+
`Mission: ${request.mission_id}`,
|
|
8
|
+
`Route: ${request.route}`,
|
|
9
|
+
`Reason: ${request.reason}`,
|
|
10
|
+
`Workspace: ${request.workspace_path}`,
|
|
11
|
+
request.thread_ref ? `Thread: ${request.thread_ref}` : '',
|
|
12
|
+
'',
|
|
13
|
+
'Artifacts:',
|
|
14
|
+
...(request.artifacts || []).map((artifact) => `- ${artifact}`),
|
|
15
|
+
'',
|
|
16
|
+
'Prompt:',
|
|
17
|
+
request.prompt,
|
|
18
|
+
'',
|
|
19
|
+
'Operator instruction: open Codex Desktop with `codex /app` and continue this mission using the artifacts above. Do not treat this handoff artifact as web UI verification evidence.'
|
|
20
|
+
].filter((line) => line !== '').join('\n');
|
|
21
|
+
}
|
|
22
|
+
export async function runCodexAppHandoff(root, request) {
|
|
23
|
+
const capability = await detectCodex0138Capability();
|
|
24
|
+
const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
|
|
25
|
+
const desktopSupported = capability.supports_app_handoff === true && platformSupported;
|
|
26
|
+
const dir = path.join(root, '.sneakoscope', 'missions', request.mission_id, 'qa-loop');
|
|
27
|
+
const artifactPath = path.join(dir, 'app-handoff.json');
|
|
28
|
+
const promptArtifactPath = path.join(dir, 'app-handoff-prompt.md');
|
|
29
|
+
const blockers = [
|
|
30
|
+
...(capability.supports_app_handoff ? [] : ['codex_0_138_app_handoff_unavailable']),
|
|
31
|
+
...(platformSupported ? [] : ['codex_app_handoff_platform_unsupported'])
|
|
32
|
+
];
|
|
33
|
+
const prompt = buildCodexAppHandoffPrompt(request);
|
|
34
|
+
await writeTextAtomic(promptArtifactPath, prompt);
|
|
35
|
+
const status = request.require_desktop && !desktopSupported
|
|
36
|
+
? 'blocked_for_desktop_review'
|
|
37
|
+
: desktopSupported
|
|
38
|
+
? 'pending'
|
|
39
|
+
: 'skipped';
|
|
40
|
+
const result = {
|
|
41
|
+
schema: 'sks.codex-app-handoff-result.v1',
|
|
42
|
+
ok: request.require_desktop ? desktopSupported : true,
|
|
43
|
+
attempted: false,
|
|
44
|
+
launched: false,
|
|
45
|
+
status,
|
|
46
|
+
codex_0138_capability: capability,
|
|
47
|
+
command_line: ['codex', '/app'],
|
|
48
|
+
desktop_handoff_supported: desktopSupported,
|
|
49
|
+
fallback_reason: desktopSupported
|
|
50
|
+
? 'interactive_tui_handoff_pending_operator'
|
|
51
|
+
: blockers.join(';') || null,
|
|
52
|
+
artifact_path: artifactPath,
|
|
53
|
+
prompt_artifact_path: promptArtifactPath,
|
|
54
|
+
blockers: request.require_desktop ? blockers : []
|
|
55
|
+
};
|
|
56
|
+
await writeJsonAtomic(artifactPath, {
|
|
57
|
+
...result,
|
|
58
|
+
request,
|
|
59
|
+
operator_instruction: {
|
|
60
|
+
open: 'codex /app',
|
|
61
|
+
prompt_artifact: path.relative(root, promptArtifactPath),
|
|
62
|
+
created_at: nowIso()
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
export function qaLoopShouldRequestAppHandoff(input = {}) {
|
|
68
|
+
const args = input.args || [];
|
|
69
|
+
return args.includes('--app-handoff')
|
|
70
|
+
|| process.env.SKS_QA_LOOP_APP_HANDOFF === '1'
|
|
71
|
+
|| input.visualArtifactsPresent === true
|
|
72
|
+
|| input.zellijUiBlocked === true
|
|
73
|
+
|| input.pluginAppTemplateUnavailable === true
|
|
74
|
+
|| input.userRequestedDesktopReview === true
|
|
75
|
+
|| input.uiRequired === true && process.env.SKS_QA_LOOP_APP_HANDOFF_FOR_VISUAL === '1';
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=codex-app-handoff.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
export async function detectCodex0138Capability(input = {}) {
|
|
6
|
+
const fake = process.env.SKS_CODEX_0138_FAKE === '1';
|
|
7
|
+
const codexBin = fake
|
|
8
|
+
? input.codexBin || process.env.CODEX_BIN || 'codex'
|
|
9
|
+
: input.codexBin || process.env.CODEX_BIN || await findCodexBinary();
|
|
10
|
+
const versionText = fake
|
|
11
|
+
? String(process.env.SKS_CODEX_VERSION_FAKE || 'codex-cli 0.138.0')
|
|
12
|
+
: await readCodexVersionText(codexBin);
|
|
13
|
+
const parsed = parseCodexVersion(versionText);
|
|
14
|
+
const atLeast138 = Boolean(parsed && semverGte(parsed, '0.138.0'));
|
|
15
|
+
const blockers = [
|
|
16
|
+
...(!codexBin ? ['codex_cli_missing'] : []),
|
|
17
|
+
...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features'])
|
|
18
|
+
];
|
|
19
|
+
return {
|
|
20
|
+
schema: 'sks.codex-0138-capability.v1',
|
|
21
|
+
ok: atLeast138,
|
|
22
|
+
codex_bin: codexBin || null,
|
|
23
|
+
version_text: versionText || null,
|
|
24
|
+
parsed_version: parsed,
|
|
25
|
+
supports_app_handoff: atLeast138,
|
|
26
|
+
supports_plugin_json: atLeast138,
|
|
27
|
+
supports_image_path_exposure: atLeast138,
|
|
28
|
+
supports_model_defined_efforts: atLeast138,
|
|
29
|
+
supports_app_server_token_usage: atLeast138,
|
|
30
|
+
supports_v2_pat_auth: atLeast138,
|
|
31
|
+
supports_oauth_mcp_prerefresh: atLeast138,
|
|
32
|
+
blockers
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function writeCodex0138CapabilityArtifacts(root, input = {}) {
|
|
36
|
+
const capability = await detectCodex0138Capability({ codexBin: input.codexBin || null });
|
|
37
|
+
const report = { ...capability, generated_at: nowIso() };
|
|
38
|
+
const rootArtifact = path.join(root, '.sneakoscope', 'codex-0138-capability.json');
|
|
39
|
+
await writeJsonAtomic(rootArtifact, report);
|
|
40
|
+
let missionArtifact = null;
|
|
41
|
+
if (input.missionId) {
|
|
42
|
+
missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0138-capability.json');
|
|
43
|
+
await writeJsonAtomic(missionArtifact, report);
|
|
44
|
+
}
|
|
45
|
+
return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
46
|
+
}
|
|
47
|
+
export function parseCodexVersion(text) {
|
|
48
|
+
return parseCodexVersionText(text);
|
|
49
|
+
}
|
|
50
|
+
export function semverGte(actual, minimum) {
|
|
51
|
+
return compareSemverLike(actual, minimum) >= 0;
|
|
52
|
+
}
|
|
53
|
+
async function readCodexVersionText(codexBin) {
|
|
54
|
+
if (!codexBin)
|
|
55
|
+
return null;
|
|
56
|
+
const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
|
|
57
|
+
code: 1,
|
|
58
|
+
stdout: '',
|
|
59
|
+
stderr: err?.message || String(err)
|
|
60
|
+
}));
|
|
61
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
62
|
+
return result.code === 0 ? text : text || null;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=codex-0138-capability.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const SKS_FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
|
|
2
|
+
export function codexModelEffortCapability(input = {}) {
|
|
3
|
+
const advertised = normalizeAdvertisedEfforts(input.advertisedEfforts);
|
|
4
|
+
const order = advertised.length ? advertised : SKS_FALLBACK_EFFORT_ORDER;
|
|
5
|
+
const defaultEffort = order.includes(String(input.defaultEffort || '')) ? String(input.defaultEffort) : order.includes('medium') ? 'medium' : order[0] || 'medium';
|
|
6
|
+
return {
|
|
7
|
+
model: String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
|
|
8
|
+
advertised_efforts: order,
|
|
9
|
+
default_effort: defaultEffort,
|
|
10
|
+
order_source: advertised.length ? 'model-advertised' : 'sks-fallback'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function normalizeAdvertisedEfforts(value) {
|
|
14
|
+
const rows = Array.isArray(value) ? value : String(value || '').split(',');
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const effort = String(row || '').trim().toLowerCase();
|
|
19
|
+
if (!effort || seen.has(effort))
|
|
20
|
+
continue;
|
|
21
|
+
seen.add(effort);
|
|
22
|
+
out.push(effort);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
export function nextAdvertisedEffort(current, capability = codexModelEffortCapability()) {
|
|
27
|
+
const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
|
|
28
|
+
const index = Math.max(0, order.indexOf(current));
|
|
29
|
+
return order[Math.min(order.length - 1, index + 1)] || current || capability.default_effort;
|
|
30
|
+
}
|
|
31
|
+
export function modelEffortAtLeast(target, capability = codexModelEffortCapability()) {
|
|
32
|
+
const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
|
|
33
|
+
if (order.includes(target))
|
|
34
|
+
return target;
|
|
35
|
+
if (target === 'recovery')
|
|
36
|
+
return order.includes('high') ? 'high' : order[order.length - 1];
|
|
37
|
+
if (target === 'forensic_vision')
|
|
38
|
+
return order.includes('xhigh') ? 'xhigh' : order[order.length - 1];
|
|
39
|
+
return capability.default_effort;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=codex-model-capabilities.js.map
|
|
@@ -2,7 +2,7 @@ export function buildCodexSdkConfig(input) {
|
|
|
2
2
|
const config = {
|
|
3
3
|
model: String(process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
|
|
4
4
|
service_tier: 'fast',
|
|
5
|
-
model_reasoning_effort: 'medium',
|
|
5
|
+
model_reasoning_effort: String(input.modelReasoningEffort || input.reasoningEffort || process.env.SKS_CODEX_REASONING || process.env.CODEX_MODEL_REASONING_EFFORT || 'medium'),
|
|
6
6
|
mcp_servers: {},
|
|
7
7
|
sks: {
|
|
8
8
|
route: input.route,
|
|
@@ -445,7 +445,7 @@ async function ensurePythonCodexLbConfig(env, config) {
|
|
|
445
445
|
`model = ${tomlQuote(model)}`,
|
|
446
446
|
'model_provider = "codex-lb"',
|
|
447
447
|
'service_tier = "fast"',
|
|
448
|
-
|
|
448
|
+
`model_reasoning_effort = ${tomlQuote(String(config.model_reasoning_effort || env.SKS_CODEX_REASONING || env.CODEX_MODEL_REASONING_EFFORT || 'minimal'))}`,
|
|
449
449
|
'approval_policy = "never"',
|
|
450
450
|
'',
|
|
451
451
|
'[model_providers.codex-lb]',
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
4
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
export async function runCodexPluginListJson() {
|
|
6
|
+
if (process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1')
|
|
7
|
+
return fakePluginList();
|
|
8
|
+
const bin = await findCodexBinary();
|
|
9
|
+
if (!bin)
|
|
10
|
+
return { plugins: [], blockers: ['codex_cli_missing'] };
|
|
11
|
+
return runCodexJson(bin, ['plugin', 'list', '--json']);
|
|
12
|
+
}
|
|
13
|
+
export async function runCodexPluginDetailJson(pluginId) {
|
|
14
|
+
if (process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1')
|
|
15
|
+
return fakePluginDetail(pluginId);
|
|
16
|
+
const bin = await findCodexBinary();
|
|
17
|
+
if (!bin)
|
|
18
|
+
return { blockers: ['codex_cli_missing'] };
|
|
19
|
+
return runCodexJson(bin, ['plugin', 'detail', pluginId, '--json']);
|
|
20
|
+
}
|
|
21
|
+
export async function buildCodexPluginInventory() {
|
|
22
|
+
const capability = await detectCodex0138Capability();
|
|
23
|
+
const listJson = await runCodexPluginListJson();
|
|
24
|
+
const summaries = normalizePluginList(listJson);
|
|
25
|
+
const plugins = [];
|
|
26
|
+
for (const summary of summaries) {
|
|
27
|
+
const detail = await runCodexPluginDetailJson(summary.id || summary.name).catch((err) => ({ error: err?.message || String(err) }));
|
|
28
|
+
plugins.push(normalizePlugin(summary, detail));
|
|
29
|
+
}
|
|
30
|
+
const blockers = [
|
|
31
|
+
...(capability.supports_plugin_json ? [] : ['codex_0_138_plugin_json_unavailable']),
|
|
32
|
+
...normalizeList(listJson?.blockers)
|
|
33
|
+
];
|
|
34
|
+
return {
|
|
35
|
+
schema: 'sks.codex-plugin-inventory.v1',
|
|
36
|
+
generated_at: nowIso(),
|
|
37
|
+
codex_0138_capability: capability,
|
|
38
|
+
plugins,
|
|
39
|
+
marketplace_available: plugins.some((plugin) => plugin.source === 'marketplace' || plugin.source === 'remote') || Boolean(listJson?.marketplace_available || listJson?.marketplaceAvailable),
|
|
40
|
+
blockers
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function writeCodexPluginInventoryArtifacts(root, inventory = null) {
|
|
44
|
+
const report = inventory || await buildCodexPluginInventory();
|
|
45
|
+
const artifact = path.join(root, '.sneakoscope', 'codex-plugin-inventory.json');
|
|
46
|
+
await writeJsonAtomic(artifact, report);
|
|
47
|
+
return { report, artifact };
|
|
48
|
+
}
|
|
49
|
+
export function pluginAppTemplatePolicy(inventory) {
|
|
50
|
+
const unavailable = inventory.plugins.flatMap((plugin) => plugin.unavailable_app_templates.map((template) => ({
|
|
51
|
+
plugin: plugin.id,
|
|
52
|
+
template
|
|
53
|
+
})));
|
|
54
|
+
return {
|
|
55
|
+
schema: 'sks.codex-plugin-app-template-policy.v1',
|
|
56
|
+
ok: true,
|
|
57
|
+
unavailable_app_templates: unavailable,
|
|
58
|
+
qa_loop_app_handoff_recommended: unavailable.length > 0,
|
|
59
|
+
doctor_warnings: unavailable.map((row) => `plugin_app_template_unavailable:${row.plugin}`)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function runCodexJson(bin, args) {
|
|
63
|
+
const result = await runProcess(bin, args, { timeoutMs: 20_000, maxOutputBytes: 256 * 1024 }).catch((err) => ({
|
|
64
|
+
code: 1,
|
|
65
|
+
stdout: '',
|
|
66
|
+
stderr: err?.message || String(err)
|
|
67
|
+
}));
|
|
68
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
69
|
+
try {
|
|
70
|
+
return text ? JSON.parse(text) : {};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { raw_text: text, blockers: [`codex_plugin_json_parse_failed:${args.join(' ')}`] };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function normalizePluginList(value) {
|
|
77
|
+
if (Array.isArray(value))
|
|
78
|
+
return value;
|
|
79
|
+
for (const key of ['plugins', 'installed_plugins', 'installedPlugins', 'items']) {
|
|
80
|
+
if (Array.isArray(value?.[key]))
|
|
81
|
+
return value[key];
|
|
82
|
+
}
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
function normalizePlugin(summary, detail) {
|
|
86
|
+
const raw = { summary, detail };
|
|
87
|
+
const id = String(detail?.id || summary?.id || summary?.plugin_id || summary?.name || 'unknown');
|
|
88
|
+
const name = String(detail?.name || summary?.name || id);
|
|
89
|
+
const sourceText = String(detail?.source || detail?.marketplaceSource || summary?.source || summary?.marketplaceSource || '').toLowerCase();
|
|
90
|
+
const source = sourceText.includes('marketplace') ? 'marketplace'
|
|
91
|
+
: sourceText.includes('remote') ? 'remote'
|
|
92
|
+
: sourceText.includes('local') ? 'local'
|
|
93
|
+
: 'unknown';
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
name,
|
|
97
|
+
source,
|
|
98
|
+
installed: boolish(detail?.installed ?? summary?.installed, true),
|
|
99
|
+
enabled: boolish(detail?.enabled ?? summary?.enabled, true),
|
|
100
|
+
default_prompts: normalizeList(detail?.default_prompts || detail?.defaultPrompts || detail?.prompts),
|
|
101
|
+
remote_mcp_servers: normalizeMcpServers(detail?.remote_mcp_servers || detail?.remoteMcpServers || detail?.mcp_servers || detail?.mcpServers),
|
|
102
|
+
unavailable_app_templates: normalizeList(detail?.unavailable_app_templates || detail?.unavailableAppTemplates || detail?.app_templates_unavailable),
|
|
103
|
+
raw
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function normalizeMcpServers(value) {
|
|
107
|
+
const rows = Array.isArray(value) ? value : value && typeof value === 'object' ? Object.entries(value).map(([name, row]) => ({ name, ...(row || {}) })) : [];
|
|
108
|
+
return rows.map((row, index) => ({
|
|
109
|
+
name: String(row?.name || row?.id || `remote-mcp-${index + 1}`),
|
|
110
|
+
url: stringOrNull(row?.url || row?.endpoint),
|
|
111
|
+
auth_type: stringOrNull(row?.auth_type || row?.authType || row?.auth)
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
function normalizeList(value) {
|
|
115
|
+
return Array.isArray(value) ? value.filter(Boolean).map(String) : value ? [String(value)] : [];
|
|
116
|
+
}
|
|
117
|
+
function stringOrNull(value) {
|
|
118
|
+
const text = String(value || '').trim();
|
|
119
|
+
return text ? text : null;
|
|
120
|
+
}
|
|
121
|
+
function boolish(value, fallback = false) {
|
|
122
|
+
if (value === true || value === 'true')
|
|
123
|
+
return true;
|
|
124
|
+
if (value === false || value === 'false')
|
|
125
|
+
return false;
|
|
126
|
+
return fallback;
|
|
127
|
+
}
|
|
128
|
+
function fakePluginList() {
|
|
129
|
+
return {
|
|
130
|
+
marketplace_available: true,
|
|
131
|
+
plugins: [{
|
|
132
|
+
id: 'fixture-plugin',
|
|
133
|
+
name: 'Fixture Plugin',
|
|
134
|
+
source: 'marketplace',
|
|
135
|
+
installed: true,
|
|
136
|
+
enabled: true
|
|
137
|
+
}]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function fakePluginDetail(pluginId) {
|
|
141
|
+
return {
|
|
142
|
+
id: pluginId,
|
|
143
|
+
name: pluginId,
|
|
144
|
+
source: 'marketplace',
|
|
145
|
+
installed: true,
|
|
146
|
+
enabled: true,
|
|
147
|
+
default_prompts: ['Use the fixture plugin safely.'],
|
|
148
|
+
remote_mcp_servers: [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
|
|
149
|
+
unavailable_app_templates: ['fixture-desktop-template']
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=codex-plugin-json.js.map
|
|
@@ -20,6 +20,7 @@ import { runCodexLaunchPreflight } from '../preflight/parallel-preflight-engine.
|
|
|
20
20
|
import { diffCodexAppUiSnapshots, writeCodexAppUiSnapshot } from '../codex-app/codex-app-ui-state-snapshot.js';
|
|
21
21
|
import { checkSksUpdateNotice } from '../update/update-notice.js';
|
|
22
22
|
import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
|
|
23
|
+
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
23
24
|
export async function madHighCommand(args = [], deps = {}) {
|
|
24
25
|
const subcommand = firstSubcommand(args);
|
|
25
26
|
if (subcommand)
|
|
@@ -379,6 +380,7 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
|
|
|
379
380
|
const has = (scope) => allowedScopes.has(scope);
|
|
380
381
|
const dbWriteAllowed = has('db_write');
|
|
381
382
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
|
|
383
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
382
384
|
const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
|
|
383
385
|
// The interactive launch 'before' snapshot is only persisted (env + policy json)
|
|
384
386
|
// and is never compared against an 'after' snapshot during the session, so the
|
|
@@ -564,6 +566,7 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
564
566
|
}
|
|
565
567
|
export async function madSksFixture(root) {
|
|
566
568
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: '$MAD-SKS fixture permission gate' });
|
|
569
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
567
570
|
const gate = { schema_version: 1, passed: true, mad_sks_permission_active: true, permissions_deactivated: true, catastrophic_safety_guard_active: true, permission_profile: permissionGateSummary(), fixture: true };
|
|
568
571
|
await writeJsonAtomic(path.join(dir, 'mad-sks-gate.json'), gate);
|
|
569
572
|
return { mission_id: id, dir, gate };
|
|
@@ -738,6 +741,7 @@ async function materializeMadSksRun(root, targetRoot, permission, userIntent, js
|
|
|
738
741
|
if (!(await exists(path.join(root, '.sneakoscope'))))
|
|
739
742
|
await initProject(root, {});
|
|
740
743
|
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: userIntent });
|
|
744
|
+
await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
|
|
741
745
|
const before = await snapshotProtectedCore(packageRoot(), 'before');
|
|
742
746
|
const authorization = opts.authorizationManifest || createMadSksAuthorizationManifest({ permission, userIntent });
|
|
743
747
|
const authorizationPath = opts.authorizationManifestPath || path.join(dir, 'mad-sks-authorization.json');
|