sneakoscope 3.1.4 → 3.1.6

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 (68) hide show
  1. package/README.md +8 -36
  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/cli/install-helpers.js +56 -4
  9. package/dist/commands/codex-app.js +1 -11
  10. package/dist/commands/codex-lb.js +12 -9
  11. package/dist/commands/codex-native.js +68 -0
  12. package/dist/commands/doctor.js +64 -0
  13. package/dist/core/codex-app/codex-agent-role-sync.js +69 -11
  14. package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
  15. package/dist/core/codex-app/codex-app-execution-profile.js +43 -14
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
  17. package/dist/core/codex-app/codex-app-harness-matrix.js +4 -65
  18. package/dist/core/codex-app/codex-app-types.js +21 -0
  19. package/dist/core/codex-app/codex-hook-approval-probe.js +188 -0
  20. package/dist/core/codex-app/codex-hook-lifecycle.js +34 -10
  21. package/dist/core/codex-app/codex-init-deep.js +154 -3
  22. package/dist/core/codex-app/codex-skill-sync.js +84 -9
  23. package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
  24. package/dist/core/codex-native/codex-native-feature-broker.js +182 -0
  25. package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
  26. package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
  27. package/dist/core/codex-native/codex-native-interop-policy.js +58 -0
  28. package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
  29. package/dist/core/codex-native/codex-native-pattern-analysis.js +56 -0
  30. package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
  31. package/dist/core/codex-native/codex-native-reference-source.js +110 -0
  32. package/dist/core/codex-native/codex-native-rename-map.js +25 -0
  33. package/dist/core/commands/mad-sks-command.js +37 -2
  34. package/dist/core/commands/qa-loop-command.js +3 -2
  35. package/dist/core/commands/research-command.js +2 -2
  36. package/dist/core/doctor/doctor-zellij-repair.js +5 -1
  37. package/dist/core/feature-fixtures.js +3 -4
  38. package/dist/core/feature-registry.js +5 -2
  39. package/dist/core/fsx.js +1 -1
  40. package/dist/core/image/image-artifact-path-contract.js +18 -1
  41. package/dist/core/init.js +4 -1
  42. package/dist/core/loops/loop-continuation-enforcer.js +11 -3
  43. package/dist/core/loops/loop-owner-inference.js +3 -0
  44. package/dist/core/loops/loop-planner.js +28 -5
  45. package/dist/core/loops/loop-worker-runtime.js +52 -8
  46. package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
  47. package/dist/core/qa-loop.js +62 -4
  48. package/dist/core/research/research-cycle-runner.js +1 -0
  49. package/dist/core/research/research-stage-runner.js +9 -2
  50. package/dist/core/research.js +68 -1
  51. package/dist/core/routes.js +2 -3
  52. package/dist/core/version.js +1 -1
  53. package/dist/core/zellij/homebrew-policy.js +0 -1
  54. package/dist/core/zellij/zellij-self-heal-types.js +45 -0
  55. package/dist/core/zellij/zellij-self-heal.js +69 -8
  56. package/dist/core/zellij/zellij-update.js +9 -1
  57. package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
  58. package/dist/scripts/sks-3-1-5-directive-check-lib.js +318 -0
  59. package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
  60. package/package.json +53 -9
  61. package/dist/cli/hermes-command.js +0 -99
  62. package/dist/cli/openclaw-command.js +0 -83
  63. package/dist/commands/hermes.js +0 -5
  64. package/dist/commands/openclaw.js +0 -3
  65. package/dist/core/codex-app/lazycodex-analysis.js +0 -51
  66. package/dist/core/codex-app/lazycodex-interop-policy.js +0 -49
  67. package/dist/core/hermes.js +0 -192
  68. package/dist/core/openclaw.js +0 -171
@@ -28,17 +28,52 @@ export async function madHighCommand(args = [], deps = {}) {
28
28
  if (subcommand)
29
29
  return madSksSubcommand(subcommand, args.filter((arg) => String(arg) !== subcommand));
30
30
  const cleanArgs = stripMadLaunchOnlyArgs(args);
31
- if (args.includes('--json')) {
31
+ const rawArgs = (args || []).map((arg) => String(arg));
32
+ const dryRun = rawArgs.includes('--dry-run');
33
+ if (args.includes('--json') && !dryRun) {
32
34
  const profile = buildMadHighLaunchProfileNoWrite();
33
35
  return console.log(JSON.stringify(profile, null, 2));
34
36
  }
35
37
  const update = { status: 'notice_only', non_blocking: true };
36
- const rawArgs = (args || []).map((arg) => String(arg));
37
38
  const headlessZellij = rawArgs.includes('--headless') || process.env.SKS_MAD_ALLOW_HEADLESS === '1';
38
39
  const skipZellijRepair = rawArgs.includes('--skip-zellij-repair') || rawArgs.includes('--no-auto-install-zellij');
39
40
  const launchRoot = process.cwd();
40
41
  if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
41
42
  await initProject(launchRoot, {});
43
+ if (dryRun) {
44
+ const zellijPlan = skipZellijRepair
45
+ ? { schema: 'sks.zellij-self-heal.v1', ok: true, status: 'skipped', dry_run: true, planned_mutations: [], command: null, blockers: [], warnings: ['zellij_repair_skipped'] }
46
+ : await repairZellijForSks({
47
+ root: launchRoot,
48
+ requestedBy: 'sks --mad',
49
+ fixRequested: true,
50
+ autoApprove: rawArgs.includes('--yes') || rawArgs.includes('-y'),
51
+ interactive: false,
52
+ installHomebrew: rawArgs.includes('--install-homebrew'),
53
+ allowHeadlessFallback: headlessZellij,
54
+ dryRun: true
55
+ });
56
+ const report = {
57
+ schema: 'sks.mad-sks-zellij-dry-run.v1',
58
+ ok: zellijPlan.ok === true,
59
+ status: zellijPlan.ok === true ? 'dry_run' : 'repair_required',
60
+ generated_at: nowIso(),
61
+ launch_skipped: true,
62
+ zellij_repair: zellijPlan
63
+ };
64
+ await writeJsonAtomic(path.join(launchRoot, '.sneakoscope', 'reports', 'mad-sks-zellij-dry-run.json'), report);
65
+ if (rawArgs.includes('--json'))
66
+ console.log(JSON.stringify(report, null, 2));
67
+ else {
68
+ console.log(`SKS MAD dry-run: launch_skipped=true status=${report.status}`);
69
+ const planned = Array.isArray(zellijPlan.planned_mutations) ? zellijPlan.planned_mutations : [];
70
+ for (const row of planned)
71
+ console.log(`- plan: ${row.command}`);
72
+ if (zellijPlan.command && planned.length === 0)
73
+ console.log(`- run: ${zellijPlan.command}`);
74
+ }
75
+ return report;
76
+ }
42
77
  const codexUpdate = deps.maybePromptCodexUpdateForLaunch ? await deps.maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' }) : { status: 'skipped' };
43
78
  if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
44
79
  console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
@@ -146,6 +146,7 @@ async function qaLoopRun(args) {
146
146
  const mock = flag(args, '--mock');
147
147
  const qaGate = await readJson(path.join(dir, 'qa-gate.json'), {});
148
148
  const reportFile = qaGate.qa_report_file;
149
+ const executionProfile = await readJson(path.join(dir, 'qa-loop', 'execution-profile.json'), null);
149
150
  const uiRequired = qaUiRequired(contract.answers || {});
150
151
  const capabilityArtifact = await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch((err) => ({ error: err?.message || String(err), report: null }));
151
152
  const usageArtifact = await writeCodexAccountUsageArtifacts(root, { missionId: id }).catch((err) => ({ error: err?.message || String(err), snapshot: null }));
@@ -273,7 +274,7 @@ async function qaLoopRun(args) {
273
274
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'qaloop.run.started', maxCycles, mock });
274
275
  const nativeAgentPlan = await readJson(path.join(dir, 'qa-agent-plan.json'), null);
275
276
  const nativeRoster = requestedAgents === 3 ? nativeAgentPlan : null;
276
- const nativeAgentRun = await runNativeAgentOrchestrator({ root, missionId: id, route: '$QA-LOOP', prompt: mission.prompt || 'QA-LOOP run', backend: mock ? 'fake' : 'codex-sdk', mock, agents: requestedAgents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency: Math.min(requestedAgents, 5), readonly: !(applyPatches && writeMode !== 'off'), profile, writeMode: writeMode, applyPatches, dryRunPatches, maxWriteAgents, roster: nativeRoster, routeCommand: 'sks qa-loop run', routeBlackboxKind: 'actual_qa_command' });
277
+ const nativeAgentRun = await runNativeAgentOrchestrator({ root, missionId: id, route: '$QA-LOOP', prompt: mission.prompt || 'QA-LOOP run', backend: mock ? 'fake' : 'codex-sdk', mock, agents: requestedAgents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency: Math.min(requestedAgents, 5), readonly: !(applyPatches && writeMode !== 'off'), profile, writeMode: writeMode, applyPatches, dryRunPatches, maxWriteAgents, roster: nativeRoster, routeCommand: 'sks qa-loop run', routeBlackboxKind: 'actual_qa_command', env: { SKS_CODEX_APP_EXECUTION_PROFILE: executionProfile?.mode || 'unknown', SKS_CODEX_AGENT_ROLE_STRATEGY: executionProfile?.agent_role_strategy || 'message-role' } });
277
278
  await writeJsonAtomic(path.join(dir, 'qa-native-agent-run.json'), nativeAgentRun);
278
279
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'qaloop.native_agents.completed', backend: nativeAgentRun.backend, ok: nativeAgentRun.ok, proof: nativeAgentRun.proof?.status });
279
280
  if (mock) {
@@ -343,7 +344,7 @@ async function qaLoopRun(args) {
343
344
  for (let cycle = 1; cycle <= maxCycles; cycle += 1) {
344
345
  const cycleDir = path.join(dir, 'qa-loop', `cycle-${cycle}`);
345
346
  const outputFile = path.join(cycleDir, 'final.md');
346
- const prompt = buildQaLoopPrompt({ id, mission, contract, cycle, previous: last, reportFile, imagePathContract: imagePathContract?.contract || null, appHandoff });
347
+ const prompt = buildQaLoopPrompt({ id, mission, contract, cycle, previous: last, reportFile, imagePathContract: imagePathContract?.contract || null, appHandoff, executionProfile });
347
348
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'qaloop.cycle.start', cycle });
348
349
  const result = await runCodexExec({ root, prompt, outputFile, json: true, profile, logDir: cycleDir });
349
350
  await writeJsonAtomic(path.join(cycleDir, 'process.json'), { code: result.code, stdout_tail: result.stdout, stderr_tail: result.stderr, stdout_bytes: result.stdoutBytes, stderr_bytes: result.stderrBytes, truncated: result.truncated, timed_out: result.timedOut });
@@ -52,7 +52,7 @@ async function researchPrepare(args) {
52
52
  const context7Required = routeNeedsContext7(route, prompt);
53
53
  const reasoning = routeReasoning(route, prompt);
54
54
  const autoresearch = flag(args, '--autoresearch');
55
- const plan = await writeResearchPlan(dir, prompt, { depth: readFlagValue(args, '--depth', 'frontier'), missionId: id, autoresearch });
55
+ const plan = await writeResearchPlan(dir, prompt, { root, depth: readFlagValue(args, '--depth', 'frontier'), missionId: id, autoresearch });
56
56
  const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task: prompt, required: context7Required, ambiguity: { required: false, status: 'direct_research_cli' } });
57
57
  await writeJsonAtomic(path.join(dir, 'route-context.json'), {
58
58
  route: route.id,
@@ -114,7 +114,7 @@ async function researchRun(args) {
114
114
  const { dir, mission } = await loadMission(root, id);
115
115
  const planPath = path.join(dir, 'research-plan.json');
116
116
  if (!(await exists(planPath)))
117
- await writeResearchPlan(dir, mission.prompt || '', { missionId: id, autoresearch: flag(args, '--autoresearch') });
117
+ await writeResearchPlan(dir, mission.prompt || '', { root, missionId: id, autoresearch: flag(args, '--autoresearch') });
118
118
  const plan = await readJson(planPath);
119
119
  const dbScan = await scanDbSafety(root);
120
120
  if (!dbScan.ok) {
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
3
2
  export async function runDoctorZellijRepair(input) {
4
3
  const args = (input.args || []).map(String);
@@ -10,6 +9,7 @@ export async function runDoctorZellijRepair(input) {
10
9
  fixRequested: true,
11
10
  autoApprove: args.includes('--yes') || args.includes('-y'),
12
11
  installHomebrew: args.includes('--install-homebrew') || process.env.SKS_ALLOW_HOMEBREW_INSTALL === '1',
12
+ dryRun: args.includes('--dry-run'),
13
13
  interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.SKS_NO_QUESTION !== '1'),
14
14
  allowHeadlessFallback: false,
15
15
  env: process.env
@@ -18,6 +18,10 @@ export async function runDoctorZellijRepair(input) {
18
18
  export function doctorZellijRepairConsoleLine(result) {
19
19
  if (!result)
20
20
  return null;
21
+ if (result.dry_run) {
22
+ const planned = result.planned_mutations.map((row) => row.command).join(' && ') || result.command || 'none';
23
+ return `Zellij repair: dry_run planned ${planned}`;
24
+ }
21
25
  if (result.strategy === 'none-current')
22
26
  return `Zellij repair: current ${result.after.version || ''}`.trim();
23
27
  if (result.ok && (result.strategy === 'brew-install-zellij' || result.strategy === 'brew-install-homebrew-then-zellij')) {
@@ -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'),
@@ -107,6 +106,7 @@ const FIXTURES = Object.freeze({
107
106
  'route-from-chat-img': fixture('mock', '$From-Chat-IMG visual work order route', ['from-chat-img-work-order.md', 'image-voxel-ledger.json', 'completion-proof.json'], 'pass'),
108
107
  'route-ux-review': fixture('mock', '$UX-Review image UX alias route', ['image-ux-generated-review-ledger.json', 'image-voxel-ledger.json'], 'pass'),
109
108
  'route-db': fixture('execute_and_validate_artifacts', 'sks db check --sql "SELECT 1" --json', ['completion-proof.json', 'db-operation-report.json'], 'pass'),
109
+ 'route-mad-db': fixture('mock', '$MAD-DB one-cycle DB break-glass route contract', ['mad-db-capability.json', 'mad-db-ledger.jsonl', 'completion-proof.json'], 'pass'),
110
110
  'route-wiki': fixture('execute_and_validate_artifacts', 'sks wiki image-ingest test/fixtures/images/one-by-one.png --json', [{ path: 'completion-proof.json', schema: 'sks.completion-proof.v1' }, { path: 'image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1' }], 'pass'),
111
111
  'route-gx': fixture('execute_and_validate_artifacts', 'sks gx validate fixture --mock --json', ['completion-proof.json', { path: 'image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1' }, 'gx-validation.json'], 'pass'),
112
112
  'route-sks': fixture('mock', '$SKS control-surface route', ['completion-proof.json'], 'pass'),
@@ -128,8 +128,7 @@ const STATIC_CONTRACT_FEATURES = new Set([
128
128
  'cli-bootstrap',
129
129
  'cli-deps',
130
130
  'cli-auth',
131
- 'cli-openclaw',
132
- 'cli-hermes',
131
+ 'cli-codex-native',
133
132
  'cli-zellij',
134
133
  'cli-tmux',
135
134
  'cli-mad',
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
- import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS } from './routes.js';
5
+ import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, ROUTES } from './routes.js';
6
6
  import { FEATURE_QUALITY_LEVELS, fixtureForFeature, fixtureSummary, validateFeatureFixtures } from './feature-fixtures.js';
7
7
  import { runFeatureFixture, writeFeatureFixtureReports } from './feature-fixture-runner.js';
8
8
  import { PACKAGE_VERSION, exists, nowIso, packageRoot, readJson, readText, runProcess, writeJsonAtomic, writeTextAtomic } from './fsx.js';
@@ -46,6 +46,9 @@ export async function buildFeatureRegistry({ root = packageRoot(), generatedAt =
46
46
  }
47
47
  for (const route of DOLLAR_COMMANDS)
48
48
  features.push(routeFeature(route));
49
+ for (const route of ROUTES.filter((entry) => entry.hidden === true)) {
50
+ features.push(routeFeature(route));
51
+ }
49
52
  features.push(nativeAgentIntakeFeature());
50
53
  features.push(agentProofEvidenceFeature());
51
54
  for (const skillName of skillNames) {
@@ -810,7 +813,7 @@ function commandCategory(name) {
810
813
  return 'proof-route';
811
814
  if (['qa-loop', 'research', 'recallpulse', 'skill-dream', 'eval', 'perf'].includes(name))
812
815
  return 'loop';
813
- 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))
814
817
  return 'integration';
815
818
  if (['db', 'guard', 'conflicts', 'harness', 'versioning'].includes(name))
816
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.4';
8
+ export const PACKAGE_VERSION = '3.1.6';
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
  }
package/dist/core/init.js CHANGED
@@ -639,7 +639,10 @@ export async function initProject(root, opts = {}) {
639
639
  const baseUrl = globalConfig.match(/(^|\n)\[model_providers\.codex-lb\][\s\S]*?\n\s*base_url\s*=\s*"([^"]+)"/)?.[2] || parseCodexLbEnvBaseUrl(envText);
640
640
  if (!parseCodexLbEnvKey(envText) || !baseUrl)
641
641
  return next;
642
- next = upsertTopLevelTomlString(next, 'model_provider', 'codex-lb');
642
+ const shouldSelectCodexLb = selectedRe.test(next) || selectedRe.test(globalConfig);
643
+ next = shouldSelectCodexLb
644
+ ? upsertTopLevelTomlString(next, 'model_provider', 'codex-lb')
645
+ : removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
643
646
  next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = false`);
644
647
  return `${next.trim()}\n`;
645
648
  }
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import path from 'node:path';
3
2
  import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
4
3
  import { loopPlanPath } from './loop-artifacts.js';
@@ -8,9 +7,9 @@ export async function evaluateLoopContinuation(input) {
8
7
  const blockers = [];
9
8
  if (!plan)
10
9
  blockers.push('loop_plan_missing');
11
- const nodes = plan?.graph?.nodes || [];
10
+ const nodes = loopNodes(plan);
12
11
  const proofs = await Promise.all(nodes.map((node) => readJson(path.join(root, '.sneakoscope', 'missions', input.missionId, 'loops', node.loop_id, 'loop-proof.json'), null)));
13
- const completed = proofs.filter((proof) => proof?.status === 'completed').length;
12
+ const completed = proofs.filter((proof) => isRecord(proof) && proof.status === 'completed').length;
14
13
  const incomplete = Math.max(0, nodes.length - completed);
15
14
  const shouldContinue = Boolean(plan && incomplete > 0 && blockers.length === 0);
16
15
  const report = {
@@ -29,4 +28,13 @@ export async function evaluateLoopContinuation(input) {
29
28
  await writeJsonAtomic(path.join(root, '.sneakoscope', 'missions', input.missionId, 'loop-continuation-enforcer.json'), report).catch(() => undefined);
30
29
  return report;
31
30
  }
31
+ function loopNodes(value) {
32
+ if (!isRecord(value) || !isRecord(value.graph) || !Array.isArray(value.graph.nodes))
33
+ return [];
34
+ return value.graph.nodes
35
+ .filter((node) => isRecord(node) && typeof node.loop_id === 'string');
36
+ }
37
+ function isRecord(value) {
38
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
39
+ }
32
40
  //# sourceMappingURL=loop-continuation-enforcer.js.map
@@ -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'];
@@ -5,7 +5,7 @@ import { selectLoopGates } from './loop-gate-selector.js';
5
5
  import { inferLoopOwnerScope } from './loop-owner-inference.js';
6
6
  import { classifyLoopRisk } from './loop-risk-classifier.js';
7
7
  import { defaultLoopBudget, validateLoopPlan } from './loop-schema.js';
8
- import { readInitDeepMemory } from '../codex-app/codex-init-deep.js';
8
+ import { readInitDeepMemory, readInitDeepMemoryHints } from '../codex-app/codex-init-deep.js';
9
9
  export async function planLoopsFromRequest(input) {
10
10
  const parallelism = input.parallelism || 'balanced';
11
11
  const maxLoops = Math.max(1, Math.min(32, input.maxLoops || 8));
@@ -53,6 +53,16 @@ export async function planLoopsFromRequest(input) {
53
53
  })
54
54
  };
55
55
  const nodes = [...actionNodes, integrationNode];
56
+ const memoryHints = await readInitDeepMemoryHints(input.root, scopePathsForNodes(nodes)).catch(() => []);
57
+ const nodesWithMemory = nodes.map((node) => {
58
+ const hints = memoryHints.filter((hint) => hintAppliesToNode(hint, node)).slice(0, 5);
59
+ return {
60
+ ...node,
61
+ ...(hints.length ? { memory_hints: hints } : {}),
62
+ memory_hints_used: hints.length,
63
+ memory_did_not_expand_scope: true
64
+ };
65
+ });
56
66
  const plan = {
57
67
  schema: 'sks.loop-plan.v1',
58
68
  mission_id: input.missionId,
@@ -64,12 +74,12 @@ export async function planLoopsFromRequest(input) {
64
74
  confidence: actionNodes.length ? 'high' : 'medium'
65
75
  },
66
76
  graph: {
67
- nodes,
77
+ nodes: nodesWithMemory,
68
78
  edges: actionNodes.map((node) => ({ from: node.loop_id, to: integrationNode.loop_id, reason: 'integration_after_loop_proof' }))
69
79
  },
70
80
  global_budget: defaultLoopBudget({
71
- max_iterations: Math.max(...nodes.map((node) => node.budget.max_iterations)),
72
- max_subagents: nodes.reduce((sum, node) => sum + node.budget.max_subagents, 0)
81
+ max_iterations: Math.max(...nodesWithMemory.map((node) => node.budget.max_iterations)),
82
+ max_subagents: nodesWithMemory.reduce((sum, node) => sum + node.budget.max_subagents, 0)
73
83
  }),
74
84
  safety: {
75
85
  no_unrequested_fallback_code: true,
@@ -89,7 +99,8 @@ export async function planLoopsFromRequest(input) {
89
99
  plan.project_memory = {
90
100
  source: projectMemory.path,
91
101
  injected: true,
92
- 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
93
104
  };
94
105
  }
95
106
  const validation = validateLoopPlan(plan);
@@ -176,4 +187,16 @@ function dynamicCheckerWorkerCount(input) {
176
187
  function titleFromDomain(domainId) {
177
188
  return domainId === 'loop-general-coding' ? 'General coding loop' : `${domainId} loop`;
178
189
  }
190
+ function scopePathsForNodes(nodes) {
191
+ return nodes.flatMap((node) => [
192
+ ...node.owner_scope.files,
193
+ ...node.owner_scope.directories
194
+ ]).filter(Boolean);
195
+ }
196
+ function hintAppliesToNode(hint, node) {
197
+ if (hint.scope === '.')
198
+ return true;
199
+ const scopes = [...node.owner_scope.files, ...node.owner_scope.directories].map((value) => value.replace(/^\.?\//, ''));
200
+ return scopes.some((scope) => scope === hint.scope || scope.startsWith(`${hint.scope}/`) || hint.scope.startsWith(`${scope}/`));
201
+ }
179
202
  //# sourceMappingURL=loop-planner.js.map
@@ -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
  }
@@ -43,7 +44,14 @@ async function runLoopWorkerNative(input) {
43
44
  ? buildLoopMakerPrompt({ plan: input.plan, node: input.node, worktreePath: input.worktree?.path || null })
44
45
  : buildLoopCheckerPrompt({ plan: input.plan, node: input.node, makerArtifacts: input.makerArtifacts || [] });
45
46
  const workerCount = effectiveLoopWorkerCount(input);
46
- const workGraph = buildLoopNarutoWorkGraph(input, workerCount);
47
+ const executionProfile = await resolveCodexAppExecutionProfile({ root: input.root }).catch(() => null);
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);
47
55
  // Root-cause-1 fix: keep the ORCHESTRATOR root on the MAIN repo (input.root), not the
48
56
  // loop worktree. All zellij/right-column/slot-telemetry state derives from the orchestrator
49
57
  // root, so anchoring it on input.root makes the SLOTS snapshot land under
@@ -52,7 +60,6 @@ async function runLoopWorkerNative(input) {
52
60
  // The loop worktree is still where workers cwd + write: it is threaded through the per-worker
53
61
  // `worktree` opt below, which launchWorker reads as ctx.opts.worktree -> workerCwd.
54
62
  const insideZellij = Boolean(process.env.SKS_ZELLIJ_SESSION_NAME || process.env.ZELLIJ);
55
- const executionProfile = await resolveCodexAppExecutionProfile({ root: input.root }).catch(() => null);
56
63
  const visiblePaneCap = Math.min(resolveLoopVisiblePaneCap(workerCount), Math.max(1, workerCount));
57
64
  const zellijPlacementOpts = insideZellij ? {
58
65
  workerPlacement: 'zellij-pane',
@@ -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);
112
+ return normalizeNativeResult(input, orchestrator, executionProfile, invocationPlan);
103
113
  }
104
- async function normalizeNativeResult(input, result) {
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 = [
@@ -124,7 +134,18 @@ async function normalizeNativeResult(input, result) {
124
134
  blockers: [...new Set(blockers)],
125
135
  runtime_proof_path: proofPath,
126
136
  worker_ids: stringArray(result?.results?.map?.((row) => row?.agent_id || row?.id)),
127
- session_ids: stringArray(result?.results?.map?.((row) => row?.session_id))
137
+ session_ids: stringArray(result?.results?.map?.((row) => row?.session_id)),
138
+ ...(executionProfile ? {
139
+ codex_app_execution_profile: {
140
+ mode: executionProfile.mode,
141
+ agent_role_strategy: executionProfile.agent_role_strategy,
142
+ artifact_path: executionProfile.artifact_path,
143
+ agent_type_probe_artifact_path: executionProfile.agent_type_probe_artifact_path
144
+ }
145
+ } : {}),
146
+ ...(invocationPlan ? {
147
+ codex_native_invocation_plan: compactInvocationPlan(invocationPlan)
148
+ } : {})
128
149
  };
129
150
  await writeJsonAtomic(proofPath, { ...normalized, native_result_summary: summarizeNativeResult(result), generated_at: nowIso() });
130
151
  return normalized;
@@ -189,7 +210,14 @@ async function runLoopWorkerFixture(input) {
189
210
  fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
190
211
  };
191
212
  }
192
- function buildLoopNarutoWorkGraph(input, workerCount) {
213
+ function buildLoopNarutoWorkGraph(input, workerCount, executionProfile, invocationPlan) {
214
+ const profilePayload = executionProfile ? {
215
+ mode: executionProfile.mode,
216
+ agent_role_strategy: executionProfile.agent_role_strategy,
217
+ artifact_path: executionProfile.artifact_path,
218
+ agent_type_probe_artifact_path: executionProfile.agent_type_probe_artifact_path
219
+ } : undefined;
220
+ const invocationPayload = invocationPlan ? compactInvocationPlan(invocationPlan) : undefined;
193
221
  const workItems = Array.from({ length: Math.max(1, workerCount) }, (_, index) => {
194
222
  const id = `${input.node.loop_id}-${input.phase}-${index + 1}`;
195
223
  const writeAllowed = input.phase === 'maker';
@@ -218,7 +246,9 @@ function buildLoopNarutoWorkGraph(input, workerCount) {
218
246
  mode: input.worktree?.path ? 'patch-envelope-only' : 'git-worktree',
219
247
  required: input.node.worktree.required,
220
248
  allocation_required: false
221
- }
249
+ },
250
+ ...(profilePayload ? { codex_app_execution_profile: profilePayload } : {}),
251
+ ...(invocationPayload ? { codex_native_invocation_plan: invocationPayload } : {})
222
252
  };
223
253
  });
224
254
  return {
@@ -239,10 +269,24 @@ function buildLoopNarutoWorkGraph(input, workerCount) {
239
269
  worktree_root: null,
240
270
  fallback_reason: input.worktree?.path ? 'loop_worktree_already_allocated' : null
241
271
  },
272
+ ...(profilePayload ? { codex_app_execution_profile: profilePayload } : {}),
273
+ ...(invocationPayload ? { codex_native_invocation_plan: invocationPayload } : {}),
242
274
  blockers: [],
243
275
  ok: true
244
276
  };
245
277
  }
278
+ function compactInvocationPlan(plan) {
279
+ return {
280
+ route: plan.route,
281
+ desired_capability: plan.desired_capability,
282
+ selected_strategy: plan.selected_strategy,
283
+ required_artifacts: plan.required_artifacts,
284
+ proof_policy: plan.proof_policy,
285
+ env: plan.env,
286
+ blockers: plan.blockers,
287
+ warnings: plan.warnings
288
+ };
289
+ }
246
290
  function collectArtifactPaths(result) {
247
291
  return stringArray([
248
292
  result?.ledger_root,
@@ -1,7 +1,9 @@
1
- export function routeNarutoLoopWorker(node, role) {
1
+ export function routeNarutoLoopWorker(node, role, profile) {
2
2
  const domain = node.loop_id.replace(/^loop-/, '');
3
3
  const roles = roleLabels(domain);
4
4
  const gates = [...node.gates.triage, ...node.gates.local, ...node.gates.checker, ...node.gates.integration, ...node.gates.final];
5
+ const roleName = role === 'maker' ? roles.maker : roles.checker;
6
+ const strategy = profile?.agent_role_strategy || 'message-role';
5
7
  return {
6
8
  schema: 'sks.naruto-loop-worker-route.v1',
7
9
  loop_id: node.loop_id,
@@ -9,7 +11,10 @@ export function routeNarutoLoopWorker(node, role) {
9
11
  checker_role: roles.checker,
10
12
  prompt: [
11
13
  `loop purpose: ${node.purpose}`,
12
- `role: ${role === 'maker' ? roles.maker : roles.checker}`,
14
+ `role: ${roleName}`,
15
+ `agent role strategy: ${strategy}`,
16
+ `agent_type: ${strategy === 'agent_type' ? roleName.replace(/\s+/g, '-') : '-'}`,
17
+ `message role prefix: ${strategy === 'message-role' ? `Role: ${roleName}.` : '-'}`,
13
18
  `owner files: ${node.owner_scope.files.join(', ') || '-'}`,
14
19
  `owner directories: ${node.owner_scope.directories.join(', ') || '-'}`,
15
20
  `gates: ${gates.join(', ') || '-'}`,
@@ -21,6 +26,10 @@ export function routeNarutoLoopWorker(node, role) {
21
26
  allowed_files: node.owner_scope.files,
22
27
  allowed_directories: node.owner_scope.directories,
23
28
  gates,
29
+ agent_role_strategy: strategy,
30
+ agent_type: strategy === 'agent_type' ? roleName.replace(/\s+/g, '-') : null,
31
+ message_role_prefix: strategy === 'message-role' ? `Role: ${roleName}.` : null,
32
+ execution_profile_artifact: profile?.artifact_path || null,
24
33
  mutation_outside_owner_scope_allowed: false
25
34
  };
26
35
  }