sneakoscope 3.1.3 → 3.1.5
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.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +56 -4
- package/dist/commands/codex-app.js +45 -1
- package/dist/commands/codex-lb.js +12 -9
- package/dist/commands/doctor.js +44 -1
- package/dist/core/codex-app/codex-agent-role-sync.js +119 -0
- package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
- package/dist/core/codex-app/codex-app-execution-profile.js +39 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
- package/dist/core/codex-app/codex-app-harness-matrix.js +127 -0
- package/dist/core/codex-app/codex-app-types.js +21 -0
- package/dist/core/codex-app/codex-hook-approval-probe.js +188 -0
- package/dist/core/codex-app/codex-hook-lifecycle.js +61 -0
- package/dist/core/codex-app/codex-init-deep.js +180 -0
- package/dist/core/codex-app/codex-skill-sync.js +164 -0
- package/dist/core/codex-app/lazycodex-analysis.js +72 -0
- package/dist/core/codex-app/lazycodex-interop-policy.js +60 -0
- package/dist/core/codex-app/lazycodex-live-analyzer.js +98 -0
- package/dist/core/commands/loop-command.js +11 -0
- package/dist/core/commands/mad-sks-command.js +113 -17
- package/dist/core/commands/qa-loop-command.js +3 -2
- package/dist/core/commands/research-command.js +2 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +7 -0
- package/dist/core/doctor/doctor-zellij-repair.js +40 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/feature-registry.js +4 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +13 -0
- package/dist/core/init.js +4 -1
- package/dist/core/loops/loop-continuation-enforcer.js +40 -0
- package/dist/core/loops/loop-planner.js +29 -3
- package/dist/core/loops/loop-worker-runtime.js +27 -7
- package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
- package/dist/core/qa-loop.js +39 -4
- package/dist/core/research/research-cycle-runner.js +1 -0
- package/dist/core/research/research-stage-runner.js +9 -2
- package/dist/core/research.js +35 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/homebrew-policy.js +44 -0
- package/dist/core/zellij/zellij-capability.js +32 -3
- package/dist/core/zellij/zellij-self-heal-types.js +45 -0
- package/dist/core/zellij/zellij-self-heal.js +414 -0
- package/dist/core/zellij/zellij-update.js +39 -6
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +241 -0
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +347 -0
- package/package.json +52 -2
|
@@ -22,29 +22,98 @@ 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 { repairZellijForSks } from '../zellij/zellij-self-heal.js';
|
|
25
26
|
export async function madHighCommand(args = [], deps = {}) {
|
|
26
27
|
const subcommand = firstSubcommand(args);
|
|
27
28
|
if (subcommand)
|
|
28
29
|
return madSksSubcommand(subcommand, args.filter((arg) => String(arg) !== subcommand));
|
|
29
30
|
const cleanArgs = stripMadLaunchOnlyArgs(args);
|
|
30
|
-
|
|
31
|
+
const rawArgs = (args || []).map((arg) => String(arg));
|
|
32
|
+
const dryRun = rawArgs.includes('--dry-run');
|
|
33
|
+
if (args.includes('--json') && !dryRun) {
|
|
31
34
|
const profile = buildMadHighLaunchProfileNoWrite();
|
|
32
35
|
return console.log(JSON.stringify(profile, null, 2));
|
|
33
36
|
}
|
|
34
37
|
const update = { status: 'notice_only', non_blocking: true };
|
|
38
|
+
const headlessZellij = rawArgs.includes('--headless') || process.env.SKS_MAD_ALLOW_HEADLESS === '1';
|
|
39
|
+
const skipZellijRepair = rawArgs.includes('--skip-zellij-repair') || rawArgs.includes('--no-auto-install-zellij');
|
|
40
|
+
const launchRoot = process.cwd();
|
|
41
|
+
if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
|
|
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
|
+
}
|
|
35
77
|
const codexUpdate = deps.maybePromptCodexUpdateForLaunch ? await deps.maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' }) : { status: 'skipped' };
|
|
36
78
|
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
37
79
|
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
38
80
|
process.exitCode = 1;
|
|
39
81
|
return;
|
|
40
82
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
83
|
+
const zellijUpdate = skipZellijRepair
|
|
84
|
+
? { status: 'skipped', command: 'sks doctor --fix --yes' }
|
|
85
|
+
: deps.maybePromptZellijUpdateForLaunch
|
|
86
|
+
? await deps.maybePromptZellijUpdateForLaunch(args, {
|
|
87
|
+
label: 'MAD launch',
|
|
88
|
+
root: launchRoot,
|
|
89
|
+
selfHealOnMissing: true,
|
|
90
|
+
autoApprove: rawArgs.includes('--yes') || rawArgs.includes('-y'),
|
|
91
|
+
installHomebrew: rawArgs.includes('--install-homebrew'),
|
|
92
|
+
allowHeadlessFallback: headlessZellij
|
|
93
|
+
}).catch(() => ({ status: 'error', command: 'sks doctor --fix --yes' }))
|
|
94
|
+
: await repairZellijForSks({
|
|
95
|
+
root: launchRoot,
|
|
96
|
+
requestedBy: 'sks --mad',
|
|
97
|
+
fixRequested: true,
|
|
98
|
+
autoApprove: rawArgs.includes('--yes') || rawArgs.includes('-y'),
|
|
99
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.SKS_NO_QUESTION !== '1'),
|
|
100
|
+
installHomebrew: rawArgs.includes('--install-homebrew'),
|
|
101
|
+
allowHeadlessFallback: headlessZellij
|
|
102
|
+
});
|
|
103
|
+
const zellijRepairBlocked = !headlessZellij && (zellijUpdate.status === 'manual_required'
|
|
104
|
+
|| zellijUpdate.strategy === 'manual-required'
|
|
105
|
+
|| zellijUpdate.ok === false);
|
|
106
|
+
if (zellijRepairBlocked) {
|
|
107
|
+
console.error('SKS MAD launch blocked by Zellij repair_required.');
|
|
108
|
+
console.error(`Run: ${zellijUpdate.command || 'sks doctor --fix --yes'}`);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return { ok: false, status: 'repair_required', command: zellijUpdate.command || 'sks doctor --fix --yes', zellij_repair: zellijUpdate };
|
|
111
|
+
}
|
|
112
|
+
const depStatus = skipZellijRepair && deps.ensureMadLaunchDependencies
|
|
113
|
+
? await deps.ensureMadLaunchDependencies(args)
|
|
114
|
+
: { ready: true, actions: [] };
|
|
46
115
|
if (!depStatus.ready) {
|
|
47
|
-
console.error('SKS MAD launch blocked by
|
|
116
|
+
console.error('SKS MAD launch blocked by required Zellij dependency.');
|
|
48
117
|
for (const action of depStatus.actions)
|
|
49
118
|
deps.printDepsInstallAction?.(action);
|
|
50
119
|
process.exitCode = 1;
|
|
@@ -56,9 +125,6 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
56
125
|
return;
|
|
57
126
|
}
|
|
58
127
|
const profile = buildMadHighLaunchProfileNoWrite();
|
|
59
|
-
const launchRoot = process.cwd();
|
|
60
|
-
if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
|
|
61
|
-
await initProject(launchRoot, {});
|
|
62
128
|
const uiSnapshotId = Date.now().toString(36);
|
|
63
129
|
const beforeUi = await writeCodexAppUiSnapshot(launchRoot, `mad-before-${uiSnapshotId}`).catch(() => null);
|
|
64
130
|
// launchFast skips the redundant live-`codex exec` config probe (up to ~20s, run
|
|
@@ -66,7 +132,6 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
66
132
|
// later when the Zellij session opens. All filesystem/permission/EPERM/symlink/ACL
|
|
67
133
|
// readability + repair checks still run. SKS_LAUNCH_FULL_CODEX_PROBE=1 restores the
|
|
68
134
|
// old behavior.
|
|
69
|
-
const rawArgs = (args || []).map((arg) => String(arg));
|
|
70
135
|
const madDbRequested = rawArgs.includes('--mad-db');
|
|
71
136
|
const madDbAck = readOption(rawArgs, '--ack', '');
|
|
72
137
|
if (madDbRequested && madDbAck !== MAD_DB_ACK) {
|
|
@@ -138,7 +203,9 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
138
203
|
};
|
|
139
204
|
const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, { codexArgs: profile.launch_args, conciseBlockers: true, madSksEnv, launchEnv: madSksEnv });
|
|
140
205
|
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${sanitizeZellijSessionName(process.cwd())}`));
|
|
141
|
-
const launch =
|
|
206
|
+
const launch = headlessZellij
|
|
207
|
+
? await writeMadHeadlessZellijFallback(madLaunch, workspace)
|
|
208
|
+
: await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), slotCount: 0, requireZellij: process.env.SKS_REQUIRE_ZELLIJ === '1' });
|
|
142
209
|
const afterLaunchUi = beforeUi ? await writeCodexAppUiSnapshot(launchRoot, `mad-after-launch-${uiSnapshotId}`).catch(() => null) : null;
|
|
143
210
|
const launchUiDiff = beforeUi && afterLaunchUi ? diffCodexAppUiSnapshots(beforeUi, afterLaunchUi) : null;
|
|
144
211
|
if (launchUiDiff) {
|
|
@@ -157,7 +224,8 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
157
224
|
schema: 'sks.zellij-initial-ui.v1',
|
|
158
225
|
ok: true,
|
|
159
226
|
mission_id: madLaunch.mission_id,
|
|
160
|
-
session_name: launch.session_name,
|
|
227
|
+
session_name: launch.session_name || null,
|
|
228
|
+
live_panes: !headlessZellij,
|
|
161
229
|
initial_panes: 'main-only',
|
|
162
230
|
dashboard_created: false,
|
|
163
231
|
worker_panes_created: 0,
|
|
@@ -166,16 +234,16 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
166
234
|
const madNativeSwarm = await startMadNativeSwarm(madLaunch.root, madLaunch, args, profile, {
|
|
167
235
|
env: {
|
|
168
236
|
...madSksEnv,
|
|
169
|
-
SKS_ZELLIJ_SESSION_NAME: launch.session_name
|
|
237
|
+
...(launch.session_name ? { SKS_ZELLIJ_SESSION_NAME: launch.session_name } : {})
|
|
170
238
|
},
|
|
171
|
-
zellijSessionName: launch.session_name,
|
|
172
|
-
workerPlacement: shouldAutoAttachZellij(args) ? 'zellij-pane' : 'process',
|
|
239
|
+
zellijSessionName: launch.session_name || null,
|
|
240
|
+
workerPlacement: headlessZellij ? 'process' : shouldAutoAttachZellij(args) ? 'zellij-pane' : 'process',
|
|
173
241
|
zellijVisiblePaneCap: Number(process.env.SKS_ZELLIJ_VISIBLE_PANE_CAP || 8)
|
|
174
242
|
});
|
|
175
243
|
// The launcher only creates a detached background session. In an interactive
|
|
176
244
|
// terminal, immediately attach so the session actually opens for the user
|
|
177
245
|
// instead of leaving them to copy/paste the attach command by hand.
|
|
178
|
-
if (shouldAutoAttachZellij(args)) {
|
|
246
|
+
if (!headlessZellij && shouldAutoAttachZellij(args)) {
|
|
179
247
|
console.log(`Opening Zellij session: ${launch.session_name} (detach with Ctrl+q, re-attach later with: ${launch.attach_command_with_env})`);
|
|
180
248
|
const attached = attachZellijSessionInteractive(launch.session_name, { cwd: process.cwd(), configPath: launch.clipboard_config_path });
|
|
181
249
|
if (!attached.ok) {
|
|
@@ -187,6 +255,8 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
187
255
|
}
|
|
188
256
|
if (launch.attach_command_with_env)
|
|
189
257
|
console.log(`Attach with: ${launch.attach_command_with_env}`);
|
|
258
|
+
if (headlessZellij)
|
|
259
|
+
console.log('MAD launch running headless: live_panes=false.');
|
|
190
260
|
return launch;
|
|
191
261
|
}
|
|
192
262
|
export async function startMadNativeSwarm(root, madLaunch, args = [], profile = {}, opts = {}) {
|
|
@@ -375,6 +445,27 @@ function formatMadZellijAction(launch) {
|
|
|
375
445
|
const report = launch.report_path ? ` | report: ${launch.report_path}` : '';
|
|
376
446
|
return `${blockers}${detail}${report}`;
|
|
377
447
|
}
|
|
448
|
+
async function writeMadHeadlessZellijFallback(madLaunch, workspace) {
|
|
449
|
+
const report = {
|
|
450
|
+
schema: 'sks.zellij-session.v1',
|
|
451
|
+
generated_at: nowIso(),
|
|
452
|
+
ok: true,
|
|
453
|
+
kind: 'mad',
|
|
454
|
+
status: 'headless-fallback',
|
|
455
|
+
live_panes: false,
|
|
456
|
+
mission_id: madLaunch.mission_id,
|
|
457
|
+
session_name: null,
|
|
458
|
+
workspace,
|
|
459
|
+
root: madLaunch.root,
|
|
460
|
+
cwd: path.resolve(process.cwd()),
|
|
461
|
+
attach_command_with_env: null,
|
|
462
|
+
blockers: [],
|
|
463
|
+
warnings: ['zellij_headless_fallback_live_panes_false']
|
|
464
|
+
};
|
|
465
|
+
await writeJsonAtomic(path.join(madLaunch.dir, 'zellij-session.json'), report);
|
|
466
|
+
await appendJsonlBounded(path.join(madLaunch.dir, 'events.jsonl'), { ts: nowIso(), type: 'mad_sks.zellij_headless_fallback', live_panes: false });
|
|
467
|
+
return report;
|
|
468
|
+
}
|
|
378
469
|
async function activateMadZellijPermissionState(cwd = process.cwd(), args = []) {
|
|
379
470
|
const root = await sksRoot();
|
|
380
471
|
if (!(await exists(path.join(root, '.sneakoscope'))))
|
|
@@ -473,6 +564,9 @@ function madLaunchOnlyFlags() {
|
|
|
473
564
|
'--attach',
|
|
474
565
|
'--no-attach',
|
|
475
566
|
'--no-auto-install-zellij',
|
|
567
|
+
'--skip-zellij-repair',
|
|
568
|
+
'--install-homebrew',
|
|
569
|
+
'--headless',
|
|
476
570
|
'--allow-system',
|
|
477
571
|
'--allow-db-write',
|
|
478
572
|
'--allow-package-install',
|
|
@@ -528,6 +622,8 @@ export function defaultMadSwarmBackend(args = [], opts = {}) {
|
|
|
528
622
|
return String(opts.backend);
|
|
529
623
|
if (list.includes('--json') || list.includes('--no-attach') || opts.nonInteractive === true)
|
|
530
624
|
return 'codex-sdk';
|
|
625
|
+
if (list.includes('--headless') || process.env.SKS_MAD_ALLOW_HEADLESS === '1')
|
|
626
|
+
return 'codex-sdk';
|
|
531
627
|
return 'zellij';
|
|
532
628
|
}
|
|
533
629
|
function stripMadLaunchOnlyArgs(args = []) {
|
|
@@ -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) {
|
|
@@ -65,6 +65,12 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
65
65
|
warnings.add('codex_0139_real_probes_not_run');
|
|
66
66
|
for (const warning of normalizeList(input.codex_plugin_app_template_policy?.doctor_warnings))
|
|
67
67
|
warnings.add(warning);
|
|
68
|
+
const codexAppHarness = input.codex_app_harness_matrix || null;
|
|
69
|
+
for (const warning of normalizeList(codexAppHarness?.warnings))
|
|
70
|
+
warnings.add(warning);
|
|
71
|
+
if (codexAppHarness?.ok === false)
|
|
72
|
+
for (const blocker of normalizeList(codexAppHarness.blockers))
|
|
73
|
+
warnings.add(`codex_app_harness:${blocker}`);
|
|
68
74
|
if (input.codex_lb?.ok === false)
|
|
69
75
|
warnings.add(`codex_lb_${input.codex_lb?.circuit?.state || 'blocked'}`);
|
|
70
76
|
const localModel = input.local_model || {};
|
|
@@ -124,6 +130,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
124
130
|
codex_0139_real_probes: codex0139RealProbes,
|
|
125
131
|
codex_plugin_inventory: input.codex_plugin_inventory || null,
|
|
126
132
|
codex_plugin_app_template_policy: input.codex_plugin_app_template_policy || null,
|
|
133
|
+
codex_app_harness_matrix: codexAppHarness,
|
|
127
134
|
fast_mode_ready: input.fast_mode_ready !== false,
|
|
128
135
|
codex_app_ui: input.codex_app_ui || null,
|
|
129
136
|
hooks_ready: input.hooks_ready !== false,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
|
|
2
|
+
export async function runDoctorZellijRepair(input) {
|
|
3
|
+
const args = (input.args || []).map(String);
|
|
4
|
+
if (input.doctorFix !== true)
|
|
5
|
+
return null;
|
|
6
|
+
return repairZellijForSks({
|
|
7
|
+
root: input.root,
|
|
8
|
+
requestedBy: 'doctor --fix',
|
|
9
|
+
fixRequested: true,
|
|
10
|
+
autoApprove: args.includes('--yes') || args.includes('-y'),
|
|
11
|
+
installHomebrew: args.includes('--install-homebrew') || process.env.SKS_ALLOW_HOMEBREW_INSTALL === '1',
|
|
12
|
+
dryRun: args.includes('--dry-run'),
|
|
13
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.SKS_NO_QUESTION !== '1'),
|
|
14
|
+
allowHeadlessFallback: false,
|
|
15
|
+
env: process.env
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function doctorZellijRepairConsoleLine(result) {
|
|
19
|
+
if (!result)
|
|
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
|
+
}
|
|
25
|
+
if (result.strategy === 'none-current')
|
|
26
|
+
return `Zellij repair: current ${result.after.version || ''}`.trim();
|
|
27
|
+
if (result.ok && (result.strategy === 'brew-install-zellij' || result.strategy === 'brew-install-homebrew-then-zellij')) {
|
|
28
|
+
return `Zellij repair: installed ${result.after.version || 'latest'} via ${result.command || 'brew install zellij'}`;
|
|
29
|
+
}
|
|
30
|
+
if (result.ok && result.strategy === 'brew-upgrade-zellij') {
|
|
31
|
+
return `Zellij repair: upgraded ${result.before.version || 'unknown'} -> ${result.after.version || 'latest'} via ${result.command || 'brew upgrade zellij'}`;
|
|
32
|
+
}
|
|
33
|
+
if (result.strategy === 'manual-required') {
|
|
34
|
+
return `Zellij repair: manual_required\nRun: ${result.command || 'sks doctor --fix --install-homebrew --yes'}`;
|
|
35
|
+
}
|
|
36
|
+
if (result.strategy === 'headless-fallback')
|
|
37
|
+
return 'Zellij repair: headless_fallback live_panes=false';
|
|
38
|
+
return `Zellij repair: failed\nRun: ${result.command || 'sks doctor --fix --yes'}`;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=doctor-zellij-repair.js.map
|
|
@@ -107,6 +107,7 @@ const FIXTURES = Object.freeze({
|
|
|
107
107
|
'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
108
|
'route-ux-review': fixture('mock', '$UX-Review image UX alias route', ['image-ux-generated-review-ledger.json', 'image-voxel-ledger.json'], 'pass'),
|
|
109
109
|
'route-db': fixture('execute_and_validate_artifacts', 'sks db check --sql "SELECT 1" --json', ['completion-proof.json', 'db-operation-report.json'], 'pass'),
|
|
110
|
+
'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
111
|
'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
112
|
'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
113
|
'route-sks': fixture('mock', '$SKS control-surface route', ['completion-proof.json'], 'pass'),
|
|
@@ -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) {
|
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.
|
|
8
|
+
export const PACKAGE_VERSION = '3.1.5';
|
|
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() {
|
|
@@ -14,6 +14,7 @@ import { REQUIRED_CODEX_MODEL, isForbiddenCodexModel } from './codex-model-guard
|
|
|
14
14
|
import { dollarCommand, routeRequiresSubagents, stripVisibleDecisionAnswerBlocks } from './routes.js';
|
|
15
15
|
import { appendMissionStatus } from './recallpulse.js';
|
|
16
16
|
import { scanAgentTextForRecursion } from './agents/agent-recursion-guard.js';
|
|
17
|
+
import { evaluateLoopContinuation } from './loops/loop-continuation-enforcer.js';
|
|
17
18
|
import { buildCompactContinue, buildPermissionRequestAllow, buildPermissionRequestDeny, buildPostToolUseBlock, buildPostToolUseContinue, buildPreToolUseContinue, buildPreToolUseDeny, buildSessionStartContinue, buildStopBlock, buildStopContinue, buildSubagentStartContinue, buildSubagentStopBlock, buildSubagentStopContinue, buildUserPromptSubmitBlock, buildUserPromptSubmitContinue } from './codex-compat/codex-hook-output-builders.js';
|
|
18
19
|
const TEAM_DIGEST_MAX_EVENTS = 4;
|
|
19
20
|
const TEAM_DIGEST_MESSAGE_CHARS = 180;
|
|
@@ -606,6 +607,18 @@ function clarificationPauseBlockReason(state = {}) {
|
|
|
606
607
|
}
|
|
607
608
|
async function hookStop(root, state, payload, noQuestion) {
|
|
608
609
|
const last = extractLastMessage(payload);
|
|
610
|
+
if (state?.mode === 'LOOP' || state?.route === 'Loop' || state?.route_command === '$Loop') {
|
|
611
|
+
const missionId = state?.mission_id;
|
|
612
|
+
if (missionId) {
|
|
613
|
+
const continuation = await evaluateLoopContinuation({ root, missionId }).catch(() => null);
|
|
614
|
+
if (continuation?.should_continue) {
|
|
615
|
+
return {
|
|
616
|
+
decision: 'block',
|
|
617
|
+
reason: `SKS Loop continuation required. Resume with: ${continuation.resume_instruction}`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
609
622
|
if (await consumeCodexGitActionStopBypass(root, payload)) {
|
|
610
623
|
return {
|
|
611
624
|
continue: true,
|
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
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { loopPlanPath } from './loop-artifacts.js';
|
|
4
|
+
export async function evaluateLoopContinuation(input) {
|
|
5
|
+
const root = path.resolve(input.root || process.cwd());
|
|
6
|
+
const plan = await readJson(loopPlanPath(root, input.missionId), null);
|
|
7
|
+
const blockers = [];
|
|
8
|
+
if (!plan)
|
|
9
|
+
blockers.push('loop_plan_missing');
|
|
10
|
+
const nodes = loopNodes(plan);
|
|
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)));
|
|
12
|
+
const completed = proofs.filter((proof) => isRecord(proof) && proof.status === 'completed').length;
|
|
13
|
+
const incomplete = Math.max(0, nodes.length - completed);
|
|
14
|
+
const shouldContinue = Boolean(plan && incomplete > 0 && blockers.length === 0);
|
|
15
|
+
const report = {
|
|
16
|
+
schema: 'sks.loop-continuation-enforcer.v1',
|
|
17
|
+
generated_at: nowIso(),
|
|
18
|
+
ok: blockers.length === 0,
|
|
19
|
+
mission_id: input.missionId,
|
|
20
|
+
nodes: nodes.length,
|
|
21
|
+
completed,
|
|
22
|
+
incomplete,
|
|
23
|
+
max_continuation_turns: input.maxContinuationTurns || 3,
|
|
24
|
+
should_continue: shouldContinue,
|
|
25
|
+
resume_instruction: shouldContinue ? `sks loop resume ${input.missionId}` : null,
|
|
26
|
+
blockers
|
|
27
|
+
};
|
|
28
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'missions', input.missionId, 'loop-continuation-enforcer.json'), report).catch(() => undefined);
|
|
29
|
+
return report;
|
|
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
|
+
}
|
|
40
|
+
//# sourceMappingURL=loop-continuation-enforcer.js.map
|
|
@@ -5,6 +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, readInitDeepMemoryHints } from '../codex-app/codex-init-deep.js';
|
|
8
9
|
export async function planLoopsFromRequest(input) {
|
|
9
10
|
const parallelism = input.parallelism || 'balanced';
|
|
10
11
|
const maxLoops = Math.max(1, Math.min(32, input.maxLoops || 8));
|
|
@@ -52,6 +53,11 @@ export async function planLoopsFromRequest(input) {
|
|
|
52
53
|
})
|
|
53
54
|
};
|
|
54
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 hints.length ? { ...node, memory_hints: hints } : node;
|
|
60
|
+
});
|
|
55
61
|
const plan = {
|
|
56
62
|
schema: 'sks.loop-plan.v1',
|
|
57
63
|
mission_id: input.missionId,
|
|
@@ -63,12 +69,12 @@ export async function planLoopsFromRequest(input) {
|
|
|
63
69
|
confidence: actionNodes.length ? 'high' : 'medium'
|
|
64
70
|
},
|
|
65
71
|
graph: {
|
|
66
|
-
nodes,
|
|
72
|
+
nodes: nodesWithMemory,
|
|
67
73
|
edges: actionNodes.map((node) => ({ from: node.loop_id, to: integrationNode.loop_id, reason: 'integration_after_loop_proof' }))
|
|
68
74
|
},
|
|
69
75
|
global_budget: defaultLoopBudget({
|
|
70
|
-
max_iterations: Math.max(...
|
|
71
|
-
max_subagents:
|
|
76
|
+
max_iterations: Math.max(...nodesWithMemory.map((node) => node.budget.max_iterations)),
|
|
77
|
+
max_subagents: nodesWithMemory.reduce((sum, node) => sum + node.budget.max_subagents, 0)
|
|
72
78
|
}),
|
|
73
79
|
safety: {
|
|
74
80
|
no_unrequested_fallback_code: true,
|
|
@@ -83,6 +89,14 @@ export async function planLoopsFromRequest(input) {
|
|
|
83
89
|
},
|
|
84
90
|
blockers: []
|
|
85
91
|
};
|
|
92
|
+
const projectMemory = await readInitDeepMemory(input.root).catch(() => null);
|
|
93
|
+
if (projectMemory) {
|
|
94
|
+
plan.project_memory = {
|
|
95
|
+
source: projectMemory.path,
|
|
96
|
+
injected: true,
|
|
97
|
+
summary: projectMemory.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
86
100
|
const validation = validateLoopPlan(plan);
|
|
87
101
|
plan.blockers = validation.blockers;
|
|
88
102
|
await writeJsonAtomic(loopPlanPath(input.root, input.missionId), plan);
|
|
@@ -167,4 +181,16 @@ function dynamicCheckerWorkerCount(input) {
|
|
|
167
181
|
function titleFromDomain(domainId) {
|
|
168
182
|
return domainId === 'loop-general-coding' ? 'General coding loop' : `${domainId} loop`;
|
|
169
183
|
}
|
|
184
|
+
function scopePathsForNodes(nodes) {
|
|
185
|
+
return nodes.flatMap((node) => [
|
|
186
|
+
...node.owner_scope.files,
|
|
187
|
+
...node.owner_scope.directories
|
|
188
|
+
]).filter(Boolean);
|
|
189
|
+
}
|
|
190
|
+
function hintAppliesToNode(hint, node) {
|
|
191
|
+
if (hint.scope === '.')
|
|
192
|
+
return true;
|
|
193
|
+
const scopes = [...node.owner_scope.files, ...node.owner_scope.directories].map((value) => value.replace(/^\.?\//, ''));
|
|
194
|
+
return scopes.some((scope) => scope === hint.scope || scope.startsWith(`${hint.scope}/`) || hint.scope.startsWith(`${scope}/`));
|
|
195
|
+
}
|
|
170
196
|
//# sourceMappingURL=loop-planner.js.map
|
|
@@ -6,6 +6,7 @@ import { loopNodeRoot } from './loop-artifacts.js';
|
|
|
6
6
|
import { computeLoopConcurrencyBudget, loopWorkerBudgetFor } from './loop-concurrency-budget.js';
|
|
7
7
|
import { decideLoopFixturePolicy, writeLoopFixturePolicyDecision } from './loop-fixture-policy.js';
|
|
8
8
|
import { buildLoopCheckerPrompt, buildLoopMakerPrompt } from './loop-worker-prompts.js';
|
|
9
|
+
import { resolveCodexAppExecutionProfile } from '../codex-app/codex-app-execution-profile.js';
|
|
9
10
|
export async function runLoopMakerWorkers(input) {
|
|
10
11
|
return runLoopWorkers({ ...input, phase: 'maker' });
|
|
11
12
|
}
|
|
@@ -42,7 +43,8 @@ async function runLoopWorkerNative(input) {
|
|
|
42
43
|
? buildLoopMakerPrompt({ plan: input.plan, node: input.node, worktreePath: input.worktree?.path || null })
|
|
43
44
|
: buildLoopCheckerPrompt({ plan: input.plan, node: input.node, makerArtifacts: input.makerArtifacts || [] });
|
|
44
45
|
const workerCount = effectiveLoopWorkerCount(input);
|
|
45
|
-
const
|
|
46
|
+
const executionProfile = await resolveCodexAppExecutionProfile({ root: input.root }).catch(() => null);
|
|
47
|
+
const workGraph = buildLoopNarutoWorkGraph(input, workerCount, executionProfile);
|
|
46
48
|
// Root-cause-1 fix: keep the ORCHESTRATOR root on the MAIN repo (input.root), not the
|
|
47
49
|
// loop worktree. All zellij/right-column/slot-telemetry state derives from the orchestrator
|
|
48
50
|
// root, so anchoring it on input.root makes the SLOTS snapshot land under
|
|
@@ -77,7 +79,9 @@ async function runLoopWorkerNative(input) {
|
|
|
77
79
|
SKS_LOOP_ID: input.node.loop_id,
|
|
78
80
|
SKS_LOOP_PHASE: input.phase,
|
|
79
81
|
SKS_LOOP_MAIN_ROOT: input.root,
|
|
80
|
-
SKS_LOOP_WORKER_BUDGET: String(workerCount)
|
|
82
|
+
SKS_LOOP_WORKER_BUDGET: String(workerCount),
|
|
83
|
+
SKS_CODEX_APP_EXECUTION_PROFILE: executionProfile?.mode || 'unknown',
|
|
84
|
+
SKS_CODEX_AGENT_ROLE_STRATEGY: executionProfile?.agent_role_strategy || 'message-role'
|
|
81
85
|
},
|
|
82
86
|
...(input.worktree?.path ? {
|
|
83
87
|
worktree: {
|
|
@@ -95,9 +99,9 @@ async function runLoopWorkerNative(input) {
|
|
|
95
99
|
fallback_reason: null
|
|
96
100
|
} : null
|
|
97
101
|
});
|
|
98
|
-
return normalizeNativeResult(input, orchestrator);
|
|
102
|
+
return normalizeNativeResult(input, orchestrator, executionProfile);
|
|
99
103
|
}
|
|
100
|
-
async function normalizeNativeResult(input, result) {
|
|
104
|
+
async function normalizeNativeResult(input, result, executionProfile) {
|
|
101
105
|
const artifacts = collectArtifactPaths(result);
|
|
102
106
|
const changedFiles = stringArray(result?.changed_files || result?.proof?.changed_files || result?.results?.flatMap?.((row) => row?.changed_files || []));
|
|
103
107
|
const blockers = [
|
|
@@ -120,7 +124,15 @@ async function normalizeNativeResult(input, result) {
|
|
|
120
124
|
blockers: [...new Set(blockers)],
|
|
121
125
|
runtime_proof_path: proofPath,
|
|
122
126
|
worker_ids: stringArray(result?.results?.map?.((row) => row?.agent_id || row?.id)),
|
|
123
|
-
session_ids: stringArray(result?.results?.map?.((row) => row?.session_id))
|
|
127
|
+
session_ids: stringArray(result?.results?.map?.((row) => row?.session_id)),
|
|
128
|
+
...(executionProfile ? {
|
|
129
|
+
codex_app_execution_profile: {
|
|
130
|
+
mode: executionProfile.mode,
|
|
131
|
+
agent_role_strategy: executionProfile.agent_role_strategy,
|
|
132
|
+
artifact_path: executionProfile.artifact_path,
|
|
133
|
+
agent_type_probe_artifact_path: executionProfile.agent_type_probe_artifact_path
|
|
134
|
+
}
|
|
135
|
+
} : {})
|
|
124
136
|
};
|
|
125
137
|
await writeJsonAtomic(proofPath, { ...normalized, native_result_summary: summarizeNativeResult(result), generated_at: nowIso() });
|
|
126
138
|
return normalized;
|
|
@@ -185,7 +197,13 @@ async function runLoopWorkerFixture(input) {
|
|
|
185
197
|
fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
|
|
186
198
|
};
|
|
187
199
|
}
|
|
188
|
-
function buildLoopNarutoWorkGraph(input, workerCount) {
|
|
200
|
+
function buildLoopNarutoWorkGraph(input, workerCount, executionProfile) {
|
|
201
|
+
const profilePayload = executionProfile ? {
|
|
202
|
+
mode: executionProfile.mode,
|
|
203
|
+
agent_role_strategy: executionProfile.agent_role_strategy,
|
|
204
|
+
artifact_path: executionProfile.artifact_path,
|
|
205
|
+
agent_type_probe_artifact_path: executionProfile.agent_type_probe_artifact_path
|
|
206
|
+
} : undefined;
|
|
189
207
|
const workItems = Array.from({ length: Math.max(1, workerCount) }, (_, index) => {
|
|
190
208
|
const id = `${input.node.loop_id}-${input.phase}-${index + 1}`;
|
|
191
209
|
const writeAllowed = input.phase === 'maker';
|
|
@@ -214,7 +232,8 @@ function buildLoopNarutoWorkGraph(input, workerCount) {
|
|
|
214
232
|
mode: input.worktree?.path ? 'patch-envelope-only' : 'git-worktree',
|
|
215
233
|
required: input.node.worktree.required,
|
|
216
234
|
allocation_required: false
|
|
217
|
-
}
|
|
235
|
+
},
|
|
236
|
+
...(profilePayload ? { codex_app_execution_profile: profilePayload } : {})
|
|
218
237
|
};
|
|
219
238
|
});
|
|
220
239
|
return {
|
|
@@ -235,6 +254,7 @@ function buildLoopNarutoWorkGraph(input, workerCount) {
|
|
|
235
254
|
worktree_root: null,
|
|
236
255
|
fallback_reason: input.worktree?.path ? 'loop_worktree_already_allocated' : null
|
|
237
256
|
},
|
|
257
|
+
...(profilePayload ? { codex_app_execution_profile: profilePayload } : {}),
|
|
238
258
|
blockers: [],
|
|
239
259
|
ok: true
|
|
240
260
|
};
|