sneakoscope 3.0.1 → 3.0.3

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 (30) 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/agent-message-bus.js +89 -2
  8. package/dist/core/agents/runtime-proof-summary.js +46 -2
  9. package/dist/core/codex/codex-cli-syntax-builder.js +4 -1
  10. package/dist/core/codex-control/codex-0139-capability.js +68 -12
  11. package/dist/core/codex-control/codex-multi-agent-event-normalizer.js +15 -0
  12. package/dist/core/codex-control/codex-tool-schema-fixtures.js +57 -0
  13. package/dist/core/commands/naruto-command.js +9 -4
  14. package/dist/core/fsx.js +1 -1
  15. package/dist/core/mcp/mcp-0-134-policy.js +3 -0
  16. package/dist/core/pipeline-internals/runtime-core.js +6 -1
  17. package/dist/core/release/release-cache-key.js +128 -0
  18. package/dist/core/release/release-gate-cache-v2.js +6 -7
  19. package/dist/core/release/release-proof-truth.js +63 -0
  20. package/dist/core/safety/side-effect-runtime-report.js +19 -4
  21. package/dist/core/version.js +1 -1
  22. package/dist/core/zellij/zellij-capability.js +41 -0
  23. package/dist/core/zellij/zellij-command.js +14 -2
  24. package/dist/core/zellij/zellij-fake-adapter.js +163 -0
  25. package/dist/core/zellij/zellij-update.js +28 -3
  26. package/dist/core/zellij/zellij-worker-pane-manager.js +116 -5
  27. package/dist/core/zellij/zellij-worker-pane-summary.js +65 -0
  28. package/dist/scripts/github-release-body-helper.js +63 -0
  29. package/dist/scripts/release-speed-summary.js +11 -0
  30. package/package.json +33 -2
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.1** tracks Codex CLI `rust-v0.139.0`: capability detection for code-mode web search, preserved `oneOf`/`allOf` tool schemas, plugin marketplace `source`/cached catalog, the `-P` sandbox profile alias, and the multi-agent v2 `interrupt_agent` rename (accepted alongside `close_agent` in cockpit event classification). See [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md).
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).
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.1"
79
+ version = "3.0.3"
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.1"
3
+ version = "3.0.3"
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.1"),
7
+ Some("--version") => println!("sks-rs 3.0.3"),
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.1",
5
- "source_digest": "5c97e638daf3eab1aeac1eaa644b771787a9d8cfd1a9756e7ab4e76a7aba523e",
6
- "source_file_count": 2265,
7
- "built_at_source_time": 1781080652355
4
+ "package_version": "3.0.3",
5
+ "source_digest": "4630d65c17e3297a1a79b5b96be0b849ee4ada4ac1ff010a4e024b06d8b7b750",
6
+ "source_file_count": 2304,
7
+ "built_at_source_time": 1781142991546
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.1';
2
+ const FAST_PACKAGE_VERSION = '3.0.3';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1,11 +1,21 @@
1
1
  import path from 'node:path';
2
- import { appendJsonl } from '../fsx.js';
2
+ import fs from 'node:fs/promises';
3
+ import { appendJsonl, nowIso } from '../fsx.js';
3
4
  import { appendAgentLedgerEvent } from './agent-central-ledger.js';
4
5
  export async function appendAgentMessage(root, message) {
6
+ const eventType = normalizeAgentMessageEventType(message.event_type || message.type);
5
7
  const entry = {
6
8
  schema: 'sks.agent-message.v1',
9
+ ts: nowIso(),
10
+ mission_id: message.mission_id || inferMissionIdFromAgentRoot(root),
11
+ worker_id: message.worker_id || message.from,
12
+ slot_id: message.slot_id ?? message.from ?? null,
13
+ session_id: message.session_id || null,
14
+ level: message.level || (eventType === 'worker_failed' || eventType === 'blocker' ? 'error' : 'info'),
15
+ event_type: eventType,
16
+ message: message.body,
17
+ artifact_paths: message.artifact_paths || [],
7
18
  from: message.from,
8
- session_id: message.session_id,
9
19
  to: message.to || 'orchestrator',
10
20
  type: message.type || 'note',
11
21
  body: message.body
@@ -14,4 +24,81 @@ export async function appendAgentMessage(root, message) {
14
24
  await appendAgentLedgerEvent(root, { agent_id: message.from, session_id: message.session_id, event_type: 'message_appended', payload: { to: entry.to, type: entry.type } });
15
25
  return entry;
16
26
  }
27
+ export async function readAgentMessageBus(root, missionId, opts = {}) {
28
+ const file = agentMessageBusPath(root, missionId);
29
+ let text = '';
30
+ try {
31
+ text = await fs.readFile(file, 'utf8');
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ const levels = new Set((opts.levels || []).map((level) => String(level)));
37
+ const rows = text.split(/\n+/)
38
+ .map((line) => line.trim())
39
+ .filter(Boolean)
40
+ .map((line) => {
41
+ try {
42
+ return normalizeAgentMessageBusEntry(JSON.parse(line), missionId);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ })
48
+ .filter((row) => Boolean(row))
49
+ .filter((row) => !levels.size || levels.has(row.level));
50
+ const max = Math.max(0, Math.floor(Number(opts.max || rows.length)));
51
+ return max > 0 ? rows.slice(-max) : rows;
52
+ }
53
+ export function agentMessageBusPath(root, missionId) {
54
+ const resolved = path.resolve(root);
55
+ if (path.basename(resolved) === 'agents')
56
+ return path.join(resolved, 'agent-messages.jsonl');
57
+ if (path.basename(resolved) === missionId)
58
+ return path.join(resolved, 'agents', 'agent-messages.jsonl');
59
+ return path.join(resolved, '.sneakoscope', 'missions', missionId, 'agents', 'agent-messages.jsonl');
60
+ }
61
+ function normalizeAgentMessageBusEntry(value, missionId) {
62
+ const eventType = normalizeAgentMessageEventType(value.event_type || value.type);
63
+ return {
64
+ schema: 'sks.agent-message.v1',
65
+ ts: String(value.ts || value.generated_at || nowIso()),
66
+ mission_id: String(value.mission_id || missionId),
67
+ worker_id: String(value.worker_id || value.from || value.slot_id || 'worker'),
68
+ slot_id: value.slot_id == null ? value.from == null ? null : String(value.from) : String(value.slot_id),
69
+ session_id: value.session_id == null ? null : String(value.session_id),
70
+ level: normalizeAgentMessageLevel(value.level, eventType),
71
+ event_type: eventType,
72
+ message: String(value.message || value.body || ''),
73
+ artifact_paths: Array.isArray(value.artifact_paths) ? value.artifact_paths.map(String) : [],
74
+ from: value.from == null ? undefined : String(value.from),
75
+ to: value.to == null ? undefined : String(value.to),
76
+ type: value.type == null ? undefined : String(value.type),
77
+ body: value.body == null ? undefined : String(value.body)
78
+ };
79
+ }
80
+ function normalizeAgentMessageEventType(value) {
81
+ const text = String(value || '').toLowerCase();
82
+ if (text === 'worker_completed' || text === 'completed' || text === 'done')
83
+ return 'worker_completed';
84
+ if (text === 'worker_failed' || text === 'failed' || text === 'error')
85
+ return 'worker_failed';
86
+ if (text === 'blocker')
87
+ return 'blocker';
88
+ if (text === 'handoff')
89
+ return 'handoff';
90
+ return 'status';
91
+ }
92
+ function normalizeAgentMessageLevel(value, eventType) {
93
+ const text = String(value || '').toLowerCase();
94
+ if (text === 'info' || text === 'warning' || text === 'error')
95
+ return text;
96
+ if (eventType === 'worker_failed' || eventType === 'blocker')
97
+ return 'error';
98
+ return 'info';
99
+ }
100
+ function inferMissionIdFromAgentRoot(root) {
101
+ const resolved = path.resolve(root);
102
+ return path.basename(resolved) === 'agents' ? path.basename(path.dirname(resolved)) : 'unknown';
103
+ }
17
104
  //# sourceMappingURL=agent-message-bus.js.map
@@ -1,8 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import { findLatestMission, missionDir } from '../mission.js';
3
3
  import { readJson, writeJsonAtomic } from '../fsx.js';
4
+ import { readAgentMessageBus } from './agent-message-bus.js';
5
+ import { buildZellijWorkerPaneSummary } from '../zellij/zellij-worker-pane-summary.js';
4
6
  export const RUNTIME_PROOF_SUMMARY_SCHEMA = 'sks.runtime-proof-summary.v1';
5
- export async function buildRuntimeProofSummary(root, missionIdInput = 'latest') {
7
+ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest', opts = {}) {
6
8
  const missionId = missionIdInput === 'latest' ? await findLatestMission(root) : missionIdInput;
7
9
  if (!missionId)
8
10
  throw new Error('runtime_proof_summary_mission_missing');
@@ -13,6 +15,11 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest')
13
15
  const swarm = await readJson(path.join(agentsDir, 'agent-native-cli-session-swarm.json'), null);
14
16
  const telemetry = await readJson(path.join(dir, 'zellij', 'slot-telemetry.snapshot.json'), null);
15
17
  const governor = await readJson(path.join(agentsDir, 'naruto-concurrency-governor.json'), null);
18
+ const messagesAll = await readAgentMessageBus(root, missionId, { max: 500 });
19
+ const recentMessages = await readAgentMessageBus(root, missionId, { max: opts.maxMessages || 8 });
20
+ const zellijSummary = await buildZellijWorkerPaneSummary(root, missionId).catch(() => null);
21
+ const failedMessages = messagesAll.filter((row) => row.event_type === 'worker_failed');
22
+ const errorMessages = messagesAll.filter((row) => row.level === 'error');
16
23
  const telemetryAgeMs = telemetry?.updated_at ? Math.max(0, Date.now() - Date.parse(telemetry.updated_at)) : Number.MAX_SAFE_INTEGER;
17
24
  const visiblePanes = Number(parallel?.visible_panes ?? swarm?.zellij_pane_worker_sessions ?? telemetryVisiblePaneCount(telemetry) ?? 0);
18
25
  const targetActive = Number(scheduler?.target_active_slots ?? parallel?.target_active_slots ?? swarm?.target_active_slots ?? governor?.target_active_slots ?? 0);
@@ -21,7 +28,9 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest')
21
28
  ...(!parallel ? ['parallel_runtime_proof_missing'] : []),
22
29
  ...(!scheduler ? ['agent_scheduler_state_missing'] : []),
23
30
  ...(parallel?.passed === false ? parallel.blockers || ['parallel_runtime_proof_failed'] : []),
24
- ...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : [])
31
+ ...(errorMessages.length ? ['agent_message_bus_error_blockers'] : []),
32
+ ...(telemetryAgeMs > 3000 ? ['zellij_telemetry_stale'] : []),
33
+ ...(zellijSummary?.blockers || [])
25
34
  ].map(String);
26
35
  const summary = {
27
36
  schema: RUNTIME_PROOF_SUMMARY_SCHEMA,
@@ -48,6 +57,22 @@ export async function buildRuntimeProofSummary(root, missionIdInput = 'latest')
48
57
  largest_batch_size: Number(scheduler?.largest_batch_size || 0),
49
58
  utilization: Number(scheduler?.scheduler_utilization || 0)
50
59
  },
60
+ messages: {
61
+ recent: recentMessages,
62
+ completed_count: messagesAll.filter((row) => row.event_type === 'worker_completed').length,
63
+ failed_count: failedMessages.length,
64
+ warning_count: messagesAll.filter((row) => row.level === 'warning').length,
65
+ error_count: errorMessages.length
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
+ },
51
76
  blockers
52
77
  };
53
78
  await writeJsonAtomic(path.join(agentsDir, 'runtime-proof-summary.json'), summary);
@@ -62,9 +87,28 @@ export function renderRuntimeProofSummary(summary) {
62
87
  `Visible/headless: ${summary.ui.visible_panes} / ${summary.ui.headless_workers}`,
63
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`}`,
64
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}`,
94
+ ...(summary.messages.recent.length ? [
95
+ 'Recent worker messages:',
96
+ ...summary.messages.recent.map((row) => ` ${messageStatusLabel(row)} ${row.slot_id || row.worker_id}: ${row.message}`)
97
+ ] : []),
65
98
  ...(summary.blockers.length ? [`Blockers: ${summary.blockers.join(', ')}`] : [])
66
99
  ].join('\n');
67
100
  }
101
+ function messageStatusLabel(row) {
102
+ if (row.event_type === 'worker_completed')
103
+ return '[done]';
104
+ if (row.event_type === 'worker_failed')
105
+ return '[fail]';
106
+ if (row.level === 'warning')
107
+ return '[warn]';
108
+ if (row.level === 'error')
109
+ return '[err]';
110
+ return '[info]';
111
+ }
68
112
  function telemetryVisiblePaneCount(snapshot) {
69
113
  const slots = snapshot?.slots && typeof snapshot.slots === 'object' ? Object.values(snapshot.slots) : [];
70
114
  return slots.filter((row) => row?.status && row.status !== 'headless').length;
@@ -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,52 @@ 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_rich_tool_schemas: richSchemaOk,
57
+ supports_doctor_env_details: doctorEnvOk,
39
58
  supports_marketplace_source_field: marketplaceOk,
40
59
  supports_plugin_catalog_cache: atLeast139,
41
60
  supports_sandbox_profile_alias: profileAliasOk,
42
- supports_interrupt_agent_rename: atLeast139,
61
+ supports_interrupt_agent_rename: interruptAgentOk,
43
62
  feature_probe_results: featureProbeResults,
44
63
  blockers
45
64
  };
@@ -71,12 +90,23 @@ async function probeCodex0139Features(codexBin, opts = {}) {
71
90
  if (opts.fake) {
72
91
  return {
73
92
  marketplace_list_json: process.env.SKS_CODEX_0139_FAKE_MARKETPLACE_FAIL === '1' ? 'failed' : 'passed',
74
- sandbox_profile_alias: 'passed'
93
+ sandbox_profile_alias: process.env.SKS_CODEX_0139_FAKE_PROFILE_ALIAS_FAIL === '1' ? 'failed' : 'passed',
94
+ interrupt_agent_event_mapping: process.env.SKS_CODEX_0139_FAKE_INTERRUPT_FAIL === '1' ? 'failed' : 'passed',
95
+ rich_tool_schema_preservation: process.env.SKS_CODEX_0139_FAKE_RICH_SCHEMA_FAIL === '1' ? 'failed' : 'passed',
96
+ doctor_env_redaction: process.env.SKS_CODEX_0139_FAKE_DOCTOR_ENV_FAIL === '1' ? 'failed' : 'passed',
97
+ code_mode_web_search: process.env.SKS_CODEX_0139_FAKE_WEB_SEARCH_FAIL === '1' ? 'failed' : 'passed'
75
98
  };
76
99
  }
77
- const timeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
100
+ const timeoutMs = Math.max(1, Number(opts.timeoutMs || process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
78
101
  if (!codexBin) {
79
- return { marketplace_list_json: 'failed', sandbox_profile_alias: 'failed' };
102
+ return {
103
+ marketplace_list_json: 'failed',
104
+ sandbox_profile_alias: 'failed',
105
+ interrupt_agent_event_mapping: 'skipped',
106
+ rich_tool_schema_preservation: 'skipped',
107
+ doctor_env_redaction: 'skipped',
108
+ code_mode_web_search: 'skipped'
109
+ };
80
110
  }
81
111
  const marketplace = await runProcess(codexBin, ['plugin', 'marketplace', 'list', '--json'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
82
112
  const marketplaceListJson = marketplace.code === 0 && marketplaceSourcesPresent(marketplace.stdout) ? 'passed' : 'failed';
@@ -84,7 +114,11 @@ async function probeCodex0139Features(codexBin, opts = {}) {
84
114
  const aliasOk = help.code === 0 && /(^|\s)-P[,\s]/m.test(String(help.stdout || ''));
85
115
  return {
86
116
  marketplace_list_json: marketplaceListJson,
87
- sandbox_profile_alias: aliasOk ? 'passed' : 'failed'
117
+ sandbox_profile_alias: aliasOk ? 'passed' : 'failed',
118
+ interrupt_agent_event_mapping: 'skipped',
119
+ rich_tool_schema_preservation: 'skipped',
120
+ doctor_env_redaction: 'skipped',
121
+ code_mode_web_search: 'skipped'
88
122
  };
89
123
  }
90
124
  export function marketplaceSourcesPresent(stdout) {
@@ -93,10 +127,32 @@ export function marketplaceSourcesPresent(stdout) {
93
127
  const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : Array.isArray(parsed?.items) ? parsed.items : [];
94
128
  if (!rows.length)
95
129
  return true;
96
- return rows.some((row) => typeof row?.source === 'string' && row.source.length > 0);
130
+ return rows.every((row) => typeof row?.source === 'string' && row.source.length > 0);
97
131
  }
98
132
  catch {
99
133
  return false;
100
134
  }
101
135
  }
136
+ export function codexHelpSupportsSandboxProfileAlias(stdout) {
137
+ return /(^|\s)-P[,\s]+--profile\b|--profile[,\s]+-P\b|(^|\s)-P\b/m.test(String(stdout || ''));
138
+ }
139
+ export function redactCodexDoctorEnvDetails(value) {
140
+ if (Array.isArray(value))
141
+ return value.map((item) => redactCodexDoctorEnvDetails(item));
142
+ if (!value || typeof value !== 'object') {
143
+ return secretLikeValue(value) ? '<redacted>' : value;
144
+ }
145
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
146
+ if (secretLikeKey(key) || secretLikeValue(entry))
147
+ return [key, '<redacted>'];
148
+ return [key, redactCodexDoctorEnvDetails(entry)];
149
+ }));
150
+ }
151
+ function secretLikeKey(key) {
152
+ return /(?:api[_-]?key|auth[_-]?token|secret|password|credential|bearer|session[_-]?token)/i.test(key);
153
+ }
154
+ function secretLikeValue(value) {
155
+ const text = typeof value === 'string' ? value : '';
156
+ 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);
157
+ }
102
158
  //# sourceMappingURL=codex-0139-capability.js.map
@@ -0,0 +1,15 @@
1
+ export function normalizeCodexMultiAgentEventName(name) {
2
+ const sourceName = String(name || '');
3
+ const normalized = sourceName.trim().toLowerCase().replace(/[-.\s]+/g, '_');
4
+ if (normalized === 'start_agent' || normalized === 'spawn_agent' || normalized === 'subagent_start') {
5
+ return { canonical: 'start_agent', stage: 'start', source_name: sourceName };
6
+ }
7
+ if (normalized === 'interrupt_agent') {
8
+ return { canonical: 'interrupt_agent', stage: 'result', source_name: sourceName };
9
+ }
10
+ if (normalized === 'close_agent' || normalized === 'subagent_stop') {
11
+ return { canonical: 'close_agent', stage: 'result', source_name: sourceName };
12
+ }
13
+ return { canonical: 'unknown', stage: 'unknown', source_name: sourceName };
14
+ }
15
+ //# sourceMappingURL=codex-multi-agent-event-normalizer.js.map
@@ -0,0 +1,57 @@
1
+ import { compactMcpToolSchema } from '../mcp/mcp-0-134-policy.js';
2
+ export function buildCodex0139RichToolSchemaFixture() {
3
+ return {
4
+ type: 'object',
5
+ description: 'Codex 0.139 rich tool schema preservation fixture',
6
+ oneOf: [
7
+ { required: ['mode'], properties: { mode: { const: 'guided' } } },
8
+ { required: ['query'], properties: { query: { type: 'string' } } }
9
+ ],
10
+ allOf: [
11
+ { required: ['kind'] },
12
+ { properties: { kind: { enum: ['search', 'inspect'] } } }
13
+ ],
14
+ required: ['kind', 'payload'],
15
+ properties: {
16
+ kind: { enum: ['search', 'inspect'] },
17
+ payload: {
18
+ type: 'object',
19
+ required: ['target'],
20
+ properties: {
21
+ target: { type: 'string' },
22
+ filters: {
23
+ type: 'object',
24
+ properties: {
25
+ depth: { enum: ['shallow', 'deep'] }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ };
32
+ }
33
+ export function passCodex0139RichToolSchemaThroughBridge(schema = buildCodex0139RichToolSchemaFixture()) {
34
+ return compactMcpToolSchema(schema, 128).schema;
35
+ }
36
+ export function evaluateCodex0139RichToolSchemaPreservation(schema = buildCodex0139RichToolSchemaFixture()) {
37
+ const bridged = passCodex0139RichToolSchemaThroughBridge(schema);
38
+ const required = Array.isArray(bridged?.required) ? bridged.required : [];
39
+ const result = {
40
+ schema: 'sks.codex-0139-rich-tool-schema-preservation.v1',
41
+ ok: Array.isArray(bridged?.oneOf)
42
+ && Array.isArray(bridged?.allOf)
43
+ && Boolean(bridged?.properties?.payload?.properties?.target)
44
+ && required.includes('kind')
45
+ && required.includes('payload'),
46
+ top_level_oneOf_preserved: Array.isArray(bridged?.oneOf),
47
+ top_level_allOf_preserved: Array.isArray(bridged?.allOf),
48
+ nested_structure_preserved: Boolean(bridged?.properties?.payload?.properties?.target),
49
+ required_fields_retained: required.includes('kind') && required.includes('payload'),
50
+ bridged_schema: bridged
51
+ };
52
+ return {
53
+ ...result,
54
+ blockers: result.ok ? [] : ['codex_rich_tool_schema_preservation_failed']
55
+ };
56
+ }
57
+ //# sourceMappingURL=codex-tool-schema-fixtures.js.map
@@ -741,7 +741,7 @@ async function narutoProof(parsed) {
741
741
  const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
742
742
  if (!id)
743
743
  return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'proof', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
744
- const summary = await buildRuntimeProofSummary(root, id);
744
+ const summary = await buildRuntimeProofSummary(root, id, { maxMessages: parsed.messages });
745
745
  return emit(parsed, { ...summary, action: 'proof' }, () => {
746
746
  console.log(renderRuntimeProofSummary(summary));
747
747
  });
@@ -756,7 +756,7 @@ async function narutoHelp(parsed) {
756
756
  usage: [
757
757
  'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
758
758
  'sks naruto status [--mission <id>] [--json]',
759
- 'sks naruto proof latest [--json]'
759
+ 'sks naruto proof latest [--messages 20] [--json]'
760
760
  ],
761
761
  defaults: { clones: DEFAULT_NARUTO_CLONES, max_clones: MAX_NARUTO_AGENT_COUNT, backend: 'codex-sdk' }
762
762
  };
@@ -798,9 +798,10 @@ function parseNarutoArgs(args = []) {
798
798
  const attach = hasFlag(args, '--attach');
799
799
  const smoke = hasFlag(args, '--smoke');
800
800
  const parallelism = normalizeParallelism(readOption(args, '--parallelism', 'extreme'));
801
- const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism']);
801
+ const messages = normalizeMessages(readOption(args, '--messages', '8'));
802
+ const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism', '--messages']);
802
803
  const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
803
- return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism };
804
+ return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism, messages };
804
805
  }
805
806
  function normalizeParallelism(value) {
806
807
  const text = String(value || 'extreme').toLowerCase();
@@ -808,6 +809,10 @@ function normalizeParallelism(value) {
808
809
  return text;
809
810
  return 'extreme';
810
811
  }
812
+ function normalizeMessages(value) {
813
+ const parsed = Number(value);
814
+ return Math.max(0, Math.min(100, Math.floor(Number.isFinite(parsed) ? parsed : 8)));
815
+ }
811
816
  async function writeNarutoArtifacts(ledgerRoot, artifacts) {
812
817
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-work-graph.json'), artifacts.workGraph);
813
818
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '3.0.1';
8
+ export const PACKAGE_VERSION = '3.0.3';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -63,6 +63,9 @@ export function compactMcpToolSchema(schema, maxBytes = 8192) {
63
63
  type: schema?.type || 'object',
64
64
  description: String(schema?.description || '').slice(0, 512),
65
65
  properties: Object.fromEntries(Object.entries(schema?.properties || {}).slice(0, 50)),
66
+ ...(Array.isArray(schema?.oneOf) ? { oneOf: schema.oneOf } : {}),
67
+ ...(Array.isArray(schema?.allOf) ? { allOf: schema.allOf } : {}),
68
+ ...(Array.isArray(schema?.required) ? { required: schema.required } : {}),
66
69
  ...preservedDefs,
67
70
  ...(refs.length ? { refs } : {})
68
71
  },
@@ -29,6 +29,7 @@ import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE,
29
29
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../team-dag.js';
30
30
  import { formatAgentReasoning, formatRoleCounts, initTeamLive, parseTeamSpecText, teamReasoningPolicy } from '../team-live.js';
31
31
  import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from '../team-review-policy.js';
32
+ import { normalizeCodexMultiAgentEventName } from '../codex-control/codex-multi-agent-event-normalizer.js';
32
33
  export { routePrompt };
33
34
  export const PIPELINE_PLAN_ARTIFACT = 'pipeline-plan.json';
34
35
  export const PIPELINE_PLAN_SCHEMA_VERSION = 1;
@@ -1257,7 +1258,11 @@ function subagentStage(payload) {
1257
1258
  return 'exception';
1258
1259
  if (/spawn_agent/i.test(hay))
1259
1260
  return 'spawn_agent';
1260
- if (/wait_agent|close_agent|interrupt_agent|completed|final/i.test(hay))
1261
+ for (const name of ['interrupt_agent', 'close_agent']) {
1262
+ if (hay.includes(name) && normalizeCodexMultiAgentEventName(name).stage === 'result')
1263
+ return 'result';
1264
+ }
1265
+ if (/wait_agent|completed|final/i.test(hay))
1261
1266
  return 'result';
1262
1267
  return 'subagent';
1263
1268
  }