sneakoscope 10.5.2 → 10.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +1 -1
- package/dist/cli/router.js +9 -3
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/codex-app/codex-app-restart-policy.js +4 -0
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +15 -1
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +2 -1
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +1 -5
- package/dist/core/commands/align-command.js +2 -1
- package/dist/core/commands/naruto-command.js +44 -0
- package/dist/core/commands/naruto-config-command.js +23 -0
- package/dist/core/commands/pipeline-command.js +2 -1
- package/dist/core/pipeline-internals/runtime-core.js +2 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/command-contract/contracts.js +11 -1
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/subagents/naruto-command-args.js +48 -7
- package/dist/core/subagents/naruto-command-input-contract.js +40 -4
- package/dist/core/subagents/naruto-execution-mode.js +62 -0
- package/dist/core/subagents/naruto-help-contract.js +9 -0
- package/dist/core/subagents/official-subagent-runner.js +2 -5
- package/dist/core/update/update-migration-state.js +10 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +15 -4
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +105 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
18
18
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
19
19
|
|
|
20
|
-
Current package: **SKS 10.5.
|
|
20
|
+
Current package: **SKS 10.5.3**. Install the latest stable release from npm.
|
|
21
21
|
|
|
22
22
|
Sneakoscope Codex (`sks`) is a local trust layer for Codex CLI and ChatGPT Desktop. It keeps agent work bounded, preserves project context, records machine-verifiable evidence, and prevents release claims that current tests or artifacts cannot support.
|
|
23
23
|
|
|
@@ -377,7 +377,7 @@ const COMMAND_HELP_LITE = {
|
|
|
377
377
|
description: 'Project TriWiki context-pack memory into managed AGENTS.md blocks or run bounded memory cleanup.'
|
|
378
378
|
},
|
|
379
379
|
naruto: {
|
|
380
|
-
usage: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] [--json] | sks naruto status|subagents|proof [latest|M-...] [--json] | sks naruto parent-summary --mission M-... --stdin [--json]',
|
|
380
|
+
usage: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] [--json] | sks naruto status|subagents|proof [latest|M-...] [--json] | sks naruto execution status [--json] | sks naruto execution set --mode auto|current-session|standalone [--restart] [--json] | sks naruto parent-summary --mission M-... --stdin [--json]',
|
|
381
381
|
description: 'Run or inspect the Codex official subagent workflow: an orchestrating parent (latest deep-tier standalone default), children on the newest model of their tier (Jev picks the tier on spawn when Jev mode is on), max_depth=1, and structured parent-thread completion evidence.'
|
|
382
382
|
},
|
|
383
383
|
reasoning: {
|
package/dist/cli/router.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ui as cliUi } from './cli-theme.js';
|
|
|
5
5
|
import { helpResult, isHelpRequest, renderManifestHelp } from './help.js';
|
|
6
6
|
import { findRetiredGlobalExecutionArgumentErrors } from './global-mode-router.js';
|
|
7
7
|
import { normalizeIntentCommand, } from '../core/commands/intent-normalization/normalizer.js';
|
|
8
|
+
import { narutoUsesCurrentSession } from '../core/subagents/naruto-execution-mode.js';
|
|
8
9
|
const routerIntentStorage = new AsyncLocalStorage();
|
|
9
10
|
export function prepareRouterExecutionIntent(argv, inheritance = {}) {
|
|
10
11
|
const effectiveArgv = argv.length ? [...argv] : ['doctor'];
|
|
@@ -241,9 +242,9 @@ async function ensureActiveRouteCommandGate(command, args) {
|
|
|
241
242
|
import('../core/mission.js')
|
|
242
243
|
]);
|
|
243
244
|
const root = await projectRoot(process.cwd()).catch(() => process.cwd());
|
|
244
|
-
const appSessionKey =
|
|
245
|
-
? ''
|
|
246
|
-
:
|
|
245
|
+
const appSessionKey = narutoUsesCurrentSession()
|
|
246
|
+
? String(process.env.CODEX_THREAD_ID || '').trim()
|
|
247
|
+
: '';
|
|
247
248
|
const state = await loadOwnedRouteState(root, appSessionKey);
|
|
248
249
|
if (safeActiveRouteContinuation(command, args, state))
|
|
249
250
|
return { ok: true, status: 'allowed_active_route_continuation' };
|
|
@@ -296,6 +297,9 @@ export function safeReadOnlySubcommand(command, args) {
|
|
|
296
297
|
if (command === 'naruto' && ['status', 'subagents', 'proof'].includes(sub)) {
|
|
297
298
|
return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
|
|
298
299
|
}
|
|
300
|
+
if (command === 'naruto' && sub === 'execution' && nested === 'status') {
|
|
301
|
+
return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
|
|
302
|
+
}
|
|
299
303
|
if (command === 'mcp' && sub === 'config' && ['list', 'test', 'backups', 'show'].includes(nested)) {
|
|
300
304
|
return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
|
|
301
305
|
}
|
|
@@ -316,6 +320,8 @@ export function safeActiveRouteContinuation(command, args, state = {}) {
|
|
|
316
320
|
const subcommand = String(args[0] || '').toLowerCase();
|
|
317
321
|
const activeRoute = String(state.route || state.route_command || state.mode || '').replace(/^\$/, '').replace(/[-_]/g, '').toUpperCase();
|
|
318
322
|
if (command === 'naruto') {
|
|
323
|
+
if (subcommand === 'execution' && String(args[1] || '').toLowerCase() === 'set')
|
|
324
|
+
return true;
|
|
319
325
|
if (subcommand === 'parent-summary') {
|
|
320
326
|
const explicitParentSummaryMission = optionValue(args, ['--mission']);
|
|
321
327
|
return Boolean(state.mission_id)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { isCodexAppRunningByBundleId } from './menubar/config.js';
|
|
2
2
|
import { restartCodexApp } from './codex-app-restart.js';
|
|
3
|
+
export const CODEX_RESTART_FAILED_BLOCKER = 'codex_restart_failed_manual_restart_required';
|
|
4
|
+
export function codexRestartBlockers(restart) {
|
|
5
|
+
return restart?.attempted && !restart.ok ? [CODEX_RESTART_FAILED_BLOCKER] : [];
|
|
6
|
+
}
|
|
3
7
|
export async function maybeRestartRunningCodexApp(input) {
|
|
4
8
|
const skipped = (reason) => ({ attempted: false, running: null, status: 'skipped', reason, ok: true, blockers: [] });
|
|
5
9
|
if (input.noRestart)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readText, writeJsonAtomic } from '../../fsx.js';
|
|
2
2
|
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
3
|
+
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
3
4
|
import { BRIDGE_MODEL_SELECTION_SCHEMA, MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, pruneSelection, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
4
5
|
import { listOpenRouterModels } from '../../providers/openrouter/openrouter-account.js';
|
|
5
6
|
import { buildCombinedBridgeCatalog, readActiveCombinedBridgeCatalog, stageCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
@@ -338,6 +339,7 @@ export async function listSelectableModels(options) {
|
|
|
338
339
|
}
|
|
339
340
|
export async function selectExposedModels(publicIds, options) {
|
|
340
341
|
const paths = controllerPaths(options);
|
|
342
|
+
const before = await readBridgeModelSelectionState(paths.home, nowIso(options));
|
|
341
343
|
const requested = [...new Set(publicIds.map((id) => String(id || '').trim()).filter(Boolean))].sort();
|
|
342
344
|
if (requested.length > MAX_SELECTED_OPENROUTER_MODELS) {
|
|
343
345
|
throw new Error('bridge_model_selection_limit_exceeded');
|
|
@@ -352,7 +354,19 @@ export async function selectExposedModels(publicIds, options) {
|
|
|
352
354
|
const activation = sync.activation && typeof sync.activation === 'object'
|
|
353
355
|
? sync.activation
|
|
354
356
|
: {};
|
|
355
|
-
|
|
357
|
+
const changed = before.selection.openrouter.public_ids.join('\0') !== requested.join('\0');
|
|
358
|
+
const restart = sync.ok === true
|
|
359
|
+
? await maybeRestartRunningCodexApp({
|
|
360
|
+
env: controllerEnv(options),
|
|
361
|
+
changed,
|
|
362
|
+
noRestart: false,
|
|
363
|
+
...(options.platform ? { platform: options.platform } : {}),
|
|
364
|
+
...(options.codexAppRunningImpl ? { isRunningImpl: options.codexAppRunningImpl } : {}),
|
|
365
|
+
...(options.codexAppRestartImpl ? { restartImpl: options.codexAppRestartImpl } : {})
|
|
366
|
+
})
|
|
367
|
+
: null;
|
|
368
|
+
const restartBlockers = codexRestartBlockers(restart);
|
|
369
|
+
return commandResult('models.select', sync.ok === true && restartBlockers.length === 0, status, { selected_count: requested.length, changed, catalog_sync: sync, codex_restart: restart }, [...(sync.ok === true ? [] : stringArray(activation.blockers)), ...restartBlockers], options);
|
|
356
370
|
}
|
|
357
371
|
function seedSelectionFromConfiguredModel(selection, configText) {
|
|
358
372
|
const configured = readTopLevelTomlString(configText, 'model');
|
|
@@ -8,7 +8,8 @@ import { DESKTOP_BRIDGE_OFFICIAL_UPSTREAM_BASE_URL } from '../desktop-bridge/ind
|
|
|
8
8
|
import { resolveBridgeRequestRoute } from '../request-route-resolver.js';
|
|
9
9
|
import { applyOfficialModelPassthrough, buildBridgeRoutingPolicy, setBridgeRoutingDefault, writeBridgeRoutingPolicy } from '../provider-route-policy.js';
|
|
10
10
|
import { syncCatalogInternal } from './catalog.js';
|
|
11
|
-
import { codexRestartBlockers
|
|
11
|
+
import { codexRestartBlockers } from '../../codex-app/codex-app-restart-policy.js';
|
|
12
|
+
import { releaseOpenRouterOnlyForAuthPriority, releaseOpenRouterOnlyWithoutBridge, restartCodexAfterOpenRouterOnlyOff } from './openrouter-only-commands.js';
|
|
12
13
|
import { openRouterOnlyStatus } from './openrouter-only-status.js';
|
|
13
14
|
import { commandResult, controllerPaths, nowIso, persistRuntimeSettings, providerCode, providerRegistrySnapshot, stringArray } from './shared.js';
|
|
14
15
|
import { desktopBridgeStatusV3, loadCore, statusFromCore } from './status.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
2
|
-
import { maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
2
|
+
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
3
3
|
import { readText } from '../../fsx.js';
|
|
4
4
|
import { isOpenRouterModelId } from '../../imagegen/imagegen-config.js';
|
|
5
5
|
import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
|
|
@@ -32,11 +32,7 @@ function syncBlockers(sync) {
|
|
|
32
32
|
const blockers = stringArray(activation.blockers);
|
|
33
33
|
return blockers.length > 0 ? blockers : ['combined_catalog_sync_failed'];
|
|
34
34
|
}
|
|
35
|
-
export const CODEX_RESTART_FAILED_BLOCKER = 'codex_restart_failed_manual_restart_required';
|
|
36
35
|
export const CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING = 'codex_relaunch_required_for_new_subagent_models';
|
|
37
|
-
export function codexRestartBlockers(restart) {
|
|
38
|
-
return restart?.attempted && !restart.ok ? [CODEX_RESTART_FAILED_BLOCKER] : [];
|
|
39
|
-
}
|
|
40
36
|
function restartCodex(changed, noRestart, options) {
|
|
41
37
|
return maybeRestartRunningCodexApp({
|
|
42
38
|
env: controllerEnv(options),
|
|
@@ -9,6 +9,7 @@ import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
|
|
|
9
9
|
import { sksPrefixedDollarCommand } from '../routes/dollar-prefix.js';
|
|
10
10
|
import { closeWorkOrderLedgerForRouteResult } from '../work-order-ledger.js';
|
|
11
11
|
import { flag, positionalArgs, promptOf, warnOnMultipleActiveSessions } from './command-utils.js';
|
|
12
|
+
import { narutoUsesCurrentSession } from '../subagents/naruto-execution-mode.js';
|
|
12
13
|
const ACTIONS = new Set(['prepare', 'run', 'status', 'proof', 'fixture', 'help', '--help', '-h']);
|
|
13
14
|
export async function alignCommand(sub = 'prepare', args = []) {
|
|
14
15
|
const action = String(sub || 'prepare').toLowerCase();
|
|
@@ -262,7 +263,7 @@ async function closeVerifiedAlignRoute(root, missionId, sessionKey) {
|
|
|
262
263
|
return closed?.ok === true;
|
|
263
264
|
}
|
|
264
265
|
function ownerSessionKey() {
|
|
265
|
-
if (
|
|
266
|
+
if (!narutoUsesCurrentSession())
|
|
266
267
|
return null;
|
|
267
268
|
return String(process.env.CODEX_THREAD_ID || '').trim() || null;
|
|
268
269
|
}
|
|
@@ -16,6 +16,7 @@ import { effectiveSubagentTarget, refreshSubagentWaveLifecycle } from '../subage
|
|
|
16
16
|
import { HOST_CAPABILITY_HOOK_RUNTIME_FILENAME, bindParentSummaryToHostCapabilityEvidence, createHostCapabilityHookRuntimeBinding, resolveHostCapabilityHookRuntimeBinding } from '../agent-bridge/host-capability-runtime.js';
|
|
17
17
|
import { renderHostCapabilityBlockedLines } from '../agent-bridge/host-capability-policy.js';
|
|
18
18
|
import { uniqueStrings } from '../text/strings.js';
|
|
19
|
+
import { configureNarutoExecution } from './naruto-config-command.js';
|
|
19
20
|
import { completeNarutoTerminalBundle, refreshOfficialSubagentCompletionArtifacts } from '../hooks-runtime/official-subagent-lifecycle.js';
|
|
20
21
|
export { buildNarutoGateResult } from '../subagents/official-subagent-preparation.js';
|
|
21
22
|
export { attachNarutoLaunchDiagnostics } from '../subagents/naruto-launch-diagnostics.js';
|
|
@@ -65,10 +66,53 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
65
66
|
return narutoSubagents(parsed);
|
|
66
67
|
if (parsed.action === 'proof')
|
|
67
68
|
return narutoProof(parsed);
|
|
69
|
+
if (parsed.action === 'execution')
|
|
70
|
+
return narutoExecution(parsed);
|
|
68
71
|
if (parsed.action === 'parent-summary')
|
|
69
72
|
return narutoParentSummary(parsed);
|
|
70
73
|
return narutoRun(parsed);
|
|
71
74
|
}
|
|
75
|
+
async function narutoExecution(parsed) {
|
|
76
|
+
const configuration = await configureNarutoExecution({
|
|
77
|
+
...(parsed.executionMode ? { mode: parsed.executionMode } : {}),
|
|
78
|
+
restart: parsed.restart
|
|
79
|
+
});
|
|
80
|
+
const result = {
|
|
81
|
+
schema: 'sks.naruto-execution-result.v1',
|
|
82
|
+
ok: configuration.ok,
|
|
83
|
+
action: 'execution',
|
|
84
|
+
command: parsed.executionCommand,
|
|
85
|
+
mode: configuration.mode,
|
|
86
|
+
stored: configuration.stored,
|
|
87
|
+
changed: configuration.changed,
|
|
88
|
+
path: configuration.path,
|
|
89
|
+
blockers: configuration.blockers,
|
|
90
|
+
restart: configuration.restart
|
|
91
|
+
};
|
|
92
|
+
return emit(parsed, result, () => {
|
|
93
|
+
if (!configuration.ok) {
|
|
94
|
+
for (const blocker of configuration.blockers)
|
|
95
|
+
console.error(`Naruto execution setting blocked: ${blocker}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (parsed.executionCommand === 'status') {
|
|
99
|
+
console.log(`Naruto execution mode: ${configuration.mode} (${configuration.stored ? 'saved' : 'default'})`);
|
|
100
|
+
console.log(`Preference: ${configuration.path}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const restart = configuration.restart;
|
|
104
|
+
const suffix = restart?.status === 'restarted'
|
|
105
|
+
? ' Codex was restarted; start a new task.'
|
|
106
|
+
: restart?.reason === 'codex_not_running'
|
|
107
|
+
? ' Codex is not running; the setting applies on its next launch.'
|
|
108
|
+
: restart?.reason === 'not_macos'
|
|
109
|
+
? ' The setting is saved; restart Codex on macOS to apply it.'
|
|
110
|
+
: restart?.status === 'skipped'
|
|
111
|
+
? ` The setting is saved; restart skipped (${restart.reason || 'policy'}).`
|
|
112
|
+
: '';
|
|
113
|
+
console.log(`Naruto execution mode saved: ${configuration.mode}.${suffix}`);
|
|
114
|
+
}, !configuration.ok);
|
|
115
|
+
}
|
|
72
116
|
async function narutoParentSummary(parsed) {
|
|
73
117
|
const root = await sksRoot();
|
|
74
118
|
const appSession = detectCodexAppSession();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../codex-app/codex-app-restart-policy.js';
|
|
2
|
+
import { readNarutoExecutionMode, writeNarutoExecutionMode } from '../subagents/naruto-execution-mode.js';
|
|
3
|
+
export async function configureNarutoExecution(input) {
|
|
4
|
+
const env = input.env || process.env;
|
|
5
|
+
try {
|
|
6
|
+
const preference = input.mode
|
|
7
|
+
? await writeNarutoExecutionMode(input.mode, env)
|
|
8
|
+
: { ...readNarutoExecutionMode(env), changed: false };
|
|
9
|
+
if (preference.blockers.length)
|
|
10
|
+
return { ...preference, ok: false, restart: null };
|
|
11
|
+
const restart = input.mode && input.restart
|
|
12
|
+
? await maybeRestartRunningCodexApp({ env, changed: true, noRestart: false, ...input.restartOptions })
|
|
13
|
+
: null;
|
|
14
|
+
const blockers = restart && !restart.ok
|
|
15
|
+
? [...restart.blockers, ...codexRestartBlockers(restart)]
|
|
16
|
+
: [];
|
|
17
|
+
return { ...preference, ok: blockers.length === 0, blockers, restart };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return { ...readNarutoExecutionMode(env), ok: false, changed: false, restart: null,
|
|
21
|
+
blockers: [error instanceof Error ? error.message : 'naruto_execution_config_failed'] };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -7,10 +7,11 @@ import { PIPELINE_PLAN_ARTIFACT, pipelinePlanState, projectGateStatus, sealRoute
|
|
|
7
7
|
import { routePrompt } from '../routes.js';
|
|
8
8
|
import { positionalArgs } from '../../cli/args.js';
|
|
9
9
|
import { flag, readFlagValue, resolveMissionId } from './command-utils.js';
|
|
10
|
+
import { narutoUsesCurrentSession } from '../subagents/naruto-execution-mode.js';
|
|
10
11
|
export async function pipelineCommand(args = []) {
|
|
11
12
|
const root = await projectRoot();
|
|
12
13
|
const action = args[0] || 'status';
|
|
13
|
-
const sessionKey = process.env.
|
|
14
|
+
const sessionKey = narutoUsesCurrentSession() ? process.env.CODEX_THREAD_ID : '';
|
|
14
15
|
const state = await loadOwnedRouteState(root, sessionKey);
|
|
15
16
|
const sessions = await listSessionStates(root);
|
|
16
17
|
if (action === 'status') {
|
|
@@ -29,6 +29,7 @@ import { classifyTaskProfile, gateProfileForTask } from '../runtime/task-profile
|
|
|
29
29
|
import { chooseVerificationBudget } from '../runtime/verification-budget.js';
|
|
30
30
|
import { stopFinalizationRitualsEnforced } from '../verification-profile.js';
|
|
31
31
|
import { HARD_NARUTO_MAX_THREADS } from '../subagents/thread-budget.js';
|
|
32
|
+
import { narutoUsesCurrentSession } from '../subagents/naruto-execution-mode.js';
|
|
32
33
|
import { NARUTO_GATE_FILENAME, NARUTO_SUMMARY_FILENAME, SUBAGENT_PLAN_FILENAME, prepareOfficialSubagentMission } from '../subagents/official-subagent-preparation.js';
|
|
33
34
|
import { buildSubagentEvidence, readSubagentEvents, recordSubagentEvent, SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME } from '../subagents/subagent-evidence.js';
|
|
34
35
|
import { effectiveSubagentTarget, normalizeLegacySubagentCountFields, subagentCountContractBlockers } from '../subagents/wave-lifecycle.js';
|
|
@@ -601,7 +602,7 @@ export async function prepareRoute(root, prompt, state = {}, opts = {}) {
|
|
|
601
602
|
...selectedRoute,
|
|
602
603
|
requiredSkills: managedSkillNamesForPrompt(selectedRoute, cleanPrompt)
|
|
603
604
|
} : null;
|
|
604
|
-
const codexThreadId = String(process.env.CODEX_THREAD_ID || '').trim();
|
|
605
|
+
const codexThreadId = narutoUsesCurrentSession() ? String(process.env.CODEX_THREAD_ID || '').trim() : '';
|
|
605
606
|
const sessionKey = opts.sessionKey || codexThreadId || state?._session_key || null;
|
|
606
607
|
const madSksAuthorization = hasMadSksSignal(cleanPrompt);
|
|
607
608
|
const task = stripDollarCommand(stripMadSksSignal(cleanPrompt)) || stripMadSksSignal(stripDollarCommand(cleanPrompt)) || String(cleanPrompt || '').trim();
|
package/dist/core/routes.js
CHANGED
|
@@ -206,7 +206,7 @@ export const ROUTES = [
|
|
|
206
206
|
reasoningPolicy: 'high',
|
|
207
207
|
stopGate: 'naruto-gate.json',
|
|
208
208
|
coverage_required: true,
|
|
209
|
-
cliEntrypoint: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] | sks naruto status|subagents|proof | sks naruto parent-summary --mission M-... --stdin',
|
|
209
|
+
cliEntrypoint: 'sks naruto run "task" [--agents N] [--max-threads N] [--trusted-project] | sks naruto status|subagents|proof | sks naruto execution status | sks naruto execution set --mode auto|current-session|standalone [--restart] | sks naruto parent-summary --mission M-... --stdin',
|
|
210
210
|
examples: ['$Naruto run review twelve independent packages with --agents 12', '$Work']
|
|
211
211
|
},
|
|
212
212
|
{
|
|
@@ -4,12 +4,22 @@ const ARGUMENT_BUILDERS = {
|
|
|
4
4
|
none: () => [],
|
|
5
5
|
'json-only': jsonFlag,
|
|
6
6
|
naruto: (input) => {
|
|
7
|
+
const action = stringValue(input.action, input.prompt || input.task ? 'run' : 'help');
|
|
8
|
+
if (action === 'execution') {
|
|
9
|
+
const executionCommand = stringValue(input.execution_command, 'status');
|
|
10
|
+
return [
|
|
11
|
+
'execution',
|
|
12
|
+
...(executionCommand === 'status' ? [] : [executionCommand]),
|
|
13
|
+
...valueFlag(input, 'mode', '--mode'),
|
|
14
|
+
...booleanFlag(input, 'restart', '--restart'),
|
|
15
|
+
...jsonFlag(input)
|
|
16
|
+
];
|
|
17
|
+
}
|
|
7
18
|
const task = typeof input.prompt === 'string'
|
|
8
19
|
? input.prompt
|
|
9
20
|
: typeof input.task === 'string'
|
|
10
21
|
? input.task
|
|
11
22
|
: '';
|
|
12
|
-
const action = stringValue(input.action, task ? 'run' : 'help');
|
|
13
23
|
return [
|
|
14
24
|
action,
|
|
15
25
|
...(task ? [task] : []),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NARUTO_ACTIONS = ['run', 'status', 'subagents', 'proof', 'parent-summary', 'help'];
|
|
1
|
+
export const NARUTO_ACTIONS = ['run', 'status', 'subagents', 'proof', 'parent-summary', 'execution', 'help'];
|
|
@@ -3,6 +3,7 @@ import { NARUTO_CREDENTIAL_BOOLEAN_FLAGS, NARUTO_CREDENTIAL_VALUE_FLAGS } from '
|
|
|
3
3
|
import { resolveNarutoCredentialPolicy } from './naruto-host-credentials.js';
|
|
4
4
|
import { DEFAULT_SUBAGENT_EFFORT, defaultSubagentModel, NARUTO_PARENT_EFFORT, narutoParentModel } from './model-policy.js';
|
|
5
5
|
import { HARD_NARUTO_MAX_THREADS } from './thread-budget.js';
|
|
6
|
+
import { isNarutoExecutionMode } from './naruto-execution-mode.js';
|
|
6
7
|
export function parseNarutoArgs(args) {
|
|
7
8
|
const helpRequested = args.includes('--help') || args.includes('-h');
|
|
8
9
|
const validationArgs = [...args];
|
|
@@ -11,7 +12,7 @@ export function parseNarutoArgs(args) {
|
|
|
11
12
|
: args;
|
|
12
13
|
const first = normalized[0] && !normalized[0].startsWith('-') ? normalized[0] : '';
|
|
13
14
|
const actionName = first;
|
|
14
|
-
const actions = new Set(['run', 'status', 'subagents', 'proof', 'parent-summary', 'help']);
|
|
15
|
+
const actions = new Set(['run', 'status', 'subagents', 'proof', 'parent-summary', 'execution', 'help']);
|
|
15
16
|
const action = (actions.has(actionName) ? actionName : 'run');
|
|
16
17
|
const explicitAction = actions.has(actionName);
|
|
17
18
|
const rest = explicitAction ? normalized.slice(1) : normalized;
|
|
@@ -20,6 +21,7 @@ export function parseNarutoArgs(args) {
|
|
|
20
21
|
const maxThreadsOption = optionValue(optionArgs, '--max-threads');
|
|
21
22
|
const missionOption = optionValue(optionArgs, '--mission');
|
|
22
23
|
const missionIdOption = optionValue(optionArgs, '--mission-id');
|
|
24
|
+
const modeOption = optionValue(optionArgs, '--mode');
|
|
23
25
|
const credentialOptions = NARUTO_CREDENTIAL_VALUE_FLAGS.map((name) => ({
|
|
24
26
|
name,
|
|
25
27
|
option: optionValue(optionArgs, name)
|
|
@@ -29,6 +31,7 @@ export function parseNarutoArgs(args) {
|
|
|
29
31
|
...optionErrors('--max-threads', maxThreadsOption, true),
|
|
30
32
|
...optionErrors('--mission', missionOption, false),
|
|
31
33
|
...optionErrors('--mission-id', missionIdOption, false),
|
|
34
|
+
...optionErrors('--mode', modeOption, false),
|
|
32
35
|
...credentialOptions.flatMap(({ name, option }) => optionErrors(name, option, false)),
|
|
33
36
|
...booleanOptionErrors(validationArgs),
|
|
34
37
|
...unknownOptionErrors(validationArgs)
|
|
@@ -50,7 +53,7 @@ export function parseNarutoArgs(args) {
|
|
|
50
53
|
: undefined;
|
|
51
54
|
const prompt = action === 'run' ? positional.join(' ').trim() : '';
|
|
52
55
|
const positionalHead = String(positional[0] || '').toLowerCase();
|
|
53
|
-
const subcommandNames = new Set(['run', 'status', 'subagents', 'proof', 'parent-summary', 'help']);
|
|
56
|
+
const subcommandNames = new Set(['run', 'status', 'subagents', 'proof', 'parent-summary', 'execution', 'help']);
|
|
54
57
|
if (!first && !explicitAction && positionalHead && !subcommandNames.has(positionalHead)) {
|
|
55
58
|
argumentErrors.push(`unknown_subcommand:${positionalHead}`);
|
|
56
59
|
}
|
|
@@ -60,7 +63,32 @@ export function parseNarutoArgs(args) {
|
|
|
60
63
|
else if (!explicitAction && subcommandNames.has(positionalHead)) {
|
|
61
64
|
argumentErrors.push(`misplaced_subcommand:${positionalHead}`);
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
let executionCommand = 'status';
|
|
67
|
+
if (action === 'execution') {
|
|
68
|
+
const command = String(positional[0] || 'status').toLowerCase();
|
|
69
|
+
if (command === 'status' || command === 'set')
|
|
70
|
+
executionCommand = command;
|
|
71
|
+
else
|
|
72
|
+
argumentErrors.push(`invalid_naruto_execution_command:${command}`);
|
|
73
|
+
if (positional.length > 1) {
|
|
74
|
+
for (const value of positional.slice(1))
|
|
75
|
+
argumentErrors.push(`unexpected_positional:${value}`);
|
|
76
|
+
}
|
|
77
|
+
if (executionCommand === 'status') {
|
|
78
|
+
if (modeOption.present)
|
|
79
|
+
argumentErrors.push('option_not_supported_for_action:execution:--mode');
|
|
80
|
+
if (normalized.includes('--restart'))
|
|
81
|
+
argumentErrors.push('option_not_supported_for_action:execution:--restart');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if (!modeOption.present || !modeOption.value)
|
|
85
|
+
argumentErrors.push('naruto_execution_set_requires_mode');
|
|
86
|
+
if (modeOption.value !== undefined && !isNarutoExecutionMode(modeOption.value)) {
|
|
87
|
+
argumentErrors.push(`invalid_naruto_execution_mode:${modeOption.value}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (action !== 'run') {
|
|
64
92
|
let missionConsumed = false;
|
|
65
93
|
for (const value of positional) {
|
|
66
94
|
if (!missionConsumed && positionalMission !== undefined && value === positionalMission) {
|
|
@@ -87,6 +115,10 @@ export function parseNarutoArgs(args) {
|
|
|
87
115
|
...(normalized.includes('--trusted-project') ? ['--trusted-project'] : []),
|
|
88
116
|
...(normalized.includes('--no-forced-login-method') ? ['--no-forced-login-method'] : [])
|
|
89
117
|
];
|
|
118
|
+
const presentExecutionOptions = [
|
|
119
|
+
...(modeOption.present ? ['--mode'] : []),
|
|
120
|
+
...(normalized.includes('--restart') ? ['--restart'] : [])
|
|
121
|
+
];
|
|
90
122
|
if (action === 'parent-summary') {
|
|
91
123
|
if (!missionOption.present || !missionOption.value || missionOption.value === 'latest') {
|
|
92
124
|
argumentErrors.push('parent_summary_requires_explicit_mission');
|
|
@@ -104,6 +136,11 @@ export function parseNarutoArgs(args) {
|
|
|
104
136
|
argumentErrors.push(`option_not_supported_for_action:${action}:${name}`);
|
|
105
137
|
}
|
|
106
138
|
}
|
|
139
|
+
if (action !== 'execution' || executionCommand === 'status') {
|
|
140
|
+
for (const name of presentExecutionOptions) {
|
|
141
|
+
argumentErrors.push(`option_not_supported_for_action:${action}:${name}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
107
144
|
if (normalized.includes('--stdin'))
|
|
108
145
|
argumentErrors.push('stdin_only_supported_for_parent_summary');
|
|
109
146
|
}
|
|
@@ -113,6 +150,9 @@ export function parseNarutoArgs(args) {
|
|
|
113
150
|
requestedSubagents,
|
|
114
151
|
maxThreads,
|
|
115
152
|
missionId: String(missionFlag || positionalMission || 'latest'),
|
|
153
|
+
executionCommand,
|
|
154
|
+
executionMode: modeOption.value && isNarutoExecutionMode(modeOption.value) ? modeOption.value : undefined,
|
|
155
|
+
restart: normalized.includes('--restart'),
|
|
116
156
|
json: normalized.includes('--json'),
|
|
117
157
|
stdin: normalized.includes('--stdin'),
|
|
118
158
|
readOnly: normalized.includes('--readonly') || normalized.includes('--read-only'),
|
|
@@ -130,11 +170,11 @@ export function parseNarutoArgs(args) {
|
|
|
130
170
|
}
|
|
131
171
|
function positionalValues(args) {
|
|
132
172
|
const valueFlags = new Set([
|
|
133
|
-
'--agents', '--max-threads', '--mission', '--mission-id',
|
|
173
|
+
'--agents', '--max-threads', '--mission', '--mission-id', '--mode',
|
|
134
174
|
...NARUTO_CREDENTIAL_VALUE_FLAGS
|
|
135
175
|
]);
|
|
136
176
|
const booleanFlags = new Set([
|
|
137
|
-
'--json', '--stdin', '--readonly', '--read-only', '--trusted-project',
|
|
177
|
+
'--json', '--stdin', '--readonly', '--read-only', '--trusted-project', '--restart',
|
|
138
178
|
...NARUTO_CREDENTIAL_BOOLEAN_FLAGS
|
|
139
179
|
]);
|
|
140
180
|
const result = [];
|
|
@@ -197,8 +237,8 @@ function optionErrors(name, option, numeric) {
|
|
|
197
237
|
}
|
|
198
238
|
function unknownOptionErrors(args) {
|
|
199
239
|
const canonical = new Set([
|
|
200
|
-
'--agents', '--max-threads', '--mission', '--mission-id',
|
|
201
|
-
'--json', '--stdin', '--readonly', '--read-only', '--trusted-project', '--help', '-h', '--',
|
|
240
|
+
'--agents', '--max-threads', '--mission', '--mission-id', '--mode',
|
|
241
|
+
'--json', '--stdin', '--readonly', '--read-only', '--trusted-project', '--restart', '--help', '-h', '--',
|
|
202
242
|
...NARUTO_CREDENTIAL_VALUE_FLAGS,
|
|
203
243
|
...NARUTO_CREDENTIAL_BOOLEAN_FLAGS
|
|
204
244
|
]);
|
|
@@ -220,6 +260,7 @@ function booleanOptionErrors(args) {
|
|
|
220
260
|
'--readonly',
|
|
221
261
|
'--read-only',
|
|
222
262
|
'--trusted-project',
|
|
263
|
+
'--restart',
|
|
223
264
|
...NARUTO_CREDENTIAL_BOOLEAN_FLAGS,
|
|
224
265
|
'--help',
|
|
225
266
|
'-h'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NARUTO_ACTIONS } from '../safety/command-contract/types.js';
|
|
2
2
|
import { NARUTO_AUTH_MODES, NARUTO_EFFORT_TIERS } from './naruto-host-credentials.js';
|
|
3
|
+
import { NARUTO_EXECUTION_MODES } from './naruto-execution-mode.js';
|
|
3
4
|
import { HARD_NARUTO_MAX_THREADS } from './thread-budget.js';
|
|
4
5
|
export const NARUTO_CREDENTIAL_VALUE_FLAGS = Object.freeze([
|
|
5
6
|
'--auth-mode',
|
|
@@ -29,6 +30,11 @@ export const NARUTO_RUN_ONLY_INPUT_FIELDS = Object.freeze([
|
|
|
29
30
|
'subagent_effort',
|
|
30
31
|
'no_forced_login_method'
|
|
31
32
|
]);
|
|
33
|
+
export const NARUTO_EXECUTION_INPUT_FIELDS = Object.freeze([
|
|
34
|
+
'execution_command',
|
|
35
|
+
'mode',
|
|
36
|
+
'restart'
|
|
37
|
+
]);
|
|
32
38
|
export function narutoCommandInputSchema() {
|
|
33
39
|
const properties = {
|
|
34
40
|
action: { type: 'string', enum: [...NARUTO_ACTIONS] },
|
|
@@ -48,6 +54,9 @@ export function narutoCommandInputSchema() {
|
|
|
48
54
|
subagent_model: identifierSchema(),
|
|
49
55
|
subagent_effort: { type: 'string', enum: [...NARUTO_EFFORT_TIERS] },
|
|
50
56
|
no_forced_login_method: { type: 'boolean' },
|
|
57
|
+
execution_command: { type: 'string', enum: ['status', 'set'] },
|
|
58
|
+
mode: { type: 'string', enum: [...NARUTO_EXECUTION_MODES] },
|
|
59
|
+
restart: { type: 'boolean' },
|
|
51
60
|
json: { type: 'boolean' }
|
|
52
61
|
};
|
|
53
62
|
const exactlyOneTask = {
|
|
@@ -60,16 +69,16 @@ export function narutoCommandInputSchema() {
|
|
|
60
69
|
const runBranch = {
|
|
61
70
|
properties: { action: { const: 'run' } },
|
|
62
71
|
required: ['action'],
|
|
63
|
-
allOf: [exactlyOneTask, absentFields(['stdin'])]
|
|
72
|
+
allOf: [exactlyOneTask, absentFields(['stdin', ...NARUTO_EXECUTION_INPUT_FIELDS])]
|
|
64
73
|
};
|
|
65
74
|
const implicitRunBranch = {
|
|
66
75
|
not: { required: ['action'] },
|
|
67
|
-
allOf: [exactlyOneTask, absentFields(['stdin'])]
|
|
76
|
+
allOf: [exactlyOneTask, absentFields(['stdin', ...NARUTO_EXECUTION_INPUT_FIELDS])]
|
|
68
77
|
};
|
|
69
78
|
const observationBranch = {
|
|
70
79
|
properties: { action: { enum: ['status', 'subagents', 'proof'] } },
|
|
71
80
|
required: ['action'],
|
|
72
|
-
allOf: [runOnlyAbsent, absentFields(['stdin'])]
|
|
81
|
+
allOf: [runOnlyAbsent, absentFields(['stdin', ...NARUTO_EXECUTION_INPUT_FIELDS])]
|
|
73
82
|
};
|
|
74
83
|
const parentSummaryBranch = {
|
|
75
84
|
properties: {
|
|
@@ -77,11 +86,34 @@ export function narutoCommandInputSchema() {
|
|
|
77
86
|
stdin: { const: true }
|
|
78
87
|
},
|
|
79
88
|
required: ['action', 'mission', 'stdin'],
|
|
80
|
-
allOf: [runOnlyAbsent]
|
|
89
|
+
allOf: [runOnlyAbsent, absentFields(NARUTO_EXECUTION_INPUT_FIELDS)]
|
|
81
90
|
};
|
|
82
91
|
const helpBranch = {
|
|
83
92
|
properties: { action: { const: 'help' } },
|
|
84
93
|
required: ['action'],
|
|
94
|
+
allOf: [runOnlyAbsent, absentFields(['mission', 'stdin', ...NARUTO_EXECUTION_INPUT_FIELDS])]
|
|
95
|
+
};
|
|
96
|
+
const executionStatusBranch = {
|
|
97
|
+
properties: { action: { const: 'execution' } },
|
|
98
|
+
required: ['action'],
|
|
99
|
+
allOf: [runOnlyAbsent, absentFields(['mission', 'stdin', ...NARUTO_EXECUTION_INPUT_FIELDS])]
|
|
100
|
+
};
|
|
101
|
+
const executionStatusExplicitBranch = {
|
|
102
|
+
properties: {
|
|
103
|
+
action: { const: 'execution' },
|
|
104
|
+
execution_command: { const: 'status' }
|
|
105
|
+
},
|
|
106
|
+
required: ['action', 'execution_command'],
|
|
107
|
+
allOf: [runOnlyAbsent, absentFields(['mission', 'stdin', 'mode', 'restart'])]
|
|
108
|
+
};
|
|
109
|
+
const executionSetBranch = {
|
|
110
|
+
properties: {
|
|
111
|
+
action: { const: 'execution' },
|
|
112
|
+
execution_command: { const: 'set' },
|
|
113
|
+
mode: { type: 'string', enum: [...NARUTO_EXECUTION_MODES] },
|
|
114
|
+
restart: { type: 'boolean' }
|
|
115
|
+
},
|
|
116
|
+
required: ['action', 'execution_command', 'mode'],
|
|
85
117
|
allOf: [runOnlyAbsent, absentFields(['mission', 'stdin'])]
|
|
86
118
|
};
|
|
87
119
|
const implicitHelpBranch = {
|
|
@@ -90,6 +122,7 @@ export function narutoCommandInputSchema() {
|
|
|
90
122
|
{ required: ['action'] },
|
|
91
123
|
{ required: ['mission'] },
|
|
92
124
|
{ required: ['stdin'] },
|
|
125
|
+
...NARUTO_EXECUTION_INPUT_FIELDS.map((field) => ({ required: [field] })),
|
|
93
126
|
...NARUTO_RUN_ONLY_INPUT_FIELDS.map((field) => ({ required: [field] }))
|
|
94
127
|
]
|
|
95
128
|
}
|
|
@@ -103,6 +136,9 @@ export function narutoCommandInputSchema() {
|
|
|
103
136
|
implicitRunBranch,
|
|
104
137
|
observationBranch,
|
|
105
138
|
parentSummaryBranch,
|
|
139
|
+
executionStatusBranch,
|
|
140
|
+
executionStatusExplicitBranch,
|
|
141
|
+
executionSetBranch,
|
|
106
142
|
helpBranch,
|
|
107
143
|
implicitHelpBranch
|
|
108
144
|
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
6
|
+
import { withFileLock } from '../locks/file-lock.js';
|
|
7
|
+
import { ensureConfinedDirectory, inspectConfinedPath } from '../managed-path-safety.js';
|
|
8
|
+
export const NARUTO_EXECUTION_MODES = ['auto', 'current-session', 'standalone'];
|
|
9
|
+
export const NARUTO_EXECUTION_SCHEMA = 'sks.naruto-execution.v1';
|
|
10
|
+
export function narutoExecutionPath(env = process.env) {
|
|
11
|
+
return path.join(path.resolve(env.SKS_HOME || path.join(env.HOME || os.homedir(), '.sneakoscope')), 'preferences', 'naruto-execution.json');
|
|
12
|
+
}
|
|
13
|
+
export function isNarutoExecutionMode(value) {
|
|
14
|
+
return NARUTO_EXECUTION_MODES.some(mode => mode === value);
|
|
15
|
+
}
|
|
16
|
+
export function readNarutoExecutionMode(env = process.env) {
|
|
17
|
+
const file = narutoExecutionPath(env);
|
|
18
|
+
try {
|
|
19
|
+
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
20
|
+
if (raw?.schema !== NARUTO_EXECUTION_SCHEMA || !isNarutoExecutionMode(raw.mode))
|
|
21
|
+
throw new Error('invalid');
|
|
22
|
+
return { schema: NARUTO_EXECUTION_SCHEMA, mode: raw.mode, path: file, stored: true, blockers: [] };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return {
|
|
26
|
+
schema: NARUTO_EXECUTION_SCHEMA, mode: 'auto', path: file, stored: false,
|
|
27
|
+
blockers: error?.code === 'ENOENT' ? [] : ['naruto_execution_preference_unreadable']
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function writeNarutoExecutionMode(mode, env = process.env, onlyIfAbsent = false) {
|
|
32
|
+
if (!isNarutoExecutionMode(mode))
|
|
33
|
+
throw new Error('naruto_execution_mode_invalid');
|
|
34
|
+
const file = narutoExecutionPath(env);
|
|
35
|
+
const root = path.dirname(path.dirname(file));
|
|
36
|
+
await fsp.mkdir(root, { recursive: true });
|
|
37
|
+
await ensureConfinedDirectory(root, path.dirname(file));
|
|
38
|
+
return withFileLock({ lockPath: `${file}.lock`, timeoutMs: 5_000, staleMs: 30_000 }, async () => {
|
|
39
|
+
const safety = await inspectConfinedPath(root, file);
|
|
40
|
+
if (safety.leafSymlink)
|
|
41
|
+
throw new Error('naruto_execution_preference_unsafe_path');
|
|
42
|
+
const before = readNarutoExecutionMode(env);
|
|
43
|
+
if (before.blockers.length)
|
|
44
|
+
throw new Error(before.blockers[0]);
|
|
45
|
+
if ((onlyIfAbsent && before.stored) || (before.stored && before.mode === mode))
|
|
46
|
+
return { ...before, changed: false };
|
|
47
|
+
await writeJsonAtomic(file, { schema: NARUTO_EXECUTION_SCHEMA, mode }, { mode: 0o600 });
|
|
48
|
+
return { ...readNarutoExecutionMode(env), changed: true };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function narutoUsesCurrentSession(env = process.env) {
|
|
52
|
+
if (env.SKS_NARUTO_STANDALONE_CLI === '1')
|
|
53
|
+
return false;
|
|
54
|
+
if (env.SKS_NARUTO_APP_SESSION === '1')
|
|
55
|
+
return true;
|
|
56
|
+
const mode = readNarutoExecutionMode(env).mode;
|
|
57
|
+
if (mode === 'standalone')
|
|
58
|
+
return false;
|
|
59
|
+
if (mode === 'current-session')
|
|
60
|
+
return Boolean(env.CODEX_THREAD_ID?.trim());
|
|
61
|
+
return Boolean(env.CODEX_THREAD_ID?.trim());
|
|
62
|
+
}
|
|
@@ -9,6 +9,7 @@ export function renderNarutoUsage() {
|
|
|
9
9
|
'',
|
|
10
10
|
'Usage: sks naruto run "<task>" [options]',
|
|
11
11
|
' sks naruto status|subagents|proof [latest|M-...] [--json]',
|
|
12
|
+
' sks naruto execution status [--json] | sks naruto execution set --mode auto|current-session|standalone [--restart] [--json]',
|
|
12
13
|
' sks naruto parent-summary --mission M-... --stdin [--json]',
|
|
13
14
|
'',
|
|
14
15
|
'Run or inspect the Codex official-subagent Naruto workflow.',
|
|
@@ -28,6 +29,12 @@ export function renderNarutoUsage() {
|
|
|
28
29
|
' --no-forced-login-method Do not inject a forced login method.',
|
|
29
30
|
' --json Emit machine-readable output.',
|
|
30
31
|
'',
|
|
32
|
+
'Execution mode:',
|
|
33
|
+
' auto Preserve the Codex App session contract when CODEX_THREAD_ID is present (default when no update-selected lane exists).',
|
|
34
|
+
' current-session Require CODEX_THREAD_ID and use the current Codex App parent.',
|
|
35
|
+
' standalone Launch the official Codex child workflow from a terminal, even if a shell inherited a thread id.',
|
|
36
|
+
' Apply the setting from SKS Center to save it and restart a running Codex App when macOS permits it.',
|
|
37
|
+
'',
|
|
31
38
|
'Model tiers: every child runs the newest model of its tier: fast for tiny mechanical work, balanced for instructed coding, context for reads and tools, deep for planning, analysis, and review judgment.',
|
|
32
39
|
'Automatic fan-out starts at 4/6/8, or 16 for eligible mass mechanical or exploration work on the fast and context tiers.',
|
|
33
40
|
'After decomposition, either lane may expand to 256 independent useful children.',
|
|
@@ -48,6 +55,8 @@ export function buildNarutoHelpResult() {
|
|
|
48
55
|
'sks naruto status [latest|M-...] [--json]',
|
|
49
56
|
'sks naruto subagents [latest|M-...] [--json]',
|
|
50
57
|
'sks naruto proof [latest|M-...] [--json]',
|
|
58
|
+
'sks naruto execution status [--json]',
|
|
59
|
+
'sks naruto execution set --mode auto|current-session|standalone [--restart] [--json]',
|
|
51
60
|
'sks naruto parent-summary --mission M-... --stdin [--json] (active Codex App Naruto only)'
|
|
52
61
|
],
|
|
53
62
|
commands: [...NARUTO_ACTIONS],
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { randomId, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { DEFAULT_SUBAGENT_EFFORT, defaultSubagentModel, NARUTO_PARENT_EFFORT, narutoParentModel } from './model-policy.js';
|
|
6
6
|
import { nextAgentGenerationEnv } from '../agents/agent-recursion-guard.js';
|
|
7
|
+
import { narutoUsesCurrentSession } from './naruto-execution-mode.js';
|
|
7
8
|
import { inspectDesktopBridgeCliLaunchGuard, stripRetiredDirectProviderEnv } from '../codex-control/desktop-bridge-launch-guard.js';
|
|
8
9
|
import { prepareCodexAppServerRuntimeEnv } from '../codex-control/codex-app-server-runtime-env.js';
|
|
9
10
|
import { assertCodexFloor } from '../codex-compat/codex-version-policy.js';
|
|
@@ -77,11 +78,7 @@ const SECRET_BEARING_ENV_KEYS = new Set([
|
|
|
77
78
|
'all_proxy'
|
|
78
79
|
]);
|
|
79
80
|
export function detectCodexAppSession(env = process.env) {
|
|
80
|
-
|
|
81
|
-
return false;
|
|
82
|
-
if (env.SKS_NARUTO_APP_SESSION === '1')
|
|
83
|
-
return true;
|
|
84
|
-
return Boolean(env.CODEX_THREAD_ID);
|
|
81
|
+
return narutoUsesCurrentSession(env);
|
|
85
82
|
}
|
|
86
83
|
export function codexAppSessionKey(env = process.env) {
|
|
87
84
|
if (!detectCodexAppSession(env))
|
|
@@ -283,6 +283,16 @@ const UPDATE_MIGRATION_STAGES = [
|
|
|
283
283
|
min_from_version: '0.0.0',
|
|
284
284
|
run: runCurrentPublicSurfaceReconcileStage
|
|
285
285
|
},
|
|
286
|
+
{
|
|
287
|
+
id: 'naruto-execution-mode',
|
|
288
|
+
min_from_version: '0.0.0',
|
|
289
|
+
run: async () => {
|
|
290
|
+
const { writeNarutoExecutionMode } = await import('../subagents/naruto-execution-mode.js');
|
|
291
|
+
const initialMode = process.env.SKS_NARUTO_APP_SESSION === '1' ? 'current-session' : 'standalone';
|
|
292
|
+
const result = await writeNarutoExecutionMode(initialMode, process.env, true);
|
|
293
|
+
return { ok: true, status: 'ok', actions: [result.changed ? `naruto_execution_${initialMode}_initialized` : 'naruto_execution_preference_preserved'], blockers: [], warnings: [], detail: { mode: result.mode, initial_mode: initialMode } };
|
|
294
|
+
}
|
|
295
|
+
},
|
|
286
296
|
{
|
|
287
297
|
id: 'session-state-split',
|
|
288
298
|
min_from_version: '0.0.0',
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '10.5.
|
|
1
|
+
export const PACKAGE_VERSION = '10.5.3';
|
|
@@ -78,7 +78,7 @@ extension ProvidersViewController: NSTableViewDataSource, NSTableViewDelegate {
|
|
|
78
78
|
clear.setAccessibilityIdentifier("sks-provider-exposure-clear")
|
|
79
79
|
let card = NativeView.card(
|
|
80
80
|
title: "Models in Codex",
|
|
81
|
-
subtitle: "Select OpenRouter models
|
|
81
|
+
subtitle: "Select OpenRouter models and apply. SKS rebuilds the bridge catalog and restarts a running Codex App so its picker is current. Codex-LB models are included unless OpenRouter Only is on.",
|
|
82
82
|
views: [exposureStatus, exposureSearchField, scroll, ControlKit.actionRow([exposureApplyButton, clear])]
|
|
83
83
|
)
|
|
84
84
|
card.setAccessibilityIdentifier("sks-provider-card-model-exposure")
|
|
@@ -154,9 +154,20 @@ extension ProvidersViewController: NSTableViewDataSource, NSTableViewDelegate {
|
|
|
154
154
|
let blocker = truth?.blockers.first.map(ProviderSecretRedactor.redact)
|
|
155
155
|
let extra = self.openRouterOnlyEnabled == true ? " plus the subagent list models; Codex-LB models stay hidden while OpenRouter Only is on"
|
|
156
156
|
: self.openRouterOnlyEnabled == false ? " plus every Codex-LB model" : ""
|
|
157
|
-
let
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
let restart = self.json(result.output)?["result"] as? [String: Any]
|
|
158
|
+
let codexRestart = restart?["codex_restart"] as? [String: Any]
|
|
159
|
+
let summary: String
|
|
160
|
+
if completed, codexRestart?["status"] as? String == "restarted" {
|
|
161
|
+
summary = "Exposure applied · \(self.exposurePending.count) OpenRouter model(s)\(extra). Codex restarted; its picker is current."
|
|
162
|
+
} else if completed, codexRestart?["reason"] as? String == "codex_not_running" {
|
|
163
|
+
summary = "Exposure applied · \(self.exposurePending.count) OpenRouter model(s)\(extra). Codex is not running; the picker updates on its next launch."
|
|
164
|
+
} else if completed, codexRestart?["reason"] as? String == "not_macos" {
|
|
165
|
+
summary = "Exposure applied · \(self.exposurePending.count) OpenRouter model(s)\(extra). Restart Codex on macOS to refresh its picker."
|
|
166
|
+
} else if completed {
|
|
167
|
+
summary = "Exposure applied · \(self.exposurePending.count) OpenRouter model(s)\(extra). Codex restart was skipped by policy."
|
|
168
|
+
} else {
|
|
169
|
+
summary = blocker.map { "Exposure not applied · \($0)" } ?? "Exposure result schema invalid"
|
|
170
|
+
}
|
|
160
171
|
_ = self.operations.update(snapshot, state: completed ? .succeeded : .failed, stage: "complete", progress: 1, summary: summary)
|
|
161
172
|
self.exposureStatus.stringValue = summary
|
|
162
173
|
self.exposureStatus.textColor = completed ? .systemGreen : .systemRed
|
|
@@ -14,8 +14,11 @@ final class SettingsViewController: NSViewController, ControlCenterPage {
|
|
|
14
14
|
private let followCodexLifecycle = NSButton(checkboxWithTitle: "Show SKS Menu only while Codex is running", target: nil, action: nil)
|
|
15
15
|
private let status = NativeView.detail("Settings use the native app configuration file.")
|
|
16
16
|
private let contextStatus = NativeView.detail("Codex 1M context: checking current state…")
|
|
17
|
+
private let narutoModePopup = NSPopUpButton()
|
|
18
|
+
private let narutoModeStatus = NativeView.detail("Naruto execution mode: checking saved preference…")
|
|
17
19
|
private var notificationButton: NSButton!
|
|
18
20
|
private var contextToggleButton: NSButton!
|
|
21
|
+
private var narutoApplyButton: NSButton!
|
|
19
22
|
private var contextEnabled: Bool?
|
|
20
23
|
private var contextBusy = false
|
|
21
24
|
private var contextGeneration = 0
|
|
@@ -24,6 +27,8 @@ final class SettingsViewController: NSViewController, ControlCenterPage {
|
|
|
24
27
|
private var memoryEnabled: Bool?
|
|
25
28
|
private var memoryBusy = false
|
|
26
29
|
private var memoryGeneration = 0
|
|
30
|
+
private var narutoBusy = false
|
|
31
|
+
private var narutoGeneration = 0
|
|
27
32
|
init(processClient: ProcessClient, operations: OperationCoordinator, notifications: NotificationCoordinator) {
|
|
28
33
|
self.processClient = processClient
|
|
29
34
|
self.operations = operations
|
|
@@ -44,6 +49,24 @@ final class SettingsViewController: NSViewController, ControlCenterPage {
|
|
|
44
49
|
subtitle: "Experimental · Keep notes and retrieve earlier messages and tool results. Enabled by default in SKS. Applies to new tasks with supported Codex and eligible ChatGPT sign-in; API-key and custom-provider sessions may not activate it.",
|
|
45
50
|
views: [NativeView.row([memoryToggle, NativeView.detail("Enable experimental context management")]), memoryStatus]
|
|
46
51
|
)
|
|
52
|
+
narutoModePopup.removeAllItems()
|
|
53
|
+
for mode in ["auto", "current-session", "standalone"] {
|
|
54
|
+
narutoModePopup.addItem(withTitle: Self.narutoModeTitle(mode))
|
|
55
|
+
narutoModePopup.lastItem?.representedObject = mode
|
|
56
|
+
}
|
|
57
|
+
narutoModePopup.isEnabled = false
|
|
58
|
+
narutoModePopup.setAccessibilityLabel("Naruto execution mode")
|
|
59
|
+
narutoModePopup.setAccessibilityIdentifier("sks-naruto-execution-mode")
|
|
60
|
+
narutoApplyButton = ControlKit.primaryButton("Apply", target: self, action: #selector(applyNarutoExecution))
|
|
61
|
+
narutoApplyButton.isEnabled = false
|
|
62
|
+
narutoApplyButton.setAccessibilityLabel("Apply Naruto execution mode and restart Codex")
|
|
63
|
+
narutoApplyButton.setAccessibilityIdentifier("sks-naruto-execution-apply")
|
|
64
|
+
narutoModeStatus.setAccessibilityIdentifier("sks-naruto-execution-status")
|
|
65
|
+
let narutoCard = NativeView.card(
|
|
66
|
+
title: "Naruto execution",
|
|
67
|
+
subtitle: "Choose whether SKS Naruto uses the current Codex App parent or launches the official workflow as a standalone terminal command. Apply saves the preference and restarts a running Codex App so the new mode is active.",
|
|
68
|
+
views: [NativeView.row([narutoModePopup, narutoApplyButton]), narutoModeStatus]
|
|
69
|
+
)
|
|
47
70
|
followCodexLifecycle.target = self; followCodexLifecycle.action = #selector(save)
|
|
48
71
|
followCodexLifecycle.setAccessibilityLabel("Show SKS Menu only while Codex is running")
|
|
49
72
|
notificationButton = NativeView.button("Enable Notifications", target: self, action: #selector(enableNotifications))
|
|
@@ -66,13 +89,14 @@ final class SettingsViewController: NSViewController, ControlCenterPage {
|
|
|
66
89
|
)
|
|
67
90
|
view = NativeView.page([
|
|
68
91
|
ControlKit.header("Settings", "Choose how SKS works on this Mac."),
|
|
69
|
-
memoryCard, lifecycleCard, notificationsCard, NativeDisclosure("Advanced", views: [contextCard])
|
|
92
|
+
memoryCard, narutoCard, lifecycleCard, notificationsCard, NativeDisclosure("Advanced", views: [contextCard])
|
|
70
93
|
])
|
|
71
94
|
}
|
|
72
95
|
|
|
73
96
|
func refreshOnAppear() {
|
|
74
97
|
refreshContextManagement()
|
|
75
98
|
refreshContext1m()
|
|
99
|
+
refreshNarutoExecution()
|
|
76
100
|
let configResult = readConfig()
|
|
77
101
|
switch configResult {
|
|
78
102
|
case .loaded(let config):
|
|
@@ -104,6 +128,86 @@ final class SettingsViewController: NSViewController, ControlCenterPage {
|
|
|
104
128
|
}
|
|
105
129
|
}
|
|
106
130
|
|
|
131
|
+
private static func narutoModeTitle(_ mode: String) -> String {
|
|
132
|
+
switch mode {
|
|
133
|
+
case "current-session": return "Current Codex App session"
|
|
134
|
+
case "standalone": return "Standalone terminal workflow"
|
|
135
|
+
default: return "Auto (Codex App when available)"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private func refreshNarutoExecution() {
|
|
140
|
+
guard !narutoBusy else { return }
|
|
141
|
+
narutoGeneration += 1
|
|
142
|
+
let requestGeneration = narutoGeneration
|
|
143
|
+
processClient.run(["naruto", "execution", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
|
|
144
|
+
guard let self, requestGeneration == self.narutoGeneration, !self.narutoBusy else { return }
|
|
145
|
+
let payload = self.json(result.output)
|
|
146
|
+
guard result.code == 0,
|
|
147
|
+
payload?["schema"] as? String == "sks.naruto-execution-result.v1",
|
|
148
|
+
payload?["ok"] as? Bool == true,
|
|
149
|
+
let mode = payload?["mode"] as? String,
|
|
150
|
+
["auto", "current-session", "standalone"].contains(mode) else {
|
|
151
|
+
self.narutoModePopup.isEnabled = false
|
|
152
|
+
self.narutoApplyButton.isEnabled = false
|
|
153
|
+
self.narutoModeStatus.stringValue = "Naruto execution preference unavailable. Run sks update, then reopen Settings."
|
|
154
|
+
self.narutoModeStatus.textColor = .systemOrange
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
self.narutoModePopup.selectItem(withTitle: Self.narutoModeTitle(mode))
|
|
158
|
+
self.narutoModePopup.isEnabled = true
|
|
159
|
+
self.narutoApplyButton.isEnabled = true
|
|
160
|
+
let stored = payload?["stored"] as? Bool == true
|
|
161
|
+
self.narutoModeStatus.stringValue = "\(Self.narutoModeTitle(mode)) · \(stored ? "saved preference" : "default until the next update")"
|
|
162
|
+
self.narutoModeStatus.textColor = .secondaryLabelColor
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@objc private func applyNarutoExecution() {
|
|
167
|
+
guard !narutoBusy,
|
|
168
|
+
let mode = narutoModePopup.selectedItem?.representedObject as? String,
|
|
169
|
+
["auto", "current-session", "standalone"].contains(mode) else { return }
|
|
170
|
+
guard let operation = operations.begin(kind: "naruto-execution-mode", mutationGroup: "codex-config", summary: "Apply Naruto execution mode") else {
|
|
171
|
+
narutoModeStatus.stringValue = "Another configuration change is running. Try again when it finishes."
|
|
172
|
+
narutoModeStatus.textColor = .systemOrange
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
narutoBusy = true
|
|
176
|
+
narutoGeneration += 1
|
|
177
|
+
narutoModePopup.isEnabled = false
|
|
178
|
+
narutoApplyButton.isEnabled = false
|
|
179
|
+
narutoModeStatus.stringValue = "Saving Naruto execution mode and restarting Codex if it is running…"
|
|
180
|
+
narutoModeStatus.textColor = .secondaryLabelColor
|
|
181
|
+
_ = operations.update(operation, state: .running, stage: "applying", progress: nil, summary: narutoModeStatus.stringValue)
|
|
182
|
+
processClient.run(["naruto", "execution", "set", "--mode", mode, "--restart", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
|
|
183
|
+
guard let self else { return }
|
|
184
|
+
self.narutoBusy = false
|
|
185
|
+
let payload = self.json(result.output)
|
|
186
|
+
let ok = result.code == 0
|
|
187
|
+
&& payload?["schema"] as? String == "sks.naruto-execution-result.v1"
|
|
188
|
+
&& payload?["ok"] as? Bool == true
|
|
189
|
+
&& payload?["mode"] as? String == mode
|
|
190
|
+
let restart = payload?["restart"] as? [String: Any]
|
|
191
|
+
let summary: String
|
|
192
|
+
if ok, restart?["status"] as? String == "restarted" {
|
|
193
|
+
summary = "Naruto mode saved · Codex restarted. Start a new task to use it."
|
|
194
|
+
} else if ok, restart?["reason"] as? String == "codex_not_running" {
|
|
195
|
+
summary = "Naruto mode saved · Codex is not running; it applies on the next launch."
|
|
196
|
+
} else if ok, restart?["reason"] as? String == "not_macos" {
|
|
197
|
+
summary = "Naruto mode saved · restart Codex on macOS to apply it."
|
|
198
|
+
} else if ok {
|
|
199
|
+
summary = "Naruto mode saved · Codex restart was skipped by policy."
|
|
200
|
+
} else {
|
|
201
|
+
let blocker = (payload?["blockers"] as? [String])?.first
|
|
202
|
+
summary = blocker.map { "Naruto mode was not confirmed · \($0)" } ?? "Naruto mode was not confirmed · unexpected CLI response."
|
|
203
|
+
}
|
|
204
|
+
_ = self.operations.update(operation, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: summary)
|
|
205
|
+
self.narutoModeStatus.stringValue = summary
|
|
206
|
+
self.narutoModeStatus.textColor = ok ? .systemGreen : .systemRed
|
|
207
|
+
self.refreshNarutoExecution()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
107
211
|
private func refreshContextManagement() {
|
|
108
212
|
guard !memoryBusy else { return }
|
|
109
213
|
memoryGeneration += 1
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "10.5.
|
|
4
|
+
"version": "10.5.3",
|
|
5
5
|
"description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|