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
|
@@ -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,
|
|
@@ -144,30 +144,38 @@ export async function runAgentScheduler(input) {
|
|
|
144
144
|
batch_id: batchId,
|
|
145
145
|
meta: { launch_count: launches.length, active_count_before: active.size }
|
|
146
146
|
}).catch(() => undefined);
|
|
147
|
+
// Telemetry appends run concurrently across launches (per-slot ordering
|
|
148
|
+
// preserved inside each async chain). Awaiting these file writes in
|
|
149
|
+
// series before each dispatch serialized worker launch by 2 disk writes
|
|
150
|
+
// per slot — with 20 slots that is 40 sequential appends before the last
|
|
151
|
+
// worker even started.
|
|
152
|
+
const dispatchTelemetryWrites = [];
|
|
147
153
|
for (const launch of launches) {
|
|
148
154
|
const { slot, openedSlot, generation, agent, workItem } = launch;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
155
|
+
dispatchTelemetryWrites.push((async () => {
|
|
156
|
+
await appendParallelRuntimeEvent(input.root, input.missionId, {
|
|
157
|
+
event_type: 'slot_reserved',
|
|
158
|
+
slot_id: slot.slot_id,
|
|
159
|
+
generation_index: generation.generation_index,
|
|
160
|
+
session_id: generation.session_id,
|
|
161
|
+
pid: null,
|
|
162
|
+
backend: 'scheduler',
|
|
163
|
+
placement: 'unknown',
|
|
164
|
+
batch_id: batchId,
|
|
165
|
+
meta: { work_item_id: workItem.id }
|
|
166
|
+
}).catch(() => undefined);
|
|
167
|
+
await appendParallelRuntimeEvent(input.root, input.missionId, {
|
|
168
|
+
event_type: 'worker_launch_invoked',
|
|
169
|
+
slot_id: slot.slot_id,
|
|
170
|
+
generation_index: generation.generation_index,
|
|
171
|
+
session_id: generation.session_id,
|
|
172
|
+
pid: null,
|
|
173
|
+
backend: 'scheduler',
|
|
174
|
+
placement: 'unknown',
|
|
175
|
+
batch_id: batchId,
|
|
176
|
+
meta: { work_item_id: workItem.id }
|
|
177
|
+
}).catch(() => undefined);
|
|
178
|
+
})());
|
|
171
179
|
const promise = Promise.resolve()
|
|
172
180
|
.then(() => input.launchSession({ agent, workItem, generation, slot: openedSlot, queue, state }))
|
|
173
181
|
.then((result) => ({
|
|
@@ -212,14 +220,14 @@ export async function runAgentScheduler(input) {
|
|
|
212
220
|
accumulateActiveSlotTime();
|
|
213
221
|
active.set(generation.session_id, { slot_id: slot.slot_id, work_item_id: workItem.id, session_id: generation.session_id, promise });
|
|
214
222
|
}
|
|
223
|
+
await Promise.all(dispatchTelemetryWrites);
|
|
215
224
|
await appendAgentWorkQueueEvent(input.root, 'batch_work_items_dispatched', {
|
|
216
225
|
batch_id: batchId,
|
|
217
226
|
launch_count: launches.length,
|
|
218
227
|
session_ids: launches.map((launch) => launch.generation.session_id),
|
|
219
228
|
work_item_ids: launches.map((launch) => launch.workItem.id)
|
|
220
229
|
});
|
|
221
|
-
|
|
222
|
-
await appendAgentWorkQueueEvent(input.root, 'work_item_dispatched', { work_item_id: launch.workItem.id, session_id: launch.generation.session_id, slot_id: launch.slot.slot_id });
|
|
230
|
+
await Promise.all(launches.map((launch) => appendAgentWorkQueueEvent(input.root, 'work_item_dispatched', { work_item_id: launch.workItem.id, session_id: launch.generation.session_id, slot_id: launch.slot.slot_id })));
|
|
223
231
|
if (backfill) {
|
|
224
232
|
const firstLaunch = launches[0];
|
|
225
233
|
const refillLatencyMs = Math.max(0, Date.now() - backfill.closed_at_ms);
|
|
@@ -11,6 +11,7 @@ import { buildZellijSlotPaneCommand } from '../zellij/zellij-slot-pane-renderer.
|
|
|
11
11
|
import { resolveZellijWorkerPaneUiMode } from '../zellij/zellij-ui-mode.js';
|
|
12
12
|
import { appendZellijSlotTelemetry } from '../zellij/zellij-slot-telemetry.js';
|
|
13
13
|
import { appendParallelRuntimeEvent } from './parallel-runtime-proof.js';
|
|
14
|
+
import { appendAgentMessage } from './agent-message-bus.js';
|
|
14
15
|
export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
|
|
15
16
|
export function createNativeCliSessionSwarmRecorder(root, input) {
|
|
16
17
|
return new NativeCliSessionSwarmRecorder(root, input);
|
|
@@ -625,6 +626,15 @@ class NativeCliSessionSwarmRecorder {
|
|
|
625
626
|
log_tail: input.logTail || '',
|
|
626
627
|
blockers: input.blockers || []
|
|
627
628
|
}).catch(() => undefined);
|
|
629
|
+
if (input.eventType === 'worker_completed' || input.eventType === 'worker_failed') {
|
|
630
|
+
await appendAgentMessage(this.root, {
|
|
631
|
+
from: String(ctx.agent?.slot_id || ctx.agent?.id || 'worker'),
|
|
632
|
+
session_id: ctx.agent?.session_id == null ? '' : String(ctx.agent.session_id),
|
|
633
|
+
to: 'orchestrator',
|
|
634
|
+
type: input.eventType,
|
|
635
|
+
body: input.logTail || input.eventType
|
|
636
|
+
}).catch(() => undefined);
|
|
637
|
+
}
|
|
628
638
|
const parallelEvent = mapTelemetryToParallelEvent(input.eventType);
|
|
629
639
|
if (parallelEvent) {
|
|
630
640
|
await appendParallelRuntimeEvent(this.root, this.input.missionId, {
|
|
@@ -653,6 +663,12 @@ class NativeCliSessionSwarmRecorder {
|
|
|
653
663
|
summary() {
|
|
654
664
|
const closed = this.records.filter((row) => row.status === 'closed');
|
|
655
665
|
const processIds = this.records.map((row) => row.pid).filter((pid) => Number.isFinite(Number(pid)));
|
|
666
|
+
// Both pane-backed primitives count as zellij pane worker sessions: the
|
|
667
|
+
// worker command can run inside the pane (full-debug) or headless behind a
|
|
668
|
+
// live slot renderer pane (compact-slots default). Counting only the
|
|
669
|
+
// former under-reported pane sessions as 0 in the default UI mode.
|
|
670
|
+
const paneBackedRecords = this.records.filter((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane'
|
|
671
|
+
|| row.scaling_primitive === 'native_cli_process_with_zellij_slot_renderer');
|
|
656
672
|
return {
|
|
657
673
|
schema: NATIVE_CLI_SESSION_SWARM_SCHEMA,
|
|
658
674
|
generated_at: nowIso(),
|
|
@@ -660,8 +676,12 @@ class NativeCliSessionSwarmRecorder {
|
|
|
660
676
|
mission_id: this.input.missionId,
|
|
661
677
|
route: this.input.route,
|
|
662
678
|
backend: this.input.backend,
|
|
663
|
-
scaling_primitive: this.records.some((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane')
|
|
664
|
-
|
|
679
|
+
scaling_primitive: this.records.some((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane')
|
|
680
|
+
? 'native_cli_process_in_zellij_worker_pane'
|
|
681
|
+
: paneBackedRecords.length
|
|
682
|
+
? 'native_cli_process_with_zellij_slot_renderer'
|
|
683
|
+
: 'native_cli_process',
|
|
684
|
+
zellij_pane_worker_sessions: paneBackedRecords.length,
|
|
665
685
|
requested_agents: this.input.requestedAgents,
|
|
666
686
|
target_active_slots: this.input.targetActiveSlots,
|
|
667
687
|
spawned_worker_process_count: this.records.length,
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
import { attemptCodexAppLaunch } from './codex-app-launcher.js';
|
|
5
|
+
export function buildCodexAppHandoffPrompt(request) {
|
|
6
|
+
return [
|
|
7
|
+
'SKS Codex Desktop /app handoff request',
|
|
8
|
+
`Mission: ${request.mission_id}`,
|
|
9
|
+
`Route: ${request.route}`,
|
|
10
|
+
`Reason: ${request.reason}`,
|
|
11
|
+
`Workspace: ${request.workspace_path}`,
|
|
12
|
+
request.thread_ref ? `Thread: ${request.thread_ref}` : '',
|
|
13
|
+
'',
|
|
14
|
+
'Artifacts:',
|
|
15
|
+
...(request.artifacts || []).map((artifact) => `- ${artifact}`),
|
|
16
|
+
'',
|
|
17
|
+
'Prompt:',
|
|
18
|
+
request.prompt,
|
|
19
|
+
'',
|
|
20
|
+
'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.'
|
|
21
|
+
].filter((line) => line !== '').join('\n');
|
|
22
|
+
}
|
|
23
|
+
export async function runCodexAppHandoff(root, request) {
|
|
24
|
+
const capability = await detectCodex0138Capability();
|
|
25
|
+
const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
|
|
26
|
+
const desktopSupported = capability.supports_app_handoff === true && platformSupported;
|
|
27
|
+
const launchMode = request.launch_mode || 'artifact-only';
|
|
28
|
+
const dir = path.join(root, '.sneakoscope', 'missions', request.mission_id, 'qa-loop');
|
|
29
|
+
const artifactPath = path.join(dir, 'app-handoff.json');
|
|
30
|
+
const promptArtifactPath = path.join(dir, 'app-handoff-prompt.md');
|
|
31
|
+
const blockers = [
|
|
32
|
+
...(capability.supports_app_handoff ? [] : ['codex_0_138_app_handoff_unavailable']),
|
|
33
|
+
...(platformSupported ? [] : ['codex_app_handoff_platform_unsupported'])
|
|
34
|
+
];
|
|
35
|
+
const prompt = buildCodexAppHandoffPrompt(request);
|
|
36
|
+
await writeTextAtomic(promptArtifactPath, prompt);
|
|
37
|
+
const launchAttempt = desktopSupported
|
|
38
|
+
? await attemptCodexAppLaunch({
|
|
39
|
+
cwd: root,
|
|
40
|
+
promptArtifactPath,
|
|
41
|
+
mode: launchMode,
|
|
42
|
+
timeoutMs: 3000
|
|
43
|
+
})
|
|
44
|
+
: await attemptCodexAppLaunch({
|
|
45
|
+
cwd: root,
|
|
46
|
+
promptArtifactPath,
|
|
47
|
+
mode: 'artifact-only',
|
|
48
|
+
timeoutMs: 3000
|
|
49
|
+
});
|
|
50
|
+
const status = request.require_desktop && !desktopSupported
|
|
51
|
+
? 'blocked_for_desktop_review'
|
|
52
|
+
: request.require_desktop && launchMode === 'attempt-launch' && launchAttempt.attempted && !launchAttempt.launched
|
|
53
|
+
? 'blocked_for_desktop_review'
|
|
54
|
+
: desktopSupported && launchAttempt.launched
|
|
55
|
+
? 'launched_pending_confirmation'
|
|
56
|
+
: desktopSupported
|
|
57
|
+
? 'pending'
|
|
58
|
+
: 'skipped';
|
|
59
|
+
const result = {
|
|
60
|
+
schema: 'sks.codex-app-handoff-result.v1',
|
|
61
|
+
ok: request.require_desktop ? status !== 'blocked_for_desktop_review' : true,
|
|
62
|
+
attempted: launchAttempt.attempted,
|
|
63
|
+
launched: launchAttempt.launched,
|
|
64
|
+
status,
|
|
65
|
+
codex_0138_capability: capability,
|
|
66
|
+
command_line: launchAttempt.command_line,
|
|
67
|
+
launch_attempt: launchAttempt,
|
|
68
|
+
confirmation_required: request.require_desktop,
|
|
69
|
+
desktop_handoff_supported: desktopSupported,
|
|
70
|
+
fallback_reason: desktopSupported
|
|
71
|
+
? launchAttempt.fallback_reason || 'desktop_handoff_pending_operator_confirmation'
|
|
72
|
+
: blockers.join(';') || null,
|
|
73
|
+
artifact_path: artifactPath,
|
|
74
|
+
prompt_artifact_path: promptArtifactPath,
|
|
75
|
+
blockers: request.require_desktop ? [...blockers, ...(status === 'blocked_for_desktop_review' ? launchAttempt.blockers : [])] : []
|
|
76
|
+
};
|
|
77
|
+
await writeJsonAtomic(artifactPath, {
|
|
78
|
+
...result,
|
|
79
|
+
request,
|
|
80
|
+
operator_instruction: {
|
|
81
|
+
open: 'codex /app',
|
|
82
|
+
prompt_artifact: path.relative(root, promptArtifactPath),
|
|
83
|
+
created_at: nowIso()
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
export function qaLoopShouldRequestAppHandoff(input = {}) {
|
|
89
|
+
const args = input.args || [];
|
|
90
|
+
return args.includes('--app-handoff')
|
|
91
|
+
|| process.env.SKS_QA_LOOP_APP_HANDOFF === '1'
|
|
92
|
+
|| input.visualArtifactsPresent === true
|
|
93
|
+
|| input.zellijUiBlocked === true
|
|
94
|
+
|| input.pluginAppTemplateUnavailable === true
|
|
95
|
+
|| input.userRequestedDesktopReview === true
|
|
96
|
+
|| input.uiRequired === true && process.env.SKS_QA_LOOP_APP_HANDOFF_FOR_VISUAL === '1';
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=codex-app-handoff.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
2
|
+
import { runProcess } from '../fsx.js';
|
|
3
|
+
export async function attemptCodexAppLaunch(input) {
|
|
4
|
+
const platform = process.platform;
|
|
5
|
+
const timeoutMs = Math.max(1, Math.min(Number(input.timeoutMs || 3000), 3000));
|
|
6
|
+
const codexBin = input.codexBin || await findCodexBinary();
|
|
7
|
+
const commandLine = [codexBin || 'codex', '/app'];
|
|
8
|
+
if (input.mode === 'artifact-only') {
|
|
9
|
+
return launchAttempt({
|
|
10
|
+
attempted: false,
|
|
11
|
+
launched: false,
|
|
12
|
+
platform,
|
|
13
|
+
mode: input.mode,
|
|
14
|
+
command_line: commandLine,
|
|
15
|
+
exit_code: null,
|
|
16
|
+
fallback_reason: 'artifact_only_mode',
|
|
17
|
+
blockers: []
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const platformSupported = platform === 'darwin' || platform === 'win32';
|
|
21
|
+
if (!platformSupported) {
|
|
22
|
+
return launchAttempt({
|
|
23
|
+
attempted: false,
|
|
24
|
+
launched: false,
|
|
25
|
+
platform,
|
|
26
|
+
mode: input.mode,
|
|
27
|
+
command_line: commandLine,
|
|
28
|
+
exit_code: null,
|
|
29
|
+
fallback_reason: 'unsupported_platform_artifact_only_fallback',
|
|
30
|
+
blockers: ['codex_app_handoff_platform_unsupported']
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!codexBin) {
|
|
34
|
+
return launchAttempt({
|
|
35
|
+
attempted: false,
|
|
36
|
+
launched: false,
|
|
37
|
+
platform,
|
|
38
|
+
mode: input.mode,
|
|
39
|
+
command_line: commandLine,
|
|
40
|
+
exit_code: null,
|
|
41
|
+
fallback_reason: 'codex_cli_missing_artifact_only_fallback',
|
|
42
|
+
blockers: ['codex_cli_missing']
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (process.env.SKS_CODEX_APP_LAUNCH_FAKE === '1') {
|
|
46
|
+
const launched = process.env.SKS_CODEX_APP_LAUNCH_FAKE_LAUNCHED !== '0';
|
|
47
|
+
return launchAttempt({
|
|
48
|
+
attempted: true,
|
|
49
|
+
launched,
|
|
50
|
+
platform,
|
|
51
|
+
mode: input.mode,
|
|
52
|
+
command_line: commandLine,
|
|
53
|
+
exit_code: launched ? 0 : 1,
|
|
54
|
+
stdout_tail: launched ? 'fake codex /app launched' : '',
|
|
55
|
+
stderr_tail: launched ? '' : 'fake launch failed',
|
|
56
|
+
fallback_reason: launched ? null : 'fake_launch_failed',
|
|
57
|
+
blockers: launched ? [] : ['codex_app_launch_failed']
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const result = await runProcess(codexBin, ['/app'], {
|
|
61
|
+
cwd: input.cwd,
|
|
62
|
+
timeoutMs,
|
|
63
|
+
maxOutputBytes: 32 * 1024,
|
|
64
|
+
input: `Continue the SKS mission using this prompt artifact:\n${input.promptArtifactPath}\n`
|
|
65
|
+
}).catch((err) => ({
|
|
66
|
+
code: -1,
|
|
67
|
+
stdout: '',
|
|
68
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
69
|
+
timedOut: false
|
|
70
|
+
}));
|
|
71
|
+
const code = typeof result.code === 'number' ? result.code : null;
|
|
72
|
+
const stdout = String(result.stdout || '');
|
|
73
|
+
const stderr = String(result.stderr || '');
|
|
74
|
+
const markerLaunched = /(?:handoff|desktop|app).*?(?:launched|opened|ready)|(?:launched|opened).*?(?:handoff|desktop|app)/i.test(`${stdout}\n${stderr}`);
|
|
75
|
+
const launched = code === 0 || markerLaunched;
|
|
76
|
+
const timedOut = result.timedOut === true || code === 124;
|
|
77
|
+
const fallbackReason = launched
|
|
78
|
+
? null
|
|
79
|
+
: timedOut
|
|
80
|
+
? 'codex_app_handoff_interactive_or_timed_out_artifact_only_fallback'
|
|
81
|
+
: 'codex_app_launch_failed_artifact_only_fallback';
|
|
82
|
+
return launchAttempt({
|
|
83
|
+
attempted: true,
|
|
84
|
+
launched,
|
|
85
|
+
platform,
|
|
86
|
+
mode: input.mode,
|
|
87
|
+
command_line: commandLine,
|
|
88
|
+
exit_code: code,
|
|
89
|
+
stdout_tail: stdout.slice(-4000),
|
|
90
|
+
stderr_tail: stderr.slice(-4000),
|
|
91
|
+
fallback_reason: fallbackReason,
|
|
92
|
+
blockers: launched ? [] : [timedOut ? 'codex_app_launch_interactive_or_timeout' : 'codex_app_launch_failed']
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function launchAttempt(input) {
|
|
96
|
+
return {
|
|
97
|
+
schema: 'sks.codex-app-launch-attempt.v1',
|
|
98
|
+
stdout_tail: input.stdout_tail || '',
|
|
99
|
+
stderr_tail: input.stderr_tail || '',
|
|
100
|
+
...input
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=codex-app-launcher.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
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 probeMode = process.env.SKS_CODEX_0138_PROBE === '1' ? 'feature-probe' : 'version-only';
|
|
16
|
+
const featureProbeResults = probeMode === 'feature-probe'
|
|
17
|
+
? await probeCodex0138Features(codexBin, { fake })
|
|
18
|
+
: {
|
|
19
|
+
plugin_json: 'skipped',
|
|
20
|
+
app_handoff_platform: 'skipped',
|
|
21
|
+
image_path_exposure_contract: 'sks-enforced'
|
|
22
|
+
};
|
|
23
|
+
const pluginJsonOk = atLeast138 && (probeMode === 'version-only' || featureProbeResults.plugin_json !== 'failed');
|
|
24
|
+
const appHandoffOk = atLeast138 && (probeMode === 'version-only' || featureProbeResults.app_handoff_platform === 'passed');
|
|
25
|
+
const blockers = [
|
|
26
|
+
...(!codexBin ? ['codex_cli_missing'] : []),
|
|
27
|
+
...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features']),
|
|
28
|
+
...(probeMode === 'feature-probe' && featureProbeResults.plugin_json === 'failed' ? ['codex_plugin_json_probe_failed'] : [])
|
|
29
|
+
];
|
|
30
|
+
return {
|
|
31
|
+
schema: 'sks.codex-0138-capability.v1',
|
|
32
|
+
ok: atLeast138 && blockers.length === 0,
|
|
33
|
+
probe_mode: probeMode,
|
|
34
|
+
codex_bin: codexBin || null,
|
|
35
|
+
version_text: versionText || null,
|
|
36
|
+
parsed_version: parsed,
|
|
37
|
+
supports_app_handoff: appHandoffOk,
|
|
38
|
+
supports_plugin_json: pluginJsonOk,
|
|
39
|
+
supports_image_path_exposure: atLeast138,
|
|
40
|
+
supports_model_defined_efforts: atLeast138,
|
|
41
|
+
supports_app_server_token_usage: atLeast138,
|
|
42
|
+
supports_v2_pat_auth: atLeast138,
|
|
43
|
+
supports_oauth_mcp_prerefresh: atLeast138,
|
|
44
|
+
feature_probe_results: featureProbeResults,
|
|
45
|
+
blockers
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function writeCodex0138CapabilityArtifacts(root, input = {}) {
|
|
49
|
+
const capability = await detectCodex0138Capability({ codexBin: input.codexBin || null });
|
|
50
|
+
const report = { ...capability, generated_at: nowIso() };
|
|
51
|
+
const rootArtifact = path.join(root, '.sneakoscope', 'codex-0138-capability.json');
|
|
52
|
+
await writeJsonAtomic(rootArtifact, report);
|
|
53
|
+
let missionArtifact = null;
|
|
54
|
+
if (input.missionId) {
|
|
55
|
+
missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0138-capability.json');
|
|
56
|
+
await writeJsonAtomic(missionArtifact, report);
|
|
57
|
+
}
|
|
58
|
+
return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
|
|
59
|
+
}
|
|
60
|
+
export function parseCodexVersion(text) {
|
|
61
|
+
return parseCodexVersionText(text);
|
|
62
|
+
}
|
|
63
|
+
export function semverGte(actual, minimum) {
|
|
64
|
+
return compareSemverLike(actual, minimum) >= 0;
|
|
65
|
+
}
|
|
66
|
+
async function readCodexVersionText(codexBin) {
|
|
67
|
+
if (!codexBin)
|
|
68
|
+
return null;
|
|
69
|
+
const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
|
|
70
|
+
code: 1,
|
|
71
|
+
stdout: '',
|
|
72
|
+
stderr: err?.message || String(err)
|
|
73
|
+
}));
|
|
74
|
+
const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
75
|
+
return result.code === 0 ? text : text || null;
|
|
76
|
+
}
|
|
77
|
+
async function probeCodex0138Features(codexBin, opts = {}) {
|
|
78
|
+
if (opts.fake) {
|
|
79
|
+
return {
|
|
80
|
+
plugin_json: process.env.SKS_CODEX_0138_FAKE_PLUGIN_JSON_FAIL === '1' ? 'failed' : 'passed',
|
|
81
|
+
app_handoff_platform: process.platform === 'darwin' || process.platform === 'win32' ? 'passed' : 'failed',
|
|
82
|
+
image_path_exposure_contract: 'sks-enforced'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const timeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0138_PROBE_TIMEOUT_MS || 3000) || 3000);
|
|
86
|
+
const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
|
|
87
|
+
if (!codexBin) {
|
|
88
|
+
return {
|
|
89
|
+
plugin_json: 'failed',
|
|
90
|
+
app_handoff_platform: platformSupported ? 'skipped' : 'failed',
|
|
91
|
+
image_path_exposure_contract: 'sks-enforced'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const list = await runProcess(codexBin, ['plugin', 'list', '--json'], { timeoutMs, maxOutputBytes: 64 * 1024 }).catch(() => ({ code: 1 }));
|
|
95
|
+
const detailHelp = await runProcess(codexBin, ['plugin', 'detail', '--help'], { timeoutMs, maxOutputBytes: 64 * 1024 }).catch(() => ({ code: 1 }));
|
|
96
|
+
return {
|
|
97
|
+
plugin_json: list.code === 0 && detailHelp.code === 0 ? 'passed' : 'failed',
|
|
98
|
+
app_handoff_platform: platformSupported ? 'passed' : 'failed',
|
|
99
|
+
image_path_exposure_contract: 'sks-enforced'
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=codex-0138-capability.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { collectCodexModelMetadata } from './codex-model-metadata.js';
|
|
4
|
+
export const SKS_FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
|
|
5
|
+
export function codexModelEffortCapability(input = {}) {
|
|
6
|
+
const metadataIsFallback = input.metadata?.source === 'fallback';
|
|
7
|
+
const advertised = metadataIsFallback ? [] : normalizeAdvertisedEfforts(input.metadata?.advertised_efforts || input.advertisedEfforts);
|
|
8
|
+
const order = advertised.length ? advertised : SKS_FALLBACK_EFFORT_ORDER;
|
|
9
|
+
const requestedDefault = input.metadata?.default_effort || input.defaultEffort;
|
|
10
|
+
const defaultEffort = order.includes(String(requestedDefault || '')) ? String(requestedDefault) : order.includes('medium') ? 'medium' : order[0] || 'medium';
|
|
11
|
+
return {
|
|
12
|
+
model: String(input.metadata?.model || input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
|
|
13
|
+
advertised_efforts: order,
|
|
14
|
+
default_effort: defaultEffort,
|
|
15
|
+
order_source: advertised.length ? 'model-advertised' : 'sks-fallback',
|
|
16
|
+
metadata_source: input.metadata?.source || null,
|
|
17
|
+
metadata_blockers: input.metadata?.blockers || []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export async function resolveCodexModelEffortCapability(input = {}) {
|
|
21
|
+
const metadata = await collectCodexModelMetadata({ model: input.model || null });
|
|
22
|
+
return codexModelEffortCapability({ metadata });
|
|
23
|
+
}
|
|
24
|
+
export async function writeCodexModelEffortCapabilityArtifact(root, input) {
|
|
25
|
+
const capability = await resolveCodexModelEffortCapability({ model: input.model || null });
|
|
26
|
+
const artifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-model-effort-capability.json');
|
|
27
|
+
await writeJsonAtomic(artifact, {
|
|
28
|
+
schema: 'sks.codex-model-effort-capability-artifact.v1',
|
|
29
|
+
generated_at: new Date().toISOString(),
|
|
30
|
+
...capability
|
|
31
|
+
});
|
|
32
|
+
return { capability, artifact };
|
|
33
|
+
}
|
|
34
|
+
export function normalizeAdvertisedEfforts(value) {
|
|
35
|
+
const rows = Array.isArray(value) ? value : String(value || '').split(',');
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
const effort = String(row || '').trim().toLowerCase();
|
|
40
|
+
if (!effort || seen.has(effort))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(effort);
|
|
43
|
+
out.push(effort);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
export function nextAdvertisedEffort(current, capability = codexModelEffortCapability()) {
|
|
48
|
+
const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
|
|
49
|
+
const index = Math.max(0, order.indexOf(current));
|
|
50
|
+
return order[Math.min(order.length - 1, index + 1)] || current || capability.default_effort;
|
|
51
|
+
}
|
|
52
|
+
export function modelEffortAtLeast(target, capability = codexModelEffortCapability()) {
|
|
53
|
+
const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
|
|
54
|
+
if (order.includes(target))
|
|
55
|
+
return target;
|
|
56
|
+
if (target === 'recovery')
|
|
57
|
+
return order.includes('high') ? 'high' : order[order.length - 1];
|
|
58
|
+
if (target === 'forensic_vision')
|
|
59
|
+
return order.includes('xhigh') ? 'xhigh' : order[order.length - 1];
|
|
60
|
+
return capability.default_effort;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=codex-model-capabilities.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
2
|
+
import { runProcess } from '../fsx.js';
|
|
3
|
+
const FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
|
|
4
|
+
export async function collectCodexModelMetadata(input = {}) {
|
|
5
|
+
if (process.env.SKS_CODEX_MODEL_METADATA_FAKE === '1') {
|
|
6
|
+
const advertised = normalizeAdvertisedEfforts(process.env.SKS_CODEX_MODEL_EFFORTS || 'low,medium,high,xhigh');
|
|
7
|
+
return metadata(String(input.model || process.env.SKS_CODEX_MODEL || 'gpt-5.5'), advertised, 'medium', 'app-server', []);
|
|
8
|
+
}
|
|
9
|
+
const model = String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5');
|
|
10
|
+
const appServer = await readAppServerMetadata(model);
|
|
11
|
+
if (appServer)
|
|
12
|
+
return appServer;
|
|
13
|
+
const cli = await readCodexCliMetadata(model);
|
|
14
|
+
if (cli)
|
|
15
|
+
return cli;
|
|
16
|
+
const envEfforts = normalizeAdvertisedEfforts(process.env.SKS_CODEX_MODEL_EFFORTS || '');
|
|
17
|
+
if (envEfforts.length)
|
|
18
|
+
return metadata(model, envEfforts, process.env.SKS_CODEX_MODEL_DEFAULT_EFFORT || 'medium', 'env', []);
|
|
19
|
+
return metadata(model, FALLBACK_EFFORT_ORDER, 'medium', 'fallback', ['codex_model_metadata_unavailable']);
|
|
20
|
+
}
|
|
21
|
+
async function readAppServerMetadata(model) {
|
|
22
|
+
const url = String(process.env.CODEX_APP_SERVER_METADATA_URL || process.env.SKS_CODEX_APP_SERVER_METADATA_URL || '').trim();
|
|
23
|
+
if (!url)
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(3000) });
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
return null;
|
|
29
|
+
const payload = await response.json();
|
|
30
|
+
return normalizePayload(payload, model, 'app-server');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function readCodexCliMetadata(model) {
|
|
37
|
+
const bin = await findCodexBinary();
|
|
38
|
+
if (!bin)
|
|
39
|
+
return null;
|
|
40
|
+
const commands = [
|
|
41
|
+
['model', 'metadata', '--json'],
|
|
42
|
+
['debug', 'model-metadata', '--json'],
|
|
43
|
+
['capabilities', '--json']
|
|
44
|
+
];
|
|
45
|
+
for (const args of commands) {
|
|
46
|
+
const result = await runProcess(bin, args, { timeoutMs: 3000, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
47
|
+
if (!result || result.code !== 0)
|
|
48
|
+
continue;
|
|
49
|
+
try {
|
|
50
|
+
const payload = JSON.parse(`${result.stdout || ''}${result.stderr || ''}`.trim() || '{}');
|
|
51
|
+
const normalized = normalizePayload(payload, model, 'codex-cli');
|
|
52
|
+
if (normalized.advertised_efforts.length)
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function normalizePayload(payload, fallbackModel, source) {
|
|
60
|
+
const row = Array.isArray(payload?.models)
|
|
61
|
+
? payload.models.find((candidate) => String(candidate?.id || candidate?.model || candidate?.name || '') === fallbackModel) || payload.models[0]
|
|
62
|
+
: payload?.model_metadata || payload?.metadata || payload;
|
|
63
|
+
const efforts = normalizeAdvertisedEfforts(row?.advertised_efforts || row?.advertisedEfforts || row?.reasoning_efforts || row?.reasoningEfforts || payload?.advertised_efforts);
|
|
64
|
+
return metadata(String(row?.model || row?.id || row?.name || fallbackModel), efforts, row?.default_effort || row?.defaultEffort || payload?.default_effort || 'medium', source, efforts.length ? [] : ['codex_model_metadata_efforts_missing']);
|
|
65
|
+
}
|
|
66
|
+
function metadata(model, efforts, defaultEffort, source, blockers) {
|
|
67
|
+
const advertised = normalizeAdvertisedEfforts(efforts);
|
|
68
|
+
const defaultValue = advertised.includes(defaultEffort) ? defaultEffort : advertised.includes('medium') ? 'medium' : advertised[0] || 'medium';
|
|
69
|
+
return {
|
|
70
|
+
schema: 'sks.codex-model-metadata.v1',
|
|
71
|
+
model,
|
|
72
|
+
advertised_efforts: advertised,
|
|
73
|
+
default_effort: defaultValue,
|
|
74
|
+
source,
|
|
75
|
+
blockers
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function normalizeAdvertisedEfforts(value) {
|
|
79
|
+
const rows = Array.isArray(value) ? value : String(value || '').split(',');
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const row of rows) {
|
|
83
|
+
const effort = String(row || '').trim().toLowerCase();
|
|
84
|
+
if (!effort || seen.has(effort))
|
|
85
|
+
continue;
|
|
86
|
+
seen.add(effort);
|
|
87
|
+
out.push(effort);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=codex-model-metadata.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]',
|