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.
- package/README.md +8 -36
- 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/command-registry.js +1 -2
- package/dist/cli/install-helpers.js +56 -4
- package/dist/commands/codex-app.js +1 -11
- package/dist/commands/codex-lb.js +12 -9
- package/dist/commands/codex-native.js +68 -0
- package/dist/commands/doctor.js +64 -0
- package/dist/core/codex-app/codex-agent-role-sync.js +69 -11
- package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
- package/dist/core/codex-app/codex-app-execution-profile.js +43 -14
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
- package/dist/core/codex-app/codex-app-harness-matrix.js +4 -65
- 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 +34 -10
- package/dist/core/codex-app/codex-init-deep.js +154 -3
- package/dist/core/codex-app/codex-skill-sync.js +84 -9
- package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +182 -0
- package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
- package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
- package/dist/core/codex-native/codex-native-interop-policy.js +58 -0
- package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
- package/dist/core/codex-native/codex-native-pattern-analysis.js +56 -0
- package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
- package/dist/core/codex-native/codex-native-reference-source.js +110 -0
- package/dist/core/codex-native/codex-native-rename-map.js +25 -0
- package/dist/core/commands/mad-sks-command.js +37 -2
- package/dist/core/commands/qa-loop-command.js +3 -2
- package/dist/core/commands/research-command.js +2 -2
- package/dist/core/doctor/doctor-zellij-repair.js +5 -1
- package/dist/core/feature-fixtures.js +3 -4
- package/dist/core/feature-registry.js +5 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/image/image-artifact-path-contract.js +18 -1
- package/dist/core/init.js +4 -1
- package/dist/core/loops/loop-continuation-enforcer.js +11 -3
- package/dist/core/loops/loop-owner-inference.js +3 -0
- package/dist/core/loops/loop-planner.js +28 -5
- package/dist/core/loops/loop-worker-runtime.js +52 -8
- package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
- package/dist/core/qa-loop.js +62 -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 +68 -1
- package/dist/core/routes.js +2 -3
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/homebrew-policy.js +0 -1
- package/dist/core/zellij/zellij-self-heal-types.js +45 -0
- package/dist/core/zellij/zellij-self-heal.js +69 -8
- package/dist/core/zellij/zellij-update.js +9 -1
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +318 -0
- package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
- package/package.json +53 -9
- package/dist/cli/hermes-command.js +0 -99
- package/dist/cli/openclaw-command.js +0 -83
- package/dist/commands/hermes.js +0 -5
- package/dist/commands/openclaw.js +0 -3
- package/dist/core/codex-app/lazycodex-analysis.js +0 -51
- package/dist/core/codex-app/lazycodex-interop-policy.js +0 -49
- package/dist/core/hermes.js +0 -192
- 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
|
-
|
|
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-
|
|
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-
|
|
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'
|
|
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.
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
|
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
|
|
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(...
|
|
72
|
-
max_subagents:
|
|
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
|
|
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: ${
|
|
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
|
}
|