sneakoscope 3.0.3 → 3.1.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.
Files changed (61) 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/cli/command-registry.js +1 -0
  8. package/dist/cli/context7-command.js +29 -5
  9. package/dist/cli/install-helpers.js +15 -7
  10. package/dist/core/agents/runtime-proof-summary.js +4 -0
  11. package/dist/core/codex-control/codex-0139-capability.js +8 -3
  12. package/dist/core/codex-control/codex-0139-doctor-real-probe.js +64 -0
  13. package/dist/core/codex-control/codex-0139-image-path-real-probe.js +94 -0
  14. package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +107 -0
  15. package/dist/core/codex-control/codex-0139-plugin-real-probes.js +119 -0
  16. package/dist/core/codex-control/codex-0139-probe-runner.js +117 -0
  17. package/dist/core/codex-control/codex-0139-real-probe-summary.js +37 -0
  18. package/dist/core/codex-control/codex-0139-real-probes.js +74 -0
  19. package/dist/core/codex-control/codex-0139-rich-schema-real-probe.js +43 -0
  20. package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +79 -0
  21. package/dist/core/codex-control/codex-0139-web-search-probe.js +72 -0
  22. package/dist/core/commands/goal-command.js +19 -1
  23. package/dist/core/commands/loop-command.js +135 -0
  24. package/dist/core/doctor/codex-0139-doctor.js +16 -0
  25. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  26. package/dist/core/fsx.js +25 -1
  27. package/dist/core/init.js +6 -1
  28. package/dist/core/loops/goal-to-loop-compat.js +23 -0
  29. package/dist/core/loops/loop-artifacts.js +41 -0
  30. package/dist/core/loops/loop-decomposer.js +56 -0
  31. package/dist/core/loops/loop-finalizer.js +28 -0
  32. package/dist/core/loops/loop-gate-ladder.js +16 -0
  33. package/dist/core/loops/loop-gate-runner.js +29 -0
  34. package/dist/core/loops/loop-gate-selector.js +52 -0
  35. package/dist/core/loops/loop-iteration-runner.js +2 -0
  36. package/dist/core/loops/loop-lease.js +76 -0
  37. package/dist/core/loops/loop-observability.js +19 -0
  38. package/dist/core/loops/loop-owner-inference.js +57 -0
  39. package/dist/core/loops/loop-owner-ledger.js +2 -0
  40. package/dist/core/loops/loop-planner.js +139 -0
  41. package/dist/core/loops/loop-proof-summary.js +10 -0
  42. package/dist/core/loops/loop-proof.js +2 -0
  43. package/dist/core/loops/loop-risk-classifier.js +42 -0
  44. package/dist/core/loops/loop-runtime.js +159 -0
  45. package/dist/core/loops/loop-scheduler.js +60 -0
  46. package/dist/core/loops/loop-schema.js +63 -0
  47. package/dist/core/loops/loop-state.js +61 -0
  48. package/dist/core/naruto/naruto-loop-mesh.js +33 -0
  49. package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
  50. package/dist/core/pipeline-internals/runtime-core.js +82 -2
  51. package/dist/core/version.js +1 -1
  52. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -2
  53. package/dist/core/zellij/zellij-slot-pane-renderer.js +2 -0
  54. package/dist/scripts/github-release-body-helper.js +3 -1
  55. package/dist/scripts/loop-directive-check-lib.js +165 -0
  56. package/package.json +47 -3
  57. package/schemas/codex/codex-0139-real-probe-result.schema.json +85 -0
  58. package/schemas/loops/loop-node.schema.json +21 -0
  59. package/schemas/loops/loop-plan.schema.json +21 -0
  60. package/schemas/loops/loop-proof.schema.json +20 -0
  61. package/schemas/loops/loop-state.schema.json +19 -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.3** 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 optional real probes for code-mode web search markers, preserved `oneOf`/`allOf` tool schemas, plugin marketplace `source`, the `-P` profile alias, the multi-agent v2 `interrupt_agent` rename, 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).
38
+ SKS **3.1.0** 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.3"
79
+ version = "3.1.0"
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.3"
3
+ version = "3.1.0"
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.3"),
7
+ Some("--version") => println!("sks-rs 3.1.0"),
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.3",
5
- "source_digest": "4630d65c17e3297a1a79b5b96be0b849ee4ada4ac1ff010a4e024b06d8b7b750",
6
- "source_file_count": 2304,
7
- "built_at_source_time": 1781142991546
4
+ "package_version": "3.1.0",
5
+ "source_digest": "0eed1c5f95e3d3e67d263680e28b27bfd1dabe232ff7265720a57d7d377252e8",
6
+ "source_file_count": 2392,
7
+ "built_at_source_time": 1781256416238
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.3';
2
+ const FAST_PACKAGE_VERSION = '3.1.0';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -123,6 +123,7 @@ export const COMMANDS = {
123
123
  agent: entry('beta', 'Run native multi-session agent missions', 'dist/core/commands/agent-command.js', argsCommand(() => import('../core/commands/agent-command.js'), 'agentCommand', 'dist/core/commands/agent-command.js')),
124
124
  'with-local-llm': entry('beta', 'Enable or inspect local Ollama worker backend', 'dist/core/commands/local-model-command.js', argsCommand(() => import('../core/commands/local-model-command.js'), 'localModelCommand', 'dist/core/commands/local-model-command.js')),
125
125
  naruto: entry('labs', 'Run $Naruto shadow-clone swarm (up to 100 parallel sessions)', 'dist/core/commands/naruto-command.js', argsCommand(() => import('../core/commands/naruto-command.js'), 'narutoCommand', 'dist/core/commands/naruto-command.js')),
126
+ loop: entry('labs', 'Dynamic Loop Runtime: plan/run/status/proof loop graphs.', 'dist/core/commands/loop-command.js', subcommand(() => import('../core/commands/loop-command.js'), 'loopCommand', 'dist/core/commands/loop-command.js', 'help')),
126
127
  'qa-loop': entry('beta', 'Run QA loop missions', 'dist/core/commands/qa-loop-command.js', subcommand(() => import('../core/commands/qa-loop-command.js'), 'qaLoopCommand', 'dist/core/commands/qa-loop-command.js')),
127
128
  research: entry('labs', 'Run research missions', 'dist/core/commands/research-command.js', subcommand(() => import('../core/commands/research-command.js'), 'researchCommand', 'dist/core/commands/research-command.js')),
128
129
  autoresearch: entry('labs', 'Alias for research/autoresearch route', 'dist/core/commands/autoresearch-command.js', subcommand(() => import('../core/commands/autoresearch-command.js'), 'autoresearchCommand', 'dist/core/commands/autoresearch-command.js', 'status')),
@@ -3,7 +3,7 @@ import { getCodexInfo } from '../core/codex-adapter.js';
3
3
  import { context7Docs, context7Resolve, context7Text, context7Tools } from '../core/context7-client.js';
4
4
  import { context7Evidence, recordContext7Evidence } from '../core/pipeline.js';
5
5
  import { stateFile } from '../core/mission.js';
6
- import { checkContext7, ensureProjectContext7Config } from './install-helpers.js';
6
+ import { checkContext7, context7GlobalMcpStatus, ensureProjectContext7Config } from './install-helpers.js';
7
7
  const flag = (args, name) => args.includes(name);
8
8
  export async function context7Command(sub = 'check', args = []) {
9
9
  const action = sub || 'check';
@@ -92,9 +92,24 @@ export async function context7Command(sub = 'check', args = []) {
92
92
  timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
93
93
  });
94
94
  const state = { ...(await readJson(stateFile(root), {})), mission_id: missionId };
95
- await recordContext7Evidence(root, state, { tool_name: 'resolve-library-id', library: libraryNameOrId, library_id: result.library_id, source: result.resolve ? 'sks context7 evidence' : 'sks context7 evidence explicit-library-id' });
95
+ const evidenceQuery = readOption(args, '--query', readOption(args, '--topic', libraryNameOrId));
96
+ const evidenceTopic = readOption(args, '--topic', libraryNameOrId);
97
+ await recordContext7Evidence(root, state, {
98
+ tool_name: 'resolve-library-id',
99
+ library: libraryNameOrId,
100
+ library_id: result.library_id,
101
+ query: evidenceQuery,
102
+ source: result.resolve ? 'sks context7 evidence' : 'sks context7 evidence explicit-library-id'
103
+ });
96
104
  if (result.docs_tool) {
97
- await recordContext7Evidence(root, state, { tool_name: result.docs_tool, library_id: result.library_id, source: 'sks context7 evidence' });
105
+ await recordContext7Evidence(root, state, {
106
+ tool_name: result.docs_tool,
107
+ library_id: result.library_id,
108
+ query: evidenceQuery,
109
+ topic: evidenceTopic,
110
+ tokens: readNumberOption(args, '--tokens', 2000),
111
+ source: 'sks context7 evidence'
112
+ });
98
113
  }
99
114
  const evidence = await context7Evidence(root, state);
100
115
  const out = { ...result, mission_id: missionId, evidence };
@@ -126,12 +141,21 @@ export async function context7Command(sub = 'check', args = []) {
126
141
  const codex = await getCodexInfo();
127
142
  if (!codex.bin)
128
143
  throw new Error('Codex CLI missing. Install separately: npm i -g @openai/codex, or set SKS_CODEX_BIN.');
144
+ const env = { ...process.env };
145
+ env.CODEX_LB_API_KEY = '';
146
+ const existing = await context7GlobalMcpStatus(codex.bin, env);
147
+ if (existing.present) {
148
+ if (flag(args, '--json'))
149
+ return console.log(JSON.stringify({ changed: false, status: 'present', codex_mcp_list: existing }, null, 2));
150
+ console.log('Context7 global MCP already configured; existing entry preserved.');
151
+ return;
152
+ }
129
153
  const cmdArgs = transport === 'remote'
130
154
  ? ['mcp', 'add', 'context7', '--url', 'https://mcp.context7.com/mcp']
131
155
  : ['mcp', 'add', 'context7', '--', 'npx', '-y', '@upstash/context7-mcp@latest'];
132
- const result = await runProcess(codex.bin, cmdArgs, { timeoutMs: 30000, maxOutputBytes: 64 * 1024 });
156
+ const result = await runProcess(codex.bin, cmdArgs, { env, timeoutMs: 30000, maxOutputBytes: 64 * 1024 });
133
157
  if (flag(args, '--json'))
134
- return console.log(JSON.stringify({ command: `${codex.bin} ${cmdArgs.join(' ')}`, result }, null, 2));
158
+ return console.log(JSON.stringify({ changed: result.code === 0, status: result.code === 0 ? 'installed' : 'failed', command: `${codex.bin} ${cmdArgs.join(' ')}`, result }, null, 2));
135
159
  if (result.code !== 0)
136
160
  throw new Error(result.stderr || result.stdout || 'codex mcp add failed');
137
161
  console.log('Context7 global MCP configured.');
@@ -2019,14 +2019,26 @@ async function ensureGlobalContext7DuringInstall() {
2019
2019
  if (!codex.bin)
2020
2020
  return { status: 'codex_missing' };
2021
2021
  const env = withoutSecretEnv(['CODEX_LB_API_KEY']);
2022
- const list = await runProcess(codex.bin, ['mcp', 'list'], { env, timeoutMs: 8000, maxOutputBytes: 32 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
2023
- if (list.code === 0 && /context7/i.test(`${list.stdout}\n${list.stderr}`))
2022
+ const existing = await context7GlobalMcpStatus(codex.bin, env);
2023
+ if (existing.present)
2024
2024
  return { status: 'present' };
2025
2025
  const add = await runProcess(codex.bin, ['mcp', 'add', 'context7', '--', 'npx', '-y', '@upstash/context7-mcp@latest'], { env, timeoutMs: 30000, maxOutputBytes: 64 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
2026
2026
  if (add.code === 0)
2027
2027
  return { status: 'installed' };
2028
2028
  return { status: 'failed', error: `${add.stderr || add.stdout || 'codex mcp add failed'}`.trim() };
2029
2029
  }
2030
+ export async function context7GlobalMcpStatus(codexBin, env = process.env) {
2031
+ const list = await runProcess(codexBin, ['mcp', 'list'], { env, timeoutMs: 8000, maxOutputBytes: 32 * 1024 })
2032
+ .catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
2033
+ const output = `${list.stdout || ''}\n${list.stderr || ''}`;
2034
+ return {
2035
+ checked: true,
2036
+ ok: list.code === 0,
2037
+ present: list.code === 0 && /context7/i.test(output),
2038
+ stdout: list.stdout || '',
2039
+ stderr: list.stderr || ''
2040
+ };
2041
+ }
2030
2042
  function withoutSecretEnv(keys = []) {
2031
2043
  const env = { ...process.env };
2032
2044
  for (const key of keys)
@@ -2348,11 +2360,7 @@ export async function ensureProjectContext7Config(root, transport = 'local') {
2348
2360
  const block = context7ConfigToml(transport).trim();
2349
2361
  const existingBlock = /(^|\n)\[mcp_servers\.context7\]\n[\s\S]*?(?=\n\[[^\]]+\]|\s*$)/;
2350
2362
  if (existingBlock.test(current)) {
2351
- const next = current.replace(existingBlock, `$1${block}\n`);
2352
- if (next === current)
2353
- return false;
2354
- await writeTextAtomic(configPath, next.endsWith('\n') ? next : `${next}\n`);
2355
- return true;
2363
+ return false;
2356
2364
  }
2357
2365
  if (hasContext7ConfigText(current))
2358
2366
  return false;
@@ -3,6 +3,7 @@ import { findLatestMission, missionDir } from '../mission.js';
3
3
  import { readJson, writeJsonAtomic } from '../fsx.js';
4
4
  import { readAgentMessageBus } from './agent-message-bus.js';
5
5
  import { buildZellijWorkerPaneSummary } from '../zellij/zellij-worker-pane-summary.js';
6
+ import { readLoopGraphProof, summarizeLoopGraphProof } from '../loops/loop-observability.js';
6
7
  export const RUNTIME_PROOF_SUMMARY_SCHEMA = 'sks.runtime-proof-summary.v1';
7
8
  export async function buildRuntimeProofSummary(root, missionIdInput = 'latest', opts = {}) {
8
9
  const missionId = missionIdInput === 'latest' ? await findLatestMission(root) : missionIdInput;
@@ -18,6 +19,7 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
18
19
  const messagesAll = await readAgentMessageBus(root, missionId, { max: 500 });
19
20
  const recentMessages = await readAgentMessageBus(root, missionId, { max: opts.maxMessages || 8 });
20
21
  const zellijSummary = await buildZellijWorkerPaneSummary(root, missionId).catch(() => null);
22
+ const loopSummary = summarizeLoopGraphProof(await readLoopGraphProof(root, missionId).catch(() => null));
21
23
  const failedMessages = messagesAll.filter((row) => row.event_type === 'worker_failed');
22
24
  const errorMessages = messagesAll.filter((row) => row.level === 'error');
23
25
  const telemetryAgeMs = telemetry?.updated_at ? Math.max(0, Date.now() - Date.parse(telemetry.updated_at)) : Number.MAX_SAFE_INTEGER;
@@ -73,6 +75,7 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest',
73
75
  pane_lock_held_p95_ms: Number(zellijSummary?.pane_lock_held_p95_ms || 0),
74
76
  duplicate_slot_anchor_count: Number(zellijSummary?.duplicate_slot_anchor_count || 0)
75
77
  },
78
+ loops: loopSummary,
76
79
  blockers
77
80
  };
78
81
  await writeJsonAtomic(path.join(agentsDir, 'runtime-proof-summary.json'), summary);
@@ -91,6 +94,7 @@ export function renderRuntimeProofSummary(summary) {
91
94
  `Stack fallback: ${summary.zellij.stacked_fallback_count}`,
92
95
  `Pane lock wait p95: ${summary.zellij.pane_lock_wait_p95_ms}ms`,
93
96
  `SLOTS anchors: ${summary.zellij.duplicate_slot_anchor_count}`,
97
+ `Loops: ${summary.loops.total} total / ${summary.loops.completed} done / ${summary.loops.blocked} blocked / ${summary.loops.speedup_ratio}x`,
94
98
  ...(summary.messages.recent.length ? [
95
99
  'Recent worker messages:',
96
100
  ...summary.messages.recent.map((row) => ` ${messageStatusLabel(row)} ${row.slot_id || row.worker_id}: ${row.message}`)
@@ -53,6 +53,7 @@ export async function detectCodex0139Capability(input = {}) {
53
53
  version_text: versionText || null,
54
54
  parsed_version: parsed,
55
55
  supports_code_mode_web_search: codeSearchOk,
56
+ supports_code_mode_web_search_real_verified: false,
56
57
  supports_rich_tool_schemas: richSchemaOk,
57
58
  supports_doctor_env_details: doctorEnvOk,
58
59
  supports_marketplace_source_field: marketplaceOk,
@@ -111,7 +112,9 @@ async function probeCodex0139Features(codexBin, opts = {}) {
111
112
  const marketplace = await runProcess(codexBin, ['plugin', 'marketplace', 'list', '--json'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
112
113
  const marketplaceListJson = marketplace.code === 0 && marketplaceSourcesPresent(marketplace.stdout) ? 'passed' : 'failed';
113
114
  const help = await runProcess(codexBin, ['--help'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
114
- 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);
115
118
  return {
116
119
  marketplace_list_json: marketplaceListJson,
117
120
  sandbox_profile_alias: aliasOk ? 'passed' : 'failed',
@@ -127,14 +130,16 @@ export function marketplaceSourcesPresent(stdout) {
127
130
  const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : Array.isArray(parsed?.items) ? parsed.items : [];
128
131
  if (!rows.length)
129
132
  return true;
130
- return rows.every((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);
131
136
  }
132
137
  catch {
133
138
  return false;
134
139
  }
135
140
  }
136
141
  export function codexHelpSupportsSandboxProfileAlias(stdout) {
137
- return /(^|\s)-P[,\s]+--profile\b|--profile[,\s]+-P\b|(^|\s)-P\b/m.test(String(stdout || ''));
142
+ return /(^|\s)-P[,\s]+--(?:permissions-)?profile\b|--(?:permissions-)?profile[,\s]+-P\b|(^|\s)-P\b/m.test(String(stdout || ''));
138
143
  }
139
144
  export function redactCodexDoctorEnvDetails(value) {
140
145
  if (Array.isArray(value))
@@ -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
@@ -0,0 +1,119 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { ensureDir, runProcess, writeJsonAtomic } from '../fsx.js';
4
+ import { marketplaceSourcesPresent } from './codex-0139-capability.js';
5
+ import { codex0139ProbeTail, skippedCodex0139Probe } from './codex-0139-real-probes.js';
6
+ export async function runCodex0139MarketplaceSourceRealProbe(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', `marketplace-${Date.now()}`);
12
+ await ensureDir(tempDir);
13
+ const result = await runProcess(codexBin, ['plugin', 'marketplace', 'list', '--json'], {
14
+ cwd: tempDir,
15
+ timeoutMs: input.timeoutMs || 60000,
16
+ maxOutputBytes: 512 * 1024
17
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
18
+ const rows = parseRows(result.stdout);
19
+ const missingSourceRows = rows
20
+ .map((row, index) => ({ index, name: String(row?.name || row?.id || row?.pluginId || `row-${index + 1}`), keys: Object.keys(row || {}), root: typeof row?.root === 'string' ? row.root : null }))
21
+ .filter((row, index) => !rowHasMarketplaceSourceLocator(rows[index]));
22
+ const ok = result.code === 0 && marketplaceSourcesPresent(result.stdout);
23
+ const artifact = path.join(input.root, '.sneakoscope', 'codex-0139-plugin-marketplace-real.json');
24
+ await writeJsonAtomic(artifact, {
25
+ schema: 'sks.codex-0139-plugin-marketplace-real.v1',
26
+ ok,
27
+ generated_at: new Date().toISOString(),
28
+ command_line: [codexBin, 'plugin', 'marketplace', 'list', '--json'],
29
+ empty_marketplace_list: rows.length === 0,
30
+ row_count: rows.length,
31
+ rows_have_source: rows.every(rowHasMarketplaceSource),
32
+ rows_have_source_locator: rows.every(rowHasMarketplaceSourceLocator),
33
+ missing_source_rows: missingSourceRows,
34
+ stdout_tail: codex0139ProbeTail(result.stdout),
35
+ stderr_tail: codex0139ProbeTail(result.stderr)
36
+ });
37
+ return {
38
+ ok,
39
+ mode: 'actual-cli',
40
+ command_line: [codexBin, 'plugin', 'marketplace', 'list', '--json'],
41
+ duration_ms: Date.now() - started,
42
+ stdout_tail: codex0139ProbeTail(result.stdout),
43
+ stderr_tail: codex0139ProbeTail(result.stderr),
44
+ artifact_paths: [artifact],
45
+ evidence: {
46
+ empty_marketplace_list: rows.length === 0,
47
+ row_count: rows.length,
48
+ rows_have_source: rows.every(rowHasMarketplaceSource),
49
+ rows_have_source_locator: rows.every(rowHasMarketplaceSourceLocator),
50
+ missing_source_rows: missingSourceRows
51
+ },
52
+ blockers: ok ? [] : ['codex_plugin_marketplace_source_real_probe_failed']
53
+ };
54
+ }
55
+ function rowHasMarketplaceSource(row) {
56
+ return typeof row?.source === 'string' && row.source.length > 0
57
+ || typeof row?.marketplaceSource?.source === 'string' && row.marketplaceSource.source.length > 0;
58
+ }
59
+ function rowHasMarketplaceSourceLocator(row) {
60
+ return rowHasMarketplaceSource(row) || typeof row?.root === 'string' && row.root.length > 0;
61
+ }
62
+ export async function runCodex0139PluginCacheRealProbe(input) {
63
+ const started = Date.now();
64
+ const codexBin = input.codexBin || await findCodexBinary();
65
+ if (!codexBin)
66
+ return skippedCodex0139Probe('codex_cli_missing');
67
+ const tempDir = path.join(input.root, '.sneakoscope', 'tmp', 'codex-0139-real-probes', `plugin-cache-${Date.now()}`);
68
+ await ensureDir(tempDir);
69
+ const firstStarted = Date.now();
70
+ const first = await runProcess(codexBin, ['plugin', 'list', '--json'], {
71
+ cwd: tempDir,
72
+ timeoutMs: input.timeoutMs || 60000,
73
+ maxOutputBytes: 512 * 1024
74
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
75
+ const firstMs = Date.now() - firstStarted;
76
+ const secondStarted = Date.now();
77
+ const second = await runProcess(codexBin, ['plugin', 'list', '--json'], {
78
+ cwd: tempDir,
79
+ timeoutMs: input.timeoutMs || 60000,
80
+ maxOutputBytes: 512 * 1024
81
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
82
+ const secondMs = Date.now() - secondStarted;
83
+ const marker = /cache|cached|catalog|remote/i.test(`${first.stdout}\n${second.stdout}\n${first.stderr}\n${second.stderr}`);
84
+ const secondNotMuchSlower = secondMs <= Math.max(firstMs * 2, firstMs + 1000);
85
+ const ok = first.code === 0 && second.code === 0 && secondNotMuchSlower;
86
+ return {
87
+ ok,
88
+ mode: 'actual-cli',
89
+ command_line: [codexBin, 'plugin', 'list', '--json'],
90
+ duration_ms: Date.now() - started,
91
+ stdout_tail: codex0139ProbeTail(`${first.stdout || ''}\n${second.stdout || ''}`),
92
+ stderr_tail: codex0139ProbeTail(`${first.stderr || ''}\n${second.stderr || ''}`),
93
+ artifact_paths: [tempDir],
94
+ evidence: {
95
+ first_duration_ms: firstMs,
96
+ second_duration_ms: secondMs,
97
+ second_not_slower_than_2x: secondNotMuchSlower,
98
+ cache_marker_seen: marker,
99
+ cache_marker_warning: marker ? null : 'cache marker unavailable; timing success accepted'
100
+ },
101
+ blockers: ok ? [] : ['codex_plugin_catalog_cache_real_probe_failed']
102
+ };
103
+ }
104
+ function parseRows(stdout) {
105
+ try {
106
+ const parsed = JSON.parse(String(stdout || ''));
107
+ if (Array.isArray(parsed))
108
+ return parsed;
109
+ if (Array.isArray(parsed?.marketplaces))
110
+ return parsed.marketplaces;
111
+ if (Array.isArray(parsed?.items))
112
+ return parsed.items;
113
+ return [];
114
+ }
115
+ catch {
116
+ return [];
117
+ }
118
+ }
119
+ //# sourceMappingURL=codex-0139-plugin-real-probes.js.map