sneakoscope 3.1.5 → 3.1.7

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 (58) hide show
  1. package/README.md +9 -37
  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 -2
  8. package/dist/commands/codex-app.js +20 -12
  9. package/dist/commands/codex-native.js +84 -0
  10. package/dist/commands/doctor.js +90 -2
  11. package/dist/core/codex-app/codex-agent-role-sync.js +15 -5
  12. package/dist/core/codex-app/codex-app-execution-profile.js +38 -16
  13. package/dist/core/codex-app/codex-app-harness-matrix.js +4 -117
  14. package/dist/core/codex-app/codex-hook-lifecycle.js +4 -1
  15. package/dist/core/codex-app/codex-init-deep.js +66 -4
  16. package/dist/core/codex-app/codex-skill-sync.js +13 -8
  17. package/dist/core/codex-control/codex-0138-capability.js +5 -2
  18. package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
  19. package/dist/core/codex-native/codex-native-feature-broker.js +250 -0
  20. package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
  21. package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
  22. package/dist/core/{codex-app/lazycodex-interop-policy.js → codex-native/codex-native-interop-policy.js} +13 -15
  23. package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
  24. package/dist/core/codex-native/codex-native-pattern-analysis.js +68 -0
  25. package/dist/core/codex-native/codex-native-reference-cache.js +98 -0
  26. package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
  27. package/dist/core/codex-native/codex-native-reference-source.js +149 -0
  28. package/dist/core/codex-native/codex-native-rename-map.js +25 -0
  29. package/dist/core/codex-native/codex-native-repair-transaction.js +150 -0
  30. package/dist/core/codex-plugins/codex-plugin-json.js +5 -2
  31. package/dist/core/commands/mad-sks-command.js +16 -0
  32. package/dist/core/feature-fixtures.js +2 -4
  33. package/dist/core/feature-registry.js +1 -1
  34. package/dist/core/fsx.js +1 -1
  35. package/dist/core/image/image-artifact-path-contract.js +18 -1
  36. package/dist/core/loops/loop-owner-inference.js +3 -0
  37. package/dist/core/loops/loop-planner.js +8 -2
  38. package/dist/core/loops/loop-worker-prompts.js +2 -0
  39. package/dist/core/loops/loop-worker-runtime.js +42 -7
  40. package/dist/core/qa-loop.js +24 -1
  41. package/dist/core/research.js +36 -3
  42. package/dist/core/routes.js +2 -3
  43. package/dist/core/version.js +1 -1
  44. package/dist/scripts/codex-native-runtime-e2e-fixture.js +75 -0
  45. package/dist/scripts/loop-worker-fixture-child.js +2 -1
  46. package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
  47. package/dist/scripts/sks-3-1-5-directive-check-lib.js +4 -33
  48. package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
  49. package/dist/scripts/sks-3-1-7-directive-check-lib.js +58 -0
  50. package/package.json +44 -13
  51. package/dist/cli/hermes-command.js +0 -99
  52. package/dist/cli/openclaw-command.js +0 -83
  53. package/dist/commands/hermes.js +0 -5
  54. package/dist/commands/openclaw.js +0 -3
  55. package/dist/core/codex-app/lazycodex-analysis.js +0 -72
  56. package/dist/core/codex-app/lazycodex-live-analyzer.js +0 -98
  57. package/dist/core/hermes.js +0 -192
  58. package/dist/core/openclaw.js +0 -171
@@ -0,0 +1,150 @@
1
+ import path from 'node:path';
2
+ import { buildCodexHookLifecycle } from '../codex-app/codex-hook-lifecycle.js';
3
+ import { runCodexInitDeep } from '../codex-app/codex-init-deep.js';
4
+ import { syncCodexAgentRoles } from '../codex-app/codex-agent-role-sync.js';
5
+ import { syncCodexSksSkills } from '../codex-app/codex-skill-sync.js';
6
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
7
+ import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
8
+ import { evaluateMutation, mutationLedgerPath, recordMutation } from '../safety/mutation-ledger.js';
9
+ export async function repairCodexNativeManagedAssets(input) {
10
+ const root = path.resolve(input.root);
11
+ const requested = {
12
+ skills: input.repairSkills !== false,
13
+ agent_roles: input.repairAgentRoles !== false,
14
+ hooks: input.repairHooks !== false,
15
+ project_memory: input.repairProjectMemory !== false
16
+ };
17
+ const requestedAssets = Object.entries(requested)
18
+ .filter(([, enabled]) => enabled)
19
+ .map(([asset]) => asset);
20
+ if (input.yes !== true) {
21
+ const report = {
22
+ schema: 'sks.codex-native-repair-transaction.v1',
23
+ ok: false,
24
+ generated_at: nowIso(),
25
+ requested_by: input.requestedBy,
26
+ repaired: requestedAssets.map((asset) => ({
27
+ asset,
28
+ ok: false,
29
+ changed: false,
30
+ artifact_path: artifactPathFor(asset),
31
+ blockers: ['repair_transaction_requires_yes']
32
+ })),
33
+ confirmed: false,
34
+ mutation_ledger_path: null,
35
+ blockers: ['repair_transaction_requires_yes'],
36
+ warnings: []
37
+ };
38
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-repair-transaction.json'), report).catch(() => undefined);
39
+ return report;
40
+ }
41
+ const rows = [];
42
+ if (requested.skills) {
43
+ const report = await syncCodexSksSkills({ root, apply: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
44
+ rows.push({
45
+ asset: 'skills',
46
+ ok: recordOk(report) !== false,
47
+ changed: listLength(report, 'created') > 0,
48
+ artifact_path: '.sneakoscope/reports/codex-skill-sync.json',
49
+ blockers: blockersOf(report)
50
+ });
51
+ }
52
+ if (requested.agent_roles) {
53
+ const report = await syncCodexAgentRoles({ root, apply: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
54
+ rows.push({
55
+ asset: 'agent_roles',
56
+ ok: recordOk(report) !== false,
57
+ changed: listLength(report, 'created') > 0,
58
+ artifact_path: '.sneakoscope/reports/codex-agent-role-sync.json',
59
+ blockers: blockersOf(report)
60
+ });
61
+ }
62
+ if (requested.hooks) {
63
+ const report = await buildCodexHookLifecycle({ root, apply: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
64
+ rows.push({
65
+ asset: 'hooks',
66
+ ok: recordOk(report) !== false,
67
+ changed: recordOk(report) !== false,
68
+ artifact_path: '.sneakoscope/reports/codex-hook-lifecycle.json',
69
+ blockers: blockersOf(report)
70
+ });
71
+ }
72
+ if (requested.project_memory) {
73
+ const report = await runCodexInitDeep({ root, apply: true, directoryLocal: true }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
74
+ rows.push({
75
+ asset: 'project_memory',
76
+ ok: recordOk(report) !== false,
77
+ changed: listLength(report, 'directory_local_agents.created') + listLength(report, 'directory_local_agents.updated') > 0,
78
+ artifact_path: '.sneakoscope/reports/codex-init-deep.json',
79
+ blockers: blockersOf(report)
80
+ });
81
+ }
82
+ const blockers = rows.flatMap((row) => row.blockers);
83
+ const mutationWarnings = await recordRepairMutations(root, rows);
84
+ const report = {
85
+ schema: 'sks.codex-native-repair-transaction.v1',
86
+ ok: rows.every((row) => row.ok) && blockers.length === 0,
87
+ generated_at: nowIso(),
88
+ requested_by: input.requestedBy,
89
+ repaired: rows,
90
+ confirmed: true,
91
+ mutation_ledger_path: path.relative(root, mutationLedgerPath(root)).split(path.sep).join('/'),
92
+ blockers: [...new Set(blockers)],
93
+ warnings: mutationWarnings
94
+ };
95
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-native-repair-transaction.json'), report).catch(() => undefined);
96
+ return report;
97
+ }
98
+ async function recordRepairMutations(root, rows) {
99
+ const contract = createRequestedScopeContract({
100
+ route: 'codex-native-repair-transaction',
101
+ userRequest: 'Repair SKS-managed Codex Native assets after explicit --yes confirmation.',
102
+ projectRoot: root,
103
+ overrides: { global_codex_config: true }
104
+ });
105
+ const warnings = [];
106
+ for (const row of rows) {
107
+ const kind = row.asset === 'project_memory' ? 'file_write' : 'global_config_write';
108
+ const entry = evaluateMutation(contract, kind, {
109
+ target: row.asset,
110
+ confirmed: true,
111
+ applied: row.changed,
112
+ noOpReason: row.changed ? 'sks_managed_asset_transaction_recorded' : 'repair_noop'
113
+ });
114
+ await recordMutation(root, entry).catch((err) => warnings.push(`mutation_ledger_write_failed:${messageOf(err)}`));
115
+ }
116
+ return warnings;
117
+ }
118
+ function artifactPathFor(asset) {
119
+ return asset === 'skills'
120
+ ? '.sneakoscope/reports/codex-skill-sync.json'
121
+ : asset === 'agent_roles'
122
+ ? '.sneakoscope/reports/codex-agent-role-sync.json'
123
+ : asset === 'hooks'
124
+ ? '.sneakoscope/reports/codex-hook-lifecycle.json'
125
+ : '.sneakoscope/reports/codex-init-deep.json';
126
+ }
127
+ function recordOk(value) {
128
+ return Boolean(value) && typeof value === 'object' && typeof value.ok === 'boolean'
129
+ ? value.ok
130
+ : undefined;
131
+ }
132
+ function blockersOf(value) {
133
+ return Boolean(value) && typeof value === 'object' && Array.isArray(value.blockers)
134
+ ? value.blockers.map((item) => String(item)).filter(Boolean)
135
+ : [];
136
+ }
137
+ function listLength(value, key) {
138
+ const parts = key.split('.');
139
+ let current = value;
140
+ for (const part of parts) {
141
+ if (!current || typeof current !== 'object')
142
+ return 0;
143
+ current = current[part];
144
+ }
145
+ return Array.isArray(current) ? current.length : 0;
146
+ }
147
+ function messageOf(err) {
148
+ return err instanceof Error ? err.message : String(err);
149
+ }
150
+ //# sourceMappingURL=codex-native-repair-transaction.js.map
@@ -33,7 +33,8 @@ export async function buildCodexPluginInventory() {
33
33
  });
34
34
  const blockers = [
35
35
  ...(capability.supports_plugin_json ? [] : ['codex_0_138_plugin_json_unavailable']),
36
- ...normalizeList(listJson?.blockers)
36
+ ...normalizeList(listJson?.blockers),
37
+ ...(process.env.SKS_CODEX_PLUGIN_JSON_FAKE_NO_MCP === '1' ? ['fixture_mcp_candidates_disabled'] : [])
37
38
  ];
38
39
  return {
39
40
  schema: 'sks.codex-plugin-inventory.v1',
@@ -169,7 +170,9 @@ function fakePluginDetail(pluginId) {
169
170
  installed: true,
170
171
  enabled: true,
171
172
  default_prompts: ['Use the fixture plugin safely.'],
172
- remote_mcp_servers: [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
173
+ remote_mcp_servers: process.env.SKS_CODEX_PLUGIN_JSON_FAKE_NO_MCP === '1'
174
+ ? []
175
+ : [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
173
176
  unavailable_app_templates: ['fixture-desktop-template']
174
177
  };
175
178
  }
@@ -22,6 +22,7 @@ import { checkSksUpdateNotice } from '../update/update-notice.js';
22
22
  import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
23
23
  import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
24
24
  import { writeCodex0139CapabilityArtifacts } from '../codex-control/codex-0139-capability.js';
25
+ import { resolveCodexNativeInvocationPlan } from '../codex-native/codex-native-invocation-router.js';
25
26
  import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
26
27
  export async function madHighCommand(args = [], deps = {}) {
27
28
  const subcommand = firstSubcommand(args);
@@ -478,6 +479,14 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
478
479
  const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
479
480
  await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
480
481
  await writeCodex0139CapabilityArtifacts(root, { missionId: id }).catch(() => null);
482
+ const codexNativeInvocation = await resolveCodexNativeInvocationPlan({
483
+ root,
484
+ missionId: id,
485
+ route: '$MAD',
486
+ desiredCapability: 'hook-evidence'
487
+ }).catch(() => null);
488
+ if (codexNativeInvocation)
489
+ await writeJsonAtomic(path.join(dir, 'mad-codex-native-invocation.json'), codexNativeInvocation).catch(() => undefined);
481
490
  const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
482
491
  // The interactive launch 'before' snapshot is only persisted (env + policy json)
483
492
  // and is never compared against an 'after' snapshot during the session, so the
@@ -521,6 +530,13 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
521
530
  protected_core_policy: protectedCorePolicyPath,
522
531
  protected_core_before: protectedCoreBeforePath,
523
532
  protected_core_digest: protectedCoreBefore.digest,
533
+ codex_native_invocation_plan: codexNativeInvocation ? {
534
+ selected_strategy: codexNativeInvocation.selected_strategy,
535
+ hook_evidence_policy: codexNativeInvocation.env.SKS_CODEX_NATIVE_HOOK_EVIDENCE_POLICY,
536
+ blockers: codexNativeInvocation.blockers,
537
+ warnings: codexNativeInvocation.warnings,
538
+ artifact_path: 'mad-codex-native-invocation.json'
539
+ } : null,
524
540
  activated_by: 'sks --mad',
525
541
  cwd: path.resolve(cwd || process.cwd())
526
542
  };
@@ -64,8 +64,7 @@ const FIXTURES = Object.freeze({
64
64
  'cli-bootstrap': fixture('mock', 'sks bootstrap --dry-run', [], 'pass'),
65
65
  'cli-deps': fixture('mock', 'sks deps check --json', [], 'pass'),
66
66
  'cli-auth': fixture('mock', 'sks auth status --json', [], 'pass'),
67
- 'cli-openclaw': fixture('mock', 'sks openclaw status --json', [], 'pass'),
68
- 'cli-hermes': fixture('mock', 'sks hermes status --json', [], 'pass'),
67
+ 'cli-codex-native': fixture('mock', 'sks codex-native status --json', [], 'pass'),
69
68
  'cli-zellij': fixture('mock', 'npm run zellij:capability --silent', [], 'pass'),
70
69
  'cli-tmux': fixture('mock', 'removed runtime migration notice: sks tmux --json', [], 'pass'),
71
70
  'cli-mad': fixture('mock', 'sks mad --help', [], 'pass'),
@@ -129,8 +128,7 @@ const STATIC_CONTRACT_FEATURES = new Set([
129
128
  'cli-bootstrap',
130
129
  'cli-deps',
131
130
  'cli-auth',
132
- 'cli-openclaw',
133
- 'cli-hermes',
131
+ 'cli-codex-native',
134
132
  'cli-zellij',
135
133
  'cli-tmux',
136
134
  'cli-mad',
@@ -813,7 +813,7 @@ function commandCategory(name) {
813
813
  return 'proof-route';
814
814
  if (['qa-loop', 'research', 'recallpulse', 'skill-dream', 'eval', 'perf'].includes(name))
815
815
  return 'loop';
816
- if (['codex', 'codex-app', 'codex-lb', 'auth', 'hooks', 'context7', 'openclaw', 'hermes'].includes(name))
816
+ if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'auth', 'hooks', 'context7'].includes(name))
817
817
  return 'integration';
818
818
  if (['db', 'guard', 'conflicts', 'harness', 'versioning'].includes(name))
819
819
  return 'safety';
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.1.5';
8
+ export const PACKAGE_VERSION = '3.1.7';
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() {
@@ -2,9 +2,17 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { nowIso, writeJsonAtomic } from '../fsx.js';
4
4
  import { imageDimensions } from '../wiki-image/image-hash.js';
5
+ import { resolveCodexNativeInvocationPlan } from '../codex-native/codex-native-invocation-router.js';
5
6
  export async function buildImageArtifactPathContract(root, input) {
6
7
  const images = [];
7
8
  const blockers = [];
9
+ const invocationPlan = await resolveCodexNativeInvocationPlan({
10
+ root,
11
+ missionId: input.missionId,
12
+ route: '$Image',
13
+ desiredCapability: 'image-followup'
14
+ }).catch(() => null);
15
+ const followupStrategy = invocationPlan?.selected_strategy === 'codex-app-native' ? 'model-visible-path' : 'artifact-path';
8
16
  for (const [index, image] of input.images.entries()) {
9
17
  const filePath = path.resolve(root, image.filePath || '');
10
18
  const exists = await fileExists(filePath);
@@ -23,8 +31,11 @@ export async function buildImageArtifactPathContract(root, input) {
23
31
  width: dims?.width ?? null,
24
32
  height: dims?.height ?? null,
25
33
  model_visible_path: filePath,
34
+ codex_native_followup_strategy: followupStrategy,
26
35
  followup_edit_hint: exists
27
- ? `Use this saved local path for follow-up image edits: ${filePath}`
36
+ ? followupStrategy === 'model-visible-path'
37
+ ? `Use this model-visible saved local path for follow-up image edits: ${filePath}`
38
+ : `Use this saved artifact path for follow-up image edits: ${filePath}`
28
39
  : 'Image file path missing; do not run visual QA until a real saved file path exists.'
29
40
  });
30
41
  }
@@ -35,6 +46,12 @@ export async function buildImageArtifactPathContract(root, input) {
35
46
  mission_id: input.missionId,
36
47
  generated_at: nowIso(),
37
48
  images,
49
+ codex_native_invocation_plan: invocationPlan ? {
50
+ selected_strategy: invocationPlan.selected_strategy,
51
+ proof_policy: invocationPlan.proof_policy,
52
+ blockers: invocationPlan.blockers,
53
+ warnings: invocationPlan.warnings
54
+ } : null,
38
55
  blockers: [...new Set(blockers)]
39
56
  };
40
57
  }
@@ -39,6 +39,9 @@ export function detectOwnerScopeCollisions(scopes) {
39
39
  }
40
40
  return blockers;
41
41
  }
42
+ export function memoryHintMayExpandOwnerScope() {
43
+ return false;
44
+ }
42
45
  function inferPackageScripts(domainId) {
43
46
  if (domainId === 'docs')
44
47
  return ['docs:loop-runtime'];
@@ -56,7 +56,12 @@ export async function planLoopsFromRequest(input) {
56
56
  const memoryHints = await readInitDeepMemoryHints(input.root, scopePathsForNodes(nodes)).catch(() => []);
57
57
  const nodesWithMemory = nodes.map((node) => {
58
58
  const hints = memoryHints.filter((hint) => hintAppliesToNode(hint, node)).slice(0, 5);
59
- return hints.length ? { ...node, memory_hints: hints } : node;
59
+ return {
60
+ ...node,
61
+ ...(hints.length ? { memory_hints: hints } : {}),
62
+ memory_hints_used: hints,
63
+ memory_did_not_expand_scope: true
64
+ };
60
65
  });
61
66
  const plan = {
62
67
  schema: 'sks.loop-plan.v1',
@@ -94,7 +99,8 @@ export async function planLoopsFromRequest(input) {
94
99
  plan.project_memory = {
95
100
  source: projectMemory.path,
96
101
  injected: true,
97
- summary: projectMemory.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8)
102
+ summary: projectMemory.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8),
103
+ memory_did_not_expand_scope: true
98
104
  };
99
105
  }
100
106
  const validation = validateLoopPlan(plan);
@@ -10,6 +10,7 @@ export function buildLoopMakerPrompt(input) {
10
10
  `Owner directories: ${node.owner_scope.directories.join(', ') || '-'}`,
11
11
  `Allowed mutation scope: ${ownerScopeText(node)}`,
12
12
  'Do not mutate outside the owner scope.',
13
+ 'Memory hints are guidance only; memory never grants write permission or expands owner scope.',
13
14
  `Selected local gates: ${allGateIds(node.gates).join(', ') || '-'}`,
14
15
  `Budget: ${JSON.stringify(node.budget)}`,
15
16
  `Worktree path: ${input.worktreePath || '-'}`,
@@ -30,6 +31,7 @@ export function buildLoopCheckerPrompt(input) {
30
31
  `Selected gates: ${allGateIds(node.gates).join(', ') || '-'}`,
31
32
  `Risk: ${node.risk.level} (${node.risk.reasons.join(', ') || '-'})`,
32
33
  'Reject unrequested side effects and owner-scope violations.',
34
+ 'Memory hints are guidance only; memory never grants write permission or expands owner scope.',
33
35
  'Write checker-findings.json with fresh_session, reviewed_maker_artifacts, side_effects_detected, and approved.',
34
36
  'No synthetic pass is allowed for production proof.'
35
37
  ].join('\n');
@@ -7,6 +7,7 @@ import { computeLoopConcurrencyBudget, loopWorkerBudgetFor } from './loop-concur
7
7
  import { decideLoopFixturePolicy, writeLoopFixturePolicyDecision } from './loop-fixture-policy.js';
8
8
  import { buildLoopCheckerPrompt, buildLoopMakerPrompt } from './loop-worker-prompts.js';
9
9
  import { resolveCodexAppExecutionProfile } from '../codex-app/codex-app-execution-profile.js';
10
+ import { resolveCodexNativeInvocationPlan } from '../codex-native/codex-native-invocation-router.js';
10
11
  export async function runLoopMakerWorkers(input) {
11
12
  return runLoopWorkers({ ...input, phase: 'maker' });
12
13
  }
@@ -44,7 +45,13 @@ async function runLoopWorkerNative(input) {
44
45
  : buildLoopCheckerPrompt({ plan: input.plan, node: input.node, makerArtifacts: input.makerArtifacts || [] });
45
46
  const workerCount = effectiveLoopWorkerCount(input);
46
47
  const executionProfile = await resolveCodexAppExecutionProfile({ root: input.root }).catch(() => null);
47
- const workGraph = buildLoopNarutoWorkGraph(input, workerCount, executionProfile);
48
+ const invocationPlan = await resolveCodexNativeInvocationPlan({
49
+ root: input.root,
50
+ missionId: input.plan.mission_id,
51
+ route: '$Loop',
52
+ desiredCapability: 'agent-role'
53
+ }).catch(() => null);
54
+ const workGraph = buildLoopNarutoWorkGraph(input, workerCount, executionProfile, invocationPlan);
48
55
  // Root-cause-1 fix: keep the ORCHESTRATOR root on the MAIN repo (input.root), not the
49
56
  // loop worktree. All zellij/right-column/slot-telemetry state derives from the orchestrator
50
57
  // root, so anchoring it on input.root makes the SLOTS snapshot land under
@@ -81,7 +88,10 @@ async function runLoopWorkerNative(input) {
81
88
  SKS_LOOP_MAIN_ROOT: input.root,
82
89
  SKS_LOOP_WORKER_BUDGET: String(workerCount),
83
90
  SKS_CODEX_APP_EXECUTION_PROFILE: executionProfile?.mode || 'unknown',
84
- SKS_CODEX_AGENT_ROLE_STRATEGY: executionProfile?.agent_role_strategy || 'message-role'
91
+ SKS_CODEX_AGENT_ROLE_STRATEGY: executionProfile?.agent_role_strategy || 'message-role',
92
+ SKS_CODEX_NATIVE_STRATEGY: invocationPlan?.selected_strategy || 'message-role-fallback',
93
+ SKS_CODEX_NATIVE_AGENT_ROLE_STRATEGY: invocationPlan?.env.SKS_CODEX_NATIVE_AGENT_ROLE_STRATEGY || executionProfile?.agent_role_strategy || 'message-role',
94
+ SKS_CODEX_NATIVE_FEATURE_MATRIX: invocationPlan?.feature_matrix_artifact || '.sneakoscope/reports/codex-native-feature-matrix.json'
85
95
  },
86
96
  ...(input.worktree?.path ? {
87
97
  worktree: {
@@ -99,9 +109,9 @@ async function runLoopWorkerNative(input) {
99
109
  fallback_reason: null
100
110
  } : null
101
111
  });
102
- return normalizeNativeResult(input, orchestrator, executionProfile);
112
+ return normalizeNativeResult(input, orchestrator, executionProfile, invocationPlan);
103
113
  }
104
- async function normalizeNativeResult(input, result, executionProfile) {
114
+ async function normalizeNativeResult(input, result, executionProfile, invocationPlan) {
105
115
  const artifacts = collectArtifactPaths(result);
106
116
  const changedFiles = stringArray(result?.changed_files || result?.proof?.changed_files || result?.results?.flatMap?.((row) => row?.changed_files || []));
107
117
  const blockers = [
@@ -132,6 +142,9 @@ async function normalizeNativeResult(input, result, executionProfile) {
132
142
  artifact_path: executionProfile.artifact_path,
133
143
  agent_type_probe_artifact_path: executionProfile.agent_type_probe_artifact_path
134
144
  }
145
+ } : {}),
146
+ ...(invocationPlan ? {
147
+ codex_native_invocation_plan: compactInvocationPlan(invocationPlan)
135
148
  } : {})
136
149
  };
137
150
  await writeJsonAtomic(proofPath, { ...normalized, native_result_summary: summarizeNativeResult(result), generated_at: nowIso() });
@@ -146,6 +159,12 @@ async function runLoopWorkerFixture(input) {
146
159
  });
147
160
  const dir = path.join(loopNodeRoot(input.root, input.plan.mission_id, input.node.loop_id), input.phase);
148
161
  await ensureDir(dir);
162
+ const invocationPlan = await resolveCodexNativeInvocationPlan({
163
+ root: input.root,
164
+ missionId: input.plan.mission_id,
165
+ route: '$Loop',
166
+ desiredCapability: 'agent-role'
167
+ }).catch(() => null);
149
168
  const resultPath = path.join(dir, 'worker-runtime-result.json');
150
169
  const childInputPath = path.join(dir, 'worker-fixture-intake.json');
151
170
  await writeJsonAtomic(childInputPath, {
@@ -157,7 +176,8 @@ async function runLoopWorkerFixture(input) {
157
176
  worker_count: input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count,
158
177
  result_path: resultPath,
159
178
  owner_scope: input.node.owner_scope,
160
- maker_artifacts: input.makerArtifacts || []
179
+ maker_artifacts: input.makerArtifacts || [],
180
+ codex_native_invocation_plan: invocationPlan ? compactInvocationPlan(invocationPlan) : null
161
181
  });
162
182
  const child = await runProcess(process.execPath, [fixtureChildEntrypoint(), childInputPath], {
163
183
  cwd: input.root,
@@ -197,13 +217,14 @@ async function runLoopWorkerFixture(input) {
197
217
  fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
198
218
  };
199
219
  }
200
- function buildLoopNarutoWorkGraph(input, workerCount, executionProfile) {
220
+ function buildLoopNarutoWorkGraph(input, workerCount, executionProfile, invocationPlan) {
201
221
  const profilePayload = executionProfile ? {
202
222
  mode: executionProfile.mode,
203
223
  agent_role_strategy: executionProfile.agent_role_strategy,
204
224
  artifact_path: executionProfile.artifact_path,
205
225
  agent_type_probe_artifact_path: executionProfile.agent_type_probe_artifact_path
206
226
  } : undefined;
227
+ const invocationPayload = invocationPlan ? compactInvocationPlan(invocationPlan) : undefined;
207
228
  const workItems = Array.from({ length: Math.max(1, workerCount) }, (_, index) => {
208
229
  const id = `${input.node.loop_id}-${input.phase}-${index + 1}`;
209
230
  const writeAllowed = input.phase === 'maker';
@@ -233,7 +254,8 @@ function buildLoopNarutoWorkGraph(input, workerCount, executionProfile) {
233
254
  required: input.node.worktree.required,
234
255
  allocation_required: false
235
256
  },
236
- ...(profilePayload ? { codex_app_execution_profile: profilePayload } : {})
257
+ ...(profilePayload ? { codex_app_execution_profile: profilePayload } : {}),
258
+ ...(invocationPayload ? { codex_native_invocation_plan: invocationPayload } : {})
237
259
  };
238
260
  });
239
261
  return {
@@ -255,10 +277,23 @@ function buildLoopNarutoWorkGraph(input, workerCount, executionProfile) {
255
277
  fallback_reason: input.worktree?.path ? 'loop_worktree_already_allocated' : null
256
278
  },
257
279
  ...(profilePayload ? { codex_app_execution_profile: profilePayload } : {}),
280
+ ...(invocationPayload ? { codex_native_invocation_plan: invocationPayload } : {}),
258
281
  blockers: [],
259
282
  ok: true
260
283
  };
261
284
  }
285
+ function compactInvocationPlan(plan) {
286
+ return {
287
+ route: plan.route,
288
+ desired_capability: plan.desired_capability,
289
+ selected_strategy: plan.selected_strategy,
290
+ required_artifacts: plan.required_artifacts,
291
+ proof_policy: plan.proof_policy,
292
+ env: plan.env,
293
+ blockers: plan.blockers,
294
+ warnings: plan.warnings
295
+ };
296
+ }
262
297
  function collectArtifactPaths(result) {
263
298
  return stringArray([
264
299
  result?.ledger_root,
@@ -3,6 +3,7 @@ import { exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic, P
3
3
  import { CODEX_WEB_VERIFICATION_EVIDENCE_SOURCE, CODEX_WEB_VERIFICATION_POLICY, evidenceMentionsForbiddenBrowserAutomation, evidenceMentionsForbiddenWebComputerUseEvidence } from './routes.js';
4
4
  import { appendAgentLedgerEvent, initializeAgentCentralLedger } from './agents/agent-central-ledger.js';
5
5
  import { resolveCodexAppExecutionProfile } from './codex-app/codex-app-execution-profile.js';
6
+ import { resolveCodexNativeInvocationPlan } from './codex-native/codex-native-invocation-router.js';
6
7
  export const QA_LOOP_ROUTE = 'QALoop';
7
8
  const QA_REPORT_SUFFIX = 'qa-report.md';
8
9
  const UI_CHROME_EXTENSION_FIRST_ACK = 'use_codex_chrome_extension_first_no_computer_use_for_web_ui_or_mark_unverified';
@@ -325,6 +326,7 @@ export function defaultQaGate(contract = {}, opts = {}) {
325
326
  codex_app_execution_profile_artifact: opts.executionProfile ? 'qa-loop/execution-profile.json' : null,
326
327
  codex_app_hooks_approval_required: opts.executionProfile?.hooks_approval_required === true,
327
328
  codex_app_agent_role_strategy: opts.executionProfile?.agent_role_strategy || null,
329
+ codex_native_invocation: opts.codexNativeInvocation || null,
328
330
  api_e2e_required: apiRequired,
329
331
  unsafe_external_side_effects: false,
330
332
  corrective_loop_enabled: corrective,
@@ -345,19 +347,23 @@ export async function writeQaLoopArtifacts(dir, mission, contract) {
345
347
  const reportFile = qaReportFilename();
346
348
  const root = missionRootFromDir(dir);
347
349
  const executionProfile = root ? await resolveCodexAppExecutionProfile({ root }).catch(() => null) : null;
350
+ const codexNativeInvocation = root ? await resolveQaCodexNativeInvocation(root, mission.id).catch(() => null) : null;
348
351
  if (executionProfile)
349
352
  await writeJsonAtomic(path.join(dir, 'qa-loop', 'execution-profile.json'), executionProfile).catch(() => undefined);
353
+ if (codexNativeInvocation)
354
+ await writeJsonAtomic(path.join(dir, 'qa-loop', 'codex-native-invocation.json'), codexNativeInvocation).catch(() => undefined);
350
355
  await writeJsonAtomic(path.join(dir, 'qa-ledger.json'), {
351
356
  schema_version: 1,
352
357
  generated_at: nowIso(),
353
358
  mission_id: mission.id,
354
359
  qa_report_file: reportFile,
355
360
  codex_app_execution_profile: executionProfile ? compactExecutionProfile(executionProfile) : null,
361
+ codex_native_invocation: codexNativeInvocation,
356
362
  target: { scope: a.QA_SCOPE, environment: a.TARGET_ENVIRONMENT, base_url: a.TARGET_BASE_URL, api_base_url: a.API_BASE_URL },
357
363
  safety: { mutation_policy: a.QA_MUTATION_POLICY, deployed_destructive_tests_allowed: 'never', credentials: 'temp_only_never_saved', ui_evidence: 'codex_chrome_extension_first_required_for_web_ui_e2e' },
358
364
  checklist
359
365
  });
360
- await writeJsonAtomic(path.join(dir, 'qa-gate.json'), defaultQaGate(contract, { reportFile, executionProfile }));
366
+ await writeJsonAtomic(path.join(dir, 'qa-gate.json'), defaultQaGate(contract, { reportFile, executionProfile, codexNativeInvocation }));
361
367
  await writeTextAtomic(path.join(dir, reportFile), qaReportTemplate(mission, contract, checklist));
362
368
  return { checklist_count: checklist.length, report_file: reportFile };
363
369
  }
@@ -446,6 +452,7 @@ export async function writeMockQaResult(dir, mission, contract) {
446
452
  codex_app_execution_profile_artifact: previousGate.codex_app_execution_profile_artifact || null,
447
453
  codex_app_hooks_approval_required: previousGate.codex_app_hooks_approval_required === true,
448
454
  codex_app_agent_role_strategy: previousGate.codex_app_agent_role_strategy || null,
455
+ codex_native_invocation: previousGate.codex_native_invocation || null,
449
456
  blockers: previousGate.blockers || [],
450
457
  passed: !uiRequired,
451
458
  qa_report_written: true,
@@ -467,6 +474,22 @@ export async function writeMockQaResult(dir, mission, contract) {
467
474
  });
468
475
  return evaluateQaGate(dir);
469
476
  }
477
+ async function resolveQaCodexNativeInvocation(root, missionId) {
478
+ const [visualReview, hookEvidence, imageFollowup] = await Promise.all([
479
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$QA-LOOP', desiredCapability: 'visual-review' }),
480
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$QA-LOOP', desiredCapability: 'hook-evidence' }),
481
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Image', desiredCapability: 'image-followup' })
482
+ ]);
483
+ return {
484
+ visual_review: visualReview.selected_strategy,
485
+ visual_review_plan: visualReview,
486
+ hook_evidence_policy: hookEvidence.env.SKS_CODEX_NATIVE_HOOK_EVIDENCE_POLICY,
487
+ hook_evidence_plan: hookEvidence,
488
+ image_path_strategy: imageFollowup.selected_strategy === 'codex-app-native' ? 'model-visible-path' : 'artifact-path',
489
+ image_followup_plan: imageFollowup,
490
+ hook_derived_evidence_counted: hookEvidence.selected_strategy !== 'blocked'
491
+ };
492
+ }
470
493
  export function buildQaLoopPrompt({ id, mission, contract, cycle, previous, reportFile, imagePathContract, appHandoff, executionProfile }) {
471
494
  const report = reportFile && isQaReportFilename(reportFile) ? reportFile : 'the date/version-prefixed report named by qa-gate.json.qa_report_file';
472
495
  const imageContractText = imagePathContract
@@ -18,6 +18,7 @@ import { RESEARCH_WORK_GRAPH_ARTIFACT, writeResearchWorkGraph } from './research
18
18
  import { researchPromptContractText, validateResearchPromptContract } from './research/research-prompt-contract.js';
19
19
  import { buildRealisticResearchPaper, buildRealisticResearchReport } from './research/research-realistic-report.js';
20
20
  import { resolveCodexAppExecutionProfile } from './codex-app/codex-app-execution-profile.js';
21
+ import { resolveCodexNativeInvocationPlan } from './codex-native/codex-native-invocation-router.js';
21
22
  export const RESEARCH_PAPER_ARTIFACT = 'research-paper.md';
22
23
  export const RESEARCH_SOURCE_SKILL_ARTIFACT = 'research-source-skill.md';
23
24
  export const RESEARCH_GENIUS_SUMMARY_ARTIFACT = 'genius-opinion-summary.md';
@@ -254,6 +255,14 @@ export function createResearchPlan(prompt, opts = {}) {
254
255
  const paperArtifact = researchPaperArtifactName(prompt, createdAt, opts);
255
256
  const nativeAgentPlan = researchNativeAgentPlan(prompt, { paperArtifact, missionId: opts.missionId });
256
257
  const executionProfile = opts.executionProfile || null;
258
+ const codexNativeInvocation = opts.codexNativeInvocation || null;
259
+ const sourceStrategy = codexNativeInvocation?.mcp_source?.selected_strategy === 'codex-app-native'
260
+ ? 'mcp-plugin-candidates'
261
+ : codexNativeInvocation?.web_search?.selected_strategy === 'codex-cli-headless'
262
+ ? 'web-sources'
263
+ : executionProfile?.plugin_mcp_inventory_ready
264
+ ? 'mcp-plugin-candidates'
265
+ : 'local-files';
257
266
  return {
258
267
  schema_version: 1,
259
268
  mission_id: opts.missionId || null,
@@ -265,6 +274,7 @@ export function createResearchPlan(prompt, opts = {}) {
265
274
  quality_contract: DEFAULT_RESEARCH_QUALITY_CONTRACT,
266
275
  native_agent_plan: nativeAgentPlan,
267
276
  codex_app_execution_profile: executionProfile ? compactExecutionProfile(executionProfile) : null,
277
+ codex_native_invocation: codexNativeInvocation,
268
278
  agent_sessions: nativeAgentPlan.personas,
269
279
  agent_batches: nativeAgentPlan.batches,
270
280
  autoresearch_cycle_policy: nativeAgentPlan.autoresearch_cycle_policy,
@@ -322,10 +332,11 @@ export function createResearchPlan(prompt, opts = {}) {
322
332
  mode: 'layered_source_retrieval_and_triangulation',
323
333
  requirement: 'Use every safely available public web/source route before synthesis, separated into source layers so the final claim is not dominated by one corpus or platform.',
324
334
  source_tool_routing: {
325
- mode: executionProfile?.plugin_mcp_inventory_ready ? 'plugin-mcp-inventory-first' : 'codex-cli-or-web-fallback',
335
+ mode: sourceStrategy,
326
336
  plugin_mcp_inventory_ready: executionProfile?.plugin_mcp_inventory_ready === true,
327
337
  execution_profile_artifact: executionProfile?.artifact_path || '.sneakoscope/reports/codex-app-execution-profile.json',
328
- rule: 'Prefer verified plugin/MCP inventory when available; otherwise record source-tool blockers instead of assuming live search coverage.'
338
+ codex_native_invocation_artifact: codexNativeInvocation ? 'research/codex-native-invocation.json' : null,
339
+ rule: 'Prefer verified plugin/MCP candidates when available; otherwise record source-tool blockers instead of assuming live search coverage.'
329
340
  },
330
341
  query_sets: [
331
342
  'first-principles and theory sources',
@@ -553,7 +564,9 @@ export function countGeniusOpinionSummaries(text = '') {
553
564
  export async function writeResearchPlan(dir, prompt, opts = {}) {
554
565
  const root = opts.root || missionRootFromDir(String(dir || ''));
555
566
  const executionProfile = opts.executionProfile || (root ? await resolveCodexAppExecutionProfile({ root }).catch(() => null) : null);
556
- const plan = createResearchPlan(prompt, { ...opts, executionProfile });
567
+ const missionId = opts.missionId || path.basename(String(dir || ''));
568
+ const codexNativeInvocation = root ? await resolveResearchCodexNativeInvocation(root, missionId).catch(() => null) : null;
569
+ const plan = createResearchPlan(prompt, { ...opts, executionProfile, codexNativeInvocation });
557
570
  const noveltyLedger = {
558
571
  schema_version: 1,
559
572
  entries: [],
@@ -571,6 +584,8 @@ export async function writeResearchPlan(dir, prompt, opts = {}) {
571
584
  await writeJsonAtomic(path.join(dir, 'research-plan.json'), plan);
572
585
  if (executionProfile)
573
586
  await writeJsonAtomic(path.join(dir, 'research', 'execution-profile.json'), executionProfile).catch(() => undefined);
587
+ if (codexNativeInvocation)
588
+ await writeJsonAtomic(path.join(dir, 'research', 'codex-native-invocation.json'), codexNativeInvocation).catch(() => undefined);
574
589
  await writeTextAtomic(path.join(dir, 'research-plan.md'), researchPlanMarkdown(plan));
575
590
  await writeTextAtomic(path.join(dir, RESEARCH_SOURCE_SKILL_ARTIFACT), researchSourceSkillMarkdown(plan));
576
591
  await writeResearchQualityContract(dir, plan.quality_contract);
@@ -593,6 +608,24 @@ export async function writeResearchPlan(dir, prompt, opts = {}) {
593
608
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.plan.created', depth: plan.depth });
594
609
  return plan;
595
610
  }
611
+ async function resolveResearchCodexNativeInvocation(root, missionId) {
612
+ const [pluginSource, mcpSource, webSearch] = await Promise.all([
613
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Research', desiredCapability: 'plugin-source' }),
614
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Research', desiredCapability: 'mcp-source' }),
615
+ resolveCodexNativeInvocationPlan({ root, missionId, route: '$Research', desiredCapability: 'web-search' })
616
+ ]);
617
+ return {
618
+ plugin_source: pluginSource,
619
+ mcp_source: mcpSource,
620
+ web_search: webSearch,
621
+ selected_source_strategy: mcpSource.selected_strategy === 'codex-app-native'
622
+ ? 'mcp-plugin-candidates'
623
+ : webSearch.selected_strategy === 'codex-cli-headless'
624
+ ? 'web-sources'
625
+ : 'local-files',
626
+ hook_derived_source_evidence_allowed: false
627
+ };
628
+ }
596
629
  function missionRootFromDir(dir) {
597
630
  const normalized = path.resolve(String(dir || ''));
598
631
  const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;