sneakoscope 3.0.2 → 3.0.4

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.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/core/agents/runtime-proof-summary.js +17 -1
  8. package/dist/core/codex/codex-cli-syntax-builder.js +4 -1
  9. package/dist/core/codex-control/codex-0139-capability.js +74 -13
  10. package/dist/core/codex-control/codex-0139-doctor-real-probe.js +64 -0
  11. package/dist/core/codex-control/codex-0139-image-path-real-probe.js +94 -0
  12. package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +107 -0
  13. package/dist/core/codex-control/codex-0139-plugin-real-probes.js +119 -0
  14. package/dist/core/codex-control/codex-0139-probe-runner.js +117 -0
  15. package/dist/core/codex-control/codex-0139-real-probe-summary.js +37 -0
  16. package/dist/core/codex-control/codex-0139-real-probes.js +74 -0
  17. package/dist/core/codex-control/codex-0139-rich-schema-real-probe.js +43 -0
  18. package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +79 -0
  19. package/dist/core/codex-control/codex-0139-web-search-probe.js +72 -0
  20. package/dist/core/codex-control/codex-multi-agent-event-normalizer.js +15 -0
  21. package/dist/core/codex-control/codex-tool-schema-fixtures.js +57 -0
  22. package/dist/core/doctor/codex-0139-doctor.js +16 -0
  23. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  24. package/dist/core/fsx.js +25 -1
  25. package/dist/core/mcp/mcp-0-134-policy.js +3 -0
  26. package/dist/core/pipeline-internals/runtime-core.js +6 -1
  27. package/dist/core/version.js +1 -1
  28. package/dist/core/zellij/zellij-command.js +12 -1
  29. package/dist/core/zellij/zellij-fake-adapter.js +163 -0
  30. package/dist/core/zellij/zellij-worker-pane-summary.js +65 -0
  31. package/dist/scripts/github-release-body-helper.js +23 -1
  32. package/package.json +28 -2
  33. package/schemas/codex/codex-0139-real-probe-result.schema.json +85 -0
package/README.md CHANGED
@@ -35,7 +35,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.0.2** tracks Codex CLI `rust-v0.139.0` and hardens the 3.0.0 swarm release: capability detection for code-mode web search, preserved `oneOf`/`allOf` tool schemas, plugin marketplace `source`/cached catalog, the `-P` sandbox profile alias, the multi-agent v2 `interrupt_agent` rename, Zellij stacked-pane version gates, pane-lock concurrency proof, release cache safety fixtures, agent message proof summaries, and release proof source-truth artifacts. See [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md).
38
+ SKS **3.0.4** is Codex 0.139-aware while it bundles @openai/codex-sdk 0.138.0 at this release boundary. It detects and uses 0.139 features from the external Codex CLI when that CLI supports them, with release gates that include hermetic fixtures and actual real probes for code-mode web search, preserved `oneOf`/`allOf` tool schemas, doctor env redaction, plugin marketplace `source` and cache behavior, the `-P` profile alias, the multi-agent v2 `interrupt_agent` rename, image referenced-path routing, sandbox/proxy preservation, Zellij stacked/fallback pane proof, pane-lock openWorkerPane integration, release cache safety fixtures, runtime proof summaries, and release proof source-truth artifacts. See [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md) and [docs/codex-0.139-real-probes.md](docs/codex-0.139-real-probes.md).
39
39
 
40
40
  SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
41
41
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.0.2"
79
+ version = "3.0.4"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "3.0.2"
3
+ version = "3.0.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -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 3.0.2"),
7
+ Some("--version") => println!("sks-rs 3.0.4"),
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": "3.0.2",
5
- "source_digest": "5d782324cdb5c0cb66ce53c5733ebdd754313abc22b579907de282270a0e6178",
6
- "source_file_count": 2284,
7
- "built_at_source_time": 1781100033829
4
+ "package_version": "3.0.4",
5
+ "source_digest": "fbc7541cf9a5f0432c9ceb28c4478eca651010d2304fc499bbe27f2f62b41d4b",
6
+ "source_file_count": 2328,
7
+ "built_at_source_time": 1781240948838
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.0.2';
2
+ const FAST_PACKAGE_VERSION = '3.0.4';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import { findLatestMission, missionDir } from '../mission.js';
3
3
  import { readJson, writeJsonAtomic } from '../fsx.js';
4
4
  import { readAgentMessageBus } from './agent-message-bus.js';
5
+ import { buildZellijWorkerPaneSummary } from '../zellij/zellij-worker-pane-summary.js';
5
6
  export const RUNTIME_PROOF_SUMMARY_SCHEMA = 'sks.runtime-proof-summary.v1';
6
7
  export async function buildRuntimeProofSummary(root, missionIdInput = 'latest', opts = {}) {
7
8
  const missionId = missionIdInput === 'latest' ? await findLatestMission(root) : missionIdInput;
@@ -16,6 +17,7 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
16
17
  const governor = await readJson(path.join(agentsDir, 'naruto-concurrency-governor.json'), null);
17
18
  const messagesAll = await readAgentMessageBus(root, missionId, { max: 500 });
18
19
  const recentMessages = await readAgentMessageBus(root, missionId, { max: opts.maxMessages || 8 });
20
+ const zellijSummary = await buildZellijWorkerPaneSummary(root, missionId).catch(() => null);
19
21
  const failedMessages = messagesAll.filter((row) => row.event_type === 'worker_failed');
20
22
  const errorMessages = messagesAll.filter((row) => row.level === 'error');
21
23
  const telemetryAgeMs = telemetry?.updated_at ? Math.max(0, Date.now() - Date.parse(telemetry.updated_at)) : Number.MAX_SAFE_INTEGER;
@@ -27,7 +29,8 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
27
29
  ...(!scheduler ? ['agent_scheduler_state_missing'] : []),
28
30
  ...(parallel?.passed === false ? parallel.blockers || ['parallel_runtime_proof_failed'] : []),
29
31
  ...(errorMessages.length ? ['agent_message_bus_error_blockers'] : []),
30
- ...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : [])
32
+ ...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : []),
33
+ ...(zellijSummary?.blockers || [])
31
34
  ].map(String);
32
35
  const summary = {
33
36
  schema: RUNTIME_PROOF_SUMMARY_SCHEMA,
@@ -61,6 +64,15 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
61
64
  warning_count: messagesAll.filter((row) => row.level === 'warning').length,
62
65
  error_count: errorMessages.length
63
66
  },
67
+ zellij: {
68
+ stacked_requested_count: Number(zellijSummary?.stacked_requested_count || 0),
69
+ stacked_applied_count: Number(zellijSummary?.stacked_applied_count || 0),
70
+ stacked_fallback_count: Number(zellijSummary?.stacked_fallback_count || 0),
71
+ fallback_modes: zellijSummary?.fallback_modes || {},
72
+ pane_lock_wait_p95_ms: Number(zellijSummary?.pane_lock_wait_p95_ms || 0),
73
+ pane_lock_held_p95_ms: Number(zellijSummary?.pane_lock_held_p95_ms || 0),
74
+ duplicate_slot_anchor_count: Number(zellijSummary?.duplicate_slot_anchor_count || 0)
75
+ },
64
76
  blockers
65
77
  };
66
78
  await writeJsonAtomic(path.join(agentsDir, 'runtime-proof-summary.json'), summary);
@@ -75,6 +87,10 @@ export function renderRuntimeProofSummary(summary) {
75
87
  `Visible/headless: ${summary.ui.visible_panes} / ${summary.ui.headless_workers}`,
76
88
  `Telemetry: ${summary.ui.stale ? `stale ${(summary.ui.telemetry_age_ms / 1000).toFixed(1)}s` : `fresh ${(summary.ui.telemetry_age_ms / 1000).toFixed(1)}s`}`,
77
89
  `Model calls max: ${summary.model_calls.max_observed}`,
90
+ `Zellij stacked panes: ${summary.zellij.stacked_applied_count}/${summary.zellij.stacked_requested_count} applied`,
91
+ `Stack fallback: ${summary.zellij.stacked_fallback_count}`,
92
+ `Pane lock wait p95: ${summary.zellij.pane_lock_wait_p95_ms}ms`,
93
+ `SLOTS anchors: ${summary.zellij.duplicate_slot_anchor_count}`,
78
94
  ...(summary.messages.recent.length ? [
79
95
  'Recent worker messages:',
80
96
  ...summary.messages.recent.map((row) => ` ${messageStatusLabel(row)} ${row.slot_id || row.worker_id}: ${row.message}`)
@@ -23,7 +23,7 @@ export function buildCodexExecArgs(opts) {
23
23
  if (opts.skipGitRepoCheck)
24
24
  args.push('--skip-git-repo-check');
25
25
  if (opts.profile)
26
- args.push('--profile', opts.profile);
26
+ args.push(...buildCodexProfileArgs(opts.profile, opts.profileAlias));
27
27
  else if (opts.ignoreUserConfig)
28
28
  args.push('--ignore-user-config');
29
29
  if (opts.ignoreRules)
@@ -40,6 +40,9 @@ export function buildCodexExecArgs(opts) {
40
40
  args.push(opts.prompt);
41
41
  return args;
42
42
  }
43
+ export function buildCodexProfileArgs(profile, alias = 'long') {
44
+ return alias === 'short' ? ['-P', profile] : ['--profile', profile];
45
+ }
43
46
  function normalizeCodexServiceTier(value) {
44
47
  const text = String(value || '').toLowerCase();
45
48
  if (text === 'fast' || text === 'priority')
@@ -13,33 +13,53 @@ export async function detectCodex0139Capability(input = {}) {
13
13
  const parsed = parseCodexVersionText(versionText);
14
14
  const atLeast139 = Boolean(parsed && compareSemverLike(parsed, '0.139.0') >= 0);
15
15
  const probeMode = process.env.SKS_CODEX_0139_PROBE === '1' ? 'feature-probe' : 'version-only';
16
+ const probeTimeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
16
17
  const featureProbeResults = probeMode === 'feature-probe'
17
- ? await probeCodex0139Features(codexBin, { fake })
18
+ ? await probeCodex0139Features(codexBin, { fake, timeoutMs: probeTimeoutMs })
18
19
  : {
19
20
  marketplace_list_json: 'skipped',
20
- sandbox_profile_alias: 'skipped'
21
+ sandbox_profile_alias: 'skipped',
22
+ interrupt_agent_event_mapping: 'skipped',
23
+ rich_tool_schema_preservation: 'skipped',
24
+ doctor_env_redaction: 'skipped',
25
+ code_mode_web_search: 'skipped'
21
26
  };
22
27
  const marketplaceOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.marketplace_list_json !== 'failed');
23
28
  const profileAliasOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.sandbox_profile_alias !== 'failed');
29
+ const interruptAgentOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.interrupt_agent_event_mapping !== 'failed');
30
+ const richSchemaOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.rich_tool_schema_preservation !== 'failed');
31
+ const doctorEnvOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.doctor_env_redaction !== 'failed');
32
+ const codeSearchOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.code_mode_web_search !== 'failed');
33
+ const probeErrorSummary = Object.entries(featureProbeResults)
34
+ .filter(([, status]) => status === 'failed')
35
+ .map(([name]) => `${name}:failed`);
24
36
  const blockers = [
25
37
  ...(!codexBin ? ['codex_cli_missing'] : []),
26
38
  ...(atLeast139 ? [] : ['codex_0_139_required_for_search_schema_marketplace_features']),
27
- ...(probeMode === 'feature-probe' && featureProbeResults.marketplace_list_json === 'failed' ? ['codex_marketplace_list_json_probe_failed'] : [])
39
+ ...(probeMode === 'feature-probe' && featureProbeResults.marketplace_list_json === 'failed' ? ['codex_marketplace_list_json_probe_failed'] : []),
40
+ ...(probeMode === 'feature-probe' && featureProbeResults.sandbox_profile_alias === 'failed' ? ['codex_sandbox_profile_alias_probe_failed'] : []),
41
+ ...(probeMode === 'feature-probe' && featureProbeResults.interrupt_agent_event_mapping === 'failed' ? ['codex_interrupt_agent_probe_failed'] : []),
42
+ ...(probeMode === 'feature-probe' && featureProbeResults.rich_tool_schema_preservation === 'failed' ? ['codex_rich_tool_schema_probe_failed'] : []),
43
+ ...(probeMode === 'feature-probe' && featureProbeResults.doctor_env_redaction === 'failed' ? ['codex_doctor_env_redaction_probe_failed'] : []),
44
+ ...(probeMode === 'feature-probe' && featureProbeResults.code_mode_web_search === 'failed' ? ['codex_code_mode_web_search_probe_failed'] : [])
28
45
  ];
29
46
  return {
30
47
  schema: 'sks.codex-0139-capability.v1',
31
48
  ok: atLeast139 && blockers.length === 0,
32
49
  probe_mode: probeMode,
50
+ probe_timeout_ms: probeTimeoutMs,
51
+ probe_error_summary: probeErrorSummary,
33
52
  codex_bin: codexBin || null,
34
53
  version_text: versionText || null,
35
54
  parsed_version: parsed,
36
- supports_code_mode_web_search: atLeast139,
37
- supports_rich_tool_schemas: atLeast139,
38
- supports_doctor_env_details: atLeast139,
55
+ supports_code_mode_web_search: codeSearchOk,
56
+ supports_code_mode_web_search_real_verified: false,
57
+ supports_rich_tool_schemas: richSchemaOk,
58
+ supports_doctor_env_details: doctorEnvOk,
39
59
  supports_marketplace_source_field: marketplaceOk,
40
60
  supports_plugin_catalog_cache: atLeast139,
41
61
  supports_sandbox_profile_alias: profileAliasOk,
42
- supports_interrupt_agent_rename: atLeast139,
62
+ supports_interrupt_agent_rename: interruptAgentOk,
43
63
  feature_probe_results: featureProbeResults,
44
64
  blockers
45
65
  };
@@ -71,20 +91,37 @@ async function probeCodex0139Features(codexBin, opts = {}) {
71
91
  if (opts.fake) {
72
92
  return {
73
93
  marketplace_list_json: process.env.SKS_CODEX_0139_FAKE_MARKETPLACE_FAIL === '1' ? 'failed' : 'passed',
74
- sandbox_profile_alias: 'passed'
94
+ sandbox_profile_alias: process.env.SKS_CODEX_0139_FAKE_PROFILE_ALIAS_FAIL === '1' ? 'failed' : 'passed',
95
+ interrupt_agent_event_mapping: process.env.SKS_CODEX_0139_FAKE_INTERRUPT_FAIL === '1' ? 'failed' : 'passed',
96
+ rich_tool_schema_preservation: process.env.SKS_CODEX_0139_FAKE_RICH_SCHEMA_FAIL === '1' ? 'failed' : 'passed',
97
+ doctor_env_redaction: process.env.SKS_CODEX_0139_FAKE_DOCTOR_ENV_FAIL === '1' ? 'failed' : 'passed',
98
+ code_mode_web_search: process.env.SKS_CODEX_0139_FAKE_WEB_SEARCH_FAIL === '1' ? 'failed' : 'passed'
75
99
  };
76
100
  }
77
- const timeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
101
+ const timeoutMs = Math.max(1, Number(opts.timeoutMs || process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
78
102
  if (!codexBin) {
79
- return { marketplace_list_json: 'failed', sandbox_profile_alias: 'failed' };
103
+ return {
104
+ marketplace_list_json: 'failed',
105
+ sandbox_profile_alias: 'failed',
106
+ interrupt_agent_event_mapping: 'skipped',
107
+ rich_tool_schema_preservation: 'skipped',
108
+ doctor_env_redaction: 'skipped',
109
+ code_mode_web_search: 'skipped'
110
+ };
80
111
  }
81
112
  const marketplace = await runProcess(codexBin, ['plugin', 'marketplace', 'list', '--json'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
82
113
  const marketplaceListJson = marketplace.code === 0 && marketplaceSourcesPresent(marketplace.stdout) ? 'passed' : 'failed';
83
114
  const help = await runProcess(codexBin, ['--help'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
84
- const aliasOk = help.code === 0 && /(^|\s)-P[,\s]/m.test(String(help.stdout || ''));
115
+ const sandboxHelp = await runProcess(codexBin, ['sandbox', '--help'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
116
+ const aliasOk = help.code === 0 && codexHelpSupportsSandboxProfileAlias(help.stdout)
117
+ || sandboxHelp.code === 0 && codexHelpSupportsSandboxProfileAlias(sandboxHelp.stdout);
85
118
  return {
86
119
  marketplace_list_json: marketplaceListJson,
87
- sandbox_profile_alias: aliasOk ? 'passed' : 'failed'
120
+ sandbox_profile_alias: aliasOk ? 'passed' : 'failed',
121
+ interrupt_agent_event_mapping: 'skipped',
122
+ rich_tool_schema_preservation: 'skipped',
123
+ doctor_env_redaction: 'skipped',
124
+ code_mode_web_search: 'skipped'
88
125
  };
89
126
  }
90
127
  export function marketplaceSourcesPresent(stdout) {
@@ -93,10 +130,34 @@ export function marketplaceSourcesPresent(stdout) {
93
130
  const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : Array.isArray(parsed?.items) ? parsed.items : [];
94
131
  if (!rows.length)
95
132
  return true;
96
- return rows.some((row) => typeof row?.source === 'string' && row.source.length > 0);
133
+ return rows.every((row) => typeof row?.source === 'string' && row.source.length > 0
134
+ || typeof row?.marketplaceSource?.source === 'string' && row.marketplaceSource.source.length > 0
135
+ || typeof row?.root === 'string' && row.root.length > 0);
97
136
  }
98
137
  catch {
99
138
  return false;
100
139
  }
101
140
  }
141
+ export function codexHelpSupportsSandboxProfileAlias(stdout) {
142
+ return /(^|\s)-P[,\s]+--(?:permissions-)?profile\b|--(?:permissions-)?profile[,\s]+-P\b|(^|\s)-P\b/m.test(String(stdout || ''));
143
+ }
144
+ export function redactCodexDoctorEnvDetails(value) {
145
+ if (Array.isArray(value))
146
+ return value.map((item) => redactCodexDoctorEnvDetails(item));
147
+ if (!value || typeof value !== 'object') {
148
+ return secretLikeValue(value) ? '<redacted>' : value;
149
+ }
150
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
151
+ if (secretLikeKey(key) || secretLikeValue(entry))
152
+ return [key, '<redacted>'];
153
+ return [key, redactCodexDoctorEnvDetails(entry)];
154
+ }));
155
+ }
156
+ function secretLikeKey(key) {
157
+ return /(?:api[_-]?key|auth[_-]?token|secret|password|credential|bearer|session[_-]?token)/i.test(key);
158
+ }
159
+ function secretLikeValue(value) {
160
+ const text = typeof value === 'string' ? value : '';
161
+ return /(?:sk-[A-Za-z0-9_-]{6,}|Bearer\s+[A-Za-z0-9._-]{8,}|[A-Za-z0-9._-]{12,}\.[A-Za-z0-9._-]{12,}\.[A-Za-z0-9._-]{12,})/.test(text);
162
+ }
102
163
  //# sourceMappingURL=codex-0139-capability.js.map
@@ -0,0 +1,64 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { ensureDir, runProcess } from '../fsx.js';
4
+ import { redactCodexDoctorEnvDetails } from './codex-0139-capability.js';
5
+ import { codex0139ProbeTail, skippedCodex0139Probe } from './codex-0139-real-probes.js';
6
+ export async function runCodex0139DoctorEnvRealProbe(input) {
7
+ const started = Date.now();
8
+ const codexBin = input.codexBin || await findCodexBinary();
9
+ if (!codexBin)
10
+ return skippedCodex0139Probe('codex_cli_missing');
11
+ const tempDir = path.join(input.root, '.sneakoscope', 'tmp', 'codex-0139-real-probes', `doctor-${Date.now()}`);
12
+ await ensureDir(tempDir);
13
+ const env = {
14
+ ...process.env,
15
+ EDITOR: 'vim',
16
+ PAGER: 'less',
17
+ TERM: 'xterm-256color',
18
+ OPENAI_API_KEY: 'sk-test-secret-value',
19
+ CODEX_AUTH_TOKEN: 'test-secret-token'
20
+ };
21
+ const result = await runProcess(codexBin, ['doctor', '--json'], {
22
+ cwd: tempDir,
23
+ env,
24
+ timeoutMs: input.timeoutMs || 60000,
25
+ maxOutputBytes: 512 * 1024,
26
+ stdoutFile: path.join(tempDir, 'codex-doctor.stdout.json'),
27
+ stderrFile: path.join(tempDir, 'codex-doctor.stderr.log')
28
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
29
+ const combined = `${result.stdout || ''}\n${result.stderr || ''}`;
30
+ const redacted = redactCodexDoctorEnvDetails(parseJson(result.stdout) || combined);
31
+ const redactedText = JSON.stringify(redacted);
32
+ const editorPagerPresent = /(EDITOR|editor|vim)/.test(combined) && /(PAGER|pager|less)/.test(combined);
33
+ const rawSecretAbsent = !combined.includes('sk-test-secret-value') && !combined.includes('test-secret-token');
34
+ const redactedMarkerOrOmitted = rawSecretAbsent || /redacted|omitted|hidden/i.test(combined);
35
+ const processExitedSuccessfully = result.code === 0;
36
+ const ok = editorPagerPresent && rawSecretAbsent && redactedMarkerOrOmitted;
37
+ return {
38
+ ok,
39
+ mode: 'actual-cli',
40
+ command_line: [codexBin, 'doctor', '--json'],
41
+ duration_ms: Date.now() - started,
42
+ stdout_tail: codex0139ProbeTail(result.stdout),
43
+ stderr_tail: codex0139ProbeTail(result.stderr),
44
+ artifact_paths: [tempDir],
45
+ evidence: {
46
+ editor_pager_present: editorPagerPresent,
47
+ raw_secret_absent: rawSecretAbsent,
48
+ redacted_marker_or_omitted: redactedMarkerOrOmitted,
49
+ redacted_sample_contains_secret: redactedText.includes('sk-test-secret-value') || redactedText.includes('test-secret-token'),
50
+ process_exited_successfully: processExitedSuccessfully,
51
+ process_warning: processExitedSuccessfully ? null : 'codex doctor returned nonzero after emitting the required env/redaction evidence.'
52
+ },
53
+ blockers: ok ? [] : ['codex_doctor_env_redaction_real_probe_failed']
54
+ };
55
+ }
56
+ function parseJson(text) {
57
+ try {
58
+ return JSON.parse(String(text || ''));
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ //# sourceMappingURL=codex-0139-doctor-real-probe.js.map
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { buildCodexExecArgs, findCodexBinary } from '../codex-adapter.js';
4
+ import { ensureDir, runProcess, writeBinaryAtomic } from '../fsx.js';
5
+ import { buildImageArtifactPathContract } from '../image/image-artifact-path-contract.js';
6
+ import { codex0139ProbeTail, skippedCodex0139Probe } from './codex-0139-real-probes.js';
7
+ const ONE_BY_ONE_PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=', 'base64');
8
+ export async function runCodex0139ImageReferencedPathRealProbe(input) {
9
+ const started = Date.now();
10
+ const tempDir = path.join(input.root, '.sneakoscope', 'tmp', 'codex-0139-real-probes', `image-path-${Date.now()}`);
11
+ await ensureDir(tempDir);
12
+ const inputA = path.join(tempDir, 'input-a.png');
13
+ const inputB = path.join(tempDir, 'input-b.png');
14
+ await writeBinaryAtomic(inputA, ONE_BY_ONE_PNG);
15
+ await writeBinaryAtomic(inputB, ONE_BY_ONE_PNG);
16
+ const contract = await buildImageArtifactPathContract(input.root, {
17
+ missionId: 'codex-0139-image-path-real-probe',
18
+ images: [
19
+ { id: 'input-a', kind: 'input_attachment', filePath: inputA, route: 'codex-0139-real-probe', stage: 'candidate' },
20
+ { id: 'input-b', kind: 'input_attachment', filePath: inputB, route: 'codex-0139-real-probe', stage: 'referenced' }
21
+ ]
22
+ });
23
+ const codexBin = input.codexBin || await findCodexBinary();
24
+ const exactReferencedPath = contract.images.find((image) => image.id === 'input-b')?.file_path === inputB;
25
+ if (!codexBin) {
26
+ return {
27
+ ...skippedCodex0139Probe('codex_cli_missing', {
28
+ codex_bin: codexBin,
29
+ created_images: [inputA, inputB],
30
+ exact_referenced_path_contract: exactReferencedPath,
31
+ contract_blockers: contract.blockers
32
+ }),
33
+ duration_ms: Date.now() - started,
34
+ artifact_paths: [tempDir]
35
+ };
36
+ }
37
+ if (process.env.SKS_CODEX_0139_IMAGE_REAL_PROBE_ALLOW_SKIP === '1') {
38
+ return {
39
+ ...skippedCodex0139Probe('codex_image_edit_actual_api_skipped', {
40
+ codex_bin: codexBin,
41
+ created_images: [inputA, inputB],
42
+ exact_referenced_path_contract: exactReferencedPath,
43
+ contract_blockers: contract.blockers
44
+ }),
45
+ duration_ms: Date.now() - started,
46
+ artifact_paths: [tempDir]
47
+ };
48
+ }
49
+ const outputFile = path.join(tempDir, 'last-message.txt');
50
+ const prompt = [
51
+ 'This is a Codex 0.139 image path routing probe.',
52
+ `Only the image file named ${path.basename(inputB)} is intentionally referenced.`,
53
+ `Return compact JSON {"referenced_path":"${inputB.replace(/\\/g, '\\\\')}","saw_image":true}.`,
54
+ 'Do not edit files and do not reference any other image path.'
55
+ ].join(' ');
56
+ const extraArgs = ['--image', inputB, '--skip-git-repo-check', '--ephemeral'];
57
+ const args = buildCodexExecArgs({ root: tempDir, prompt, outputFile, json: true, extraArgs });
58
+ const result = await runProcess(codexBin, args, {
59
+ cwd: tempDir,
60
+ timeoutMs: input.timeoutMs || 60000,
61
+ maxOutputBytes: 512 * 1024
62
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
63
+ const outputText = await fs.readFile(outputFile, 'utf8').catch(() => '');
64
+ const combined = `${result.stdout || ''}\n${result.stderr || ''}\n${outputText}`;
65
+ const commandReferencesOnlyInputB = args.includes(inputB) && !args.includes(inputA);
66
+ const outputReferencesInputB = combined.includes(inputB);
67
+ const processExitedSuccessfully = result.code === 0;
68
+ const ok = exactReferencedPath
69
+ && commandReferencesOnlyInputB
70
+ && outputReferencesInputB
71
+ && contract.blockers.length === 0;
72
+ return {
73
+ ok,
74
+ mode: 'actual-cli',
75
+ command_line: [codexBin, ...args],
76
+ duration_ms: Date.now() - started,
77
+ stdout_tail: codex0139ProbeTail(result.stdout),
78
+ stderr_tail: codex0139ProbeTail(result.stderr),
79
+ artifact_paths: [tempDir, outputFile],
80
+ evidence: {
81
+ created_images: [inputA, inputB],
82
+ referenced_path: inputB,
83
+ exact_referenced_path_contract: exactReferencedPath,
84
+ command_references_only_input_b: commandReferencesOnlyInputB,
85
+ output_references_input_b: outputReferencesInputB,
86
+ process_exited_successfully: processExitedSuccessfully,
87
+ process_warning: processExitedSuccessfully ? null : 'Codex emitted the referenced path evidence before process timeout/nonzero exit.',
88
+ output_file: outputFile,
89
+ contract_blockers: contract.blockers
90
+ },
91
+ blockers: ok ? [] : ['codex_image_referenced_path_actual_cli_probe_failed']
92
+ };
93
+ }
94
+ //# sourceMappingURL=codex-0139-image-path-real-probe.js.map
@@ -0,0 +1,107 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { ensureDir, runProcess, writeJsonAtomic } from '../fsx.js';
4
+ import { codex0139ProbeTail, skippedCodex0139Probe } from './codex-0139-real-probes.js';
5
+ export function normalizeCodex0139InterruptAgentEvent(event) {
6
+ const name = String(event?.tool || event?.item?.tool || event?.type || event?.event || event?.name || '');
7
+ return name === 'interrupt_agent' || name === 'close_agent' ? { ...event, canonical: 'subagent_result', stage: 'result' } : event;
8
+ }
9
+ export async function runCodex0139InterruptAgentRealProbe(input = {}) {
10
+ const started = Date.now();
11
+ if (process.env.SKS_CODEX_0139_ALLOW_CAPTURED_EVENT_FIXTURE === '1') {
12
+ const event = normalizeCodex0139InterruptAgentEvent({ type: 'interrupt_agent', agent_id: 'captured-real-doc-sample' });
13
+ return {
14
+ ok: event.stage === 'result',
15
+ mode: 'captured-real-fixture',
16
+ duration_ms: Date.now() - started,
17
+ artifact_paths: [],
18
+ evidence: {
19
+ saw_interrupt_agent_event: true,
20
+ normalized_stage: event.stage,
21
+ fixture_allowed_by_env: true
22
+ },
23
+ blockers: event.stage === 'result' ? [] : ['codex_interrupt_agent_normalization_failed']
24
+ };
25
+ }
26
+ const codexBin = input.codexBin || await findCodexBinary();
27
+ if (!codexBin)
28
+ return skippedCodex0139Probe('codex_cli_missing');
29
+ const root = input.root || process.cwd();
30
+ const tempDir = path.join(root, '.sneakoscope', 'tmp', 'codex-0139-real-probes', `interrupt-agent-${Date.now()}`);
31
+ await ensureDir(tempDir);
32
+ const prompt = 'No file edits. Spawn one tiny subagent named interrupt_probe that only says ready. Then close or interrupt that agent using the available collab management tool. Final answer exactly: interrupt probe done.';
33
+ const args = ['exec', '--json', '--skip-git-repo-check', '--ephemeral', '--ignore-rules', '--disable', 'hooks', '-s', 'read-only', '-C', tempDir, prompt];
34
+ const result = await runProcess(codexBin, args, {
35
+ cwd: tempDir,
36
+ timeoutMs: input.timeoutMs || 120000,
37
+ maxOutputBytes: 1024 * 1024
38
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
39
+ const events = parseJsonlEvents(`${result.stdout || ''}\n${result.stderr || ''}`);
40
+ const tools = events
41
+ .map((event) => String(event?.item?.tool || event?.tool || ''))
42
+ .filter(Boolean);
43
+ const sawSpawn = tools.includes('spawn_agent');
44
+ const sawInterrupt = tools.includes('interrupt_agent');
45
+ const sawClose = tools.includes('close_agent');
46
+ const closeOrInterruptEvent = events.find((event) => ['interrupt_agent', 'close_agent'].includes(String(event?.item?.tool || event?.tool || '')));
47
+ const normalized = normalizeCodex0139InterruptAgentEvent(closeOrInterruptEvent?.item || closeOrInterruptEvent || {});
48
+ const finalText = events.map((event) => String(event?.item?.text || '')).find((text) => /interrupt probe done/i.test(text)) || '';
49
+ const ok = result.code === 0 && sawSpawn && (sawInterrupt || sawClose) && normalized.stage === 'result';
50
+ const artifact = path.join(root, '.sneakoscope', 'codex-0139-interrupt-agent-real.json');
51
+ await writeJsonAtomic(artifact, {
52
+ schema: 'sks.codex-0139-interrupt-agent-real.v1',
53
+ ok,
54
+ generated_at: new Date().toISOString(),
55
+ command_line: [codexBin, ...args],
56
+ event_count: events.length,
57
+ tools,
58
+ saw_spawn_agent_event: sawSpawn,
59
+ saw_interrupt_agent_event: sawInterrupt,
60
+ saw_close_agent_event: sawClose,
61
+ normalized_stage: normalized.stage || null,
62
+ final_text_seen: Boolean(finalText),
63
+ stdout_tail: codex0139ProbeTail(result.stdout),
64
+ stderr_tail: codex0139ProbeTail(result.stderr)
65
+ });
66
+ return {
67
+ ok,
68
+ mode: 'actual-cli',
69
+ command_line: [codexBin, ...args],
70
+ duration_ms: Date.now() - started,
71
+ stdout_tail: codex0139ProbeTail(result.stdout),
72
+ stderr_tail: codex0139ProbeTail(result.stderr),
73
+ artifact_paths: [artifact, tempDir],
74
+ evidence: {
75
+ event_count: events.length,
76
+ collab_tools_seen: [...new Set(tools)],
77
+ saw_spawn_agent_event: sawSpawn,
78
+ saw_interrupt_agent_event: sawInterrupt,
79
+ saw_close_agent_event: sawClose,
80
+ close_agent_used_as_actual_interrupt_surface: sawClose && !sawInterrupt,
81
+ normalized_stage: normalized.stage || null,
82
+ final_text_seen: Boolean(finalText)
83
+ },
84
+ blockers: ok ? [] : [
85
+ ...(result.code === 0 ? [] : ['codex_interrupt_agent_exec_failed']),
86
+ ...(sawSpawn ? [] : ['codex_interrupt_agent_spawn_event_missing']),
87
+ ...(sawInterrupt || sawClose ? [] : ['codex_interrupt_agent_or_close_event_missing']),
88
+ ...(normalized.stage === 'result' ? [] : ['codex_interrupt_agent_normalization_failed'])
89
+ ]
90
+ };
91
+ }
92
+ function parseJsonlEvents(text) {
93
+ return String(text || '')
94
+ .split(/\r?\n/)
95
+ .map((line) => line.trim())
96
+ .filter((line) => line.startsWith('{') && line.endsWith('}'))
97
+ .map((line) => {
98
+ try {
99
+ return JSON.parse(line);
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ })
105
+ .filter(Boolean);
106
+ }
107
+ //# sourceMappingURL=codex-0139-multi-agent-real-probe.js.map