sneakoscope 6.7.0 → 7.0.2
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 +3 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +71 -0
- package/dist/cli/install-helpers-codex-lb-selftest.js +11 -14
- package/dist/cli/install-helpers.js +305 -53
- package/dist/config/skills-manifest.json +52 -52
- package/dist/core/agents/agent-orchestrator.js +56 -5
- package/dist/core/agents/agent-roster.js +4 -2
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +18 -1
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +16 -0
- package/dist/core/codex-app.js +52 -6
- package/dist/core/commands/naruto-command.js +23 -5
- package/dist/core/hooks-runtime.js +77 -3
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/mission.js +74 -4
- package/dist/core/release/package-size-budget.js +1 -1
- package/dist/core/subagents/naruto-proof-projection.js +2 -2
- package/dist/core/subagents/official-subagent-config.js +8 -5
- package/dist/core/subagents/official-subagent-preparation.js +24 -1
- package/dist/core/subagents/official-subagent-prompt.js +3 -2
- package/dist/core/subagents/official-subagent-runner.js +8 -1
- package/dist/core/subagents/wave-lifecycle.js +20 -0
- package/dist/core/subagents/wave-parent-guidance.js +42 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AlertFactory.swift +21 -2
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +3 -0
- package/dist/native/sks-menubar/Sources/AppIdentity.swift +28 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +14 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +71 -7
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +50 -9
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +61 -8
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +169 -31
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +58 -23
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +41 -7
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +34 -3
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +38 -8
- package/dist/scripts/docs-truthfulness-check.js +2 -2
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +7 -3
- package/package.json +1 -1
package/dist/core/codex-app.js
CHANGED
|
@@ -10,6 +10,8 @@ import { GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } fro
|
|
|
10
10
|
import { GLM_52_OPENROUTER_MODEL } from './providers/glm/glm-52-settings.js';
|
|
11
11
|
import { resolveOpenRouterApiKey } from './providers/openrouter/openrouter-secret-store.js';
|
|
12
12
|
import { codexLbEnvPath, loadCodexLbEnv, parseShellEnvValue, readCodexLbModelCatalog } from './codex-lb/codex-lb-env.js';
|
|
13
|
+
import { codexLbToolCatalogPath, inspectCodexLbToolCatalog } from './codex-lb/codex-lb-tool-catalog.js';
|
|
14
|
+
import { isSksOwnedGlobalUiLock } from './codex-app/codex-app-ui-state-snapshot.js';
|
|
13
15
|
import { redactString } from './secret-redaction.js';
|
|
14
16
|
export const CODEX_APP_DOCS_URL = 'https://developers.openai.com/codex/app/features';
|
|
15
17
|
export const CODEX_CHANGELOG_URL = 'https://developers.openai.com/codex/changelog';
|
|
@@ -865,9 +867,18 @@ async function codexFastModeConfigStatus(opts = {}) {
|
|
|
865
867
|
for (const config of configs) {
|
|
866
868
|
if (!config.text)
|
|
867
869
|
continue;
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
870
|
+
const lines = String(config.text).split(/\r?\n/);
|
|
871
|
+
const topLevelEnd = lines.findIndex((line) => /^\s*\[/.test(line));
|
|
872
|
+
const topLevelBound = topLevelEnd === -1 ? lines.length : topLevelEnd;
|
|
873
|
+
for (let index = 0; index < topLevelBound; index += 1) {
|
|
874
|
+
const line = lines[index] || '';
|
|
875
|
+
if (!/^\s*model_reasoning_effort\s*=/.test(line))
|
|
876
|
+
continue;
|
|
877
|
+
// Preserve unmarked user reasoning choices. Only SKS-provenanced locks hide Fast UI.
|
|
878
|
+
if (config.scope === 'global' ? isSksOwnedGlobalUiLock(lines, index) : true) {
|
|
879
|
+
blockers.push(`${config.scope}:top_level_model_reasoning_effort`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
871
882
|
if (/(^|\n)\s*fast_default_opt_out\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(tomlTable(config.text, 'notice')))
|
|
872
883
|
blockers.push(`${config.scope}:fast_default_opt_out`);
|
|
873
884
|
}
|
|
@@ -946,11 +957,39 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
946
957
|
&& hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
|
|
947
958
|
&& hasTomlBoolean(codexLbProvider, 'requires_openai_auth', true);
|
|
948
959
|
const codexLbSelectedDefault = /(?:^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevelToml(globalConfig));
|
|
949
|
-
|
|
950
|
-
|
|
960
|
+
const managedCatalogPath = codexLbToolCatalogPath(path.join(home || '', '.codex'));
|
|
961
|
+
const configuredCatalogPath = topLevelToml(globalConfig).match(/(?:^|\n)\s*model_catalog_json\s*=\s*"([^"]+)"\s*(?:#.*)?(?=\n|$)/)?.[1] || '';
|
|
962
|
+
const managedCatalogConfigured = Boolean(configuredCatalogPath)
|
|
963
|
+
&& path.resolve(configuredCatalogPath) === path.resolve(managedCatalogPath);
|
|
964
|
+
let persistedCatalog = null;
|
|
965
|
+
if (!opts.codexLbModelCatalog && managedCatalogConfigured) {
|
|
966
|
+
persistedCatalog = await inspectCodexLbToolCatalog(managedCatalogPath).catch(() => null);
|
|
967
|
+
}
|
|
968
|
+
let liveCatalog = opts.codexLbModelCatalog || null;
|
|
969
|
+
if (!liveCatalog && !persistedCatalog?.ok && codexLbProviderContractOk && codexLbApiKeySource !== 'missing' && codexLbBaseUrlSource !== 'missing') {
|
|
951
970
|
const loadedEnv = await loadCodexLbEnv({ home, envPath: codexLbEnvFilePath }).catch(() => null);
|
|
952
|
-
|
|
971
|
+
liveCatalog = loadedEnv ? await readCodexLbModelCatalog({ loadedEnv }).catch(() => null) : null;
|
|
953
972
|
}
|
|
973
|
+
// Prefer the persisted Codex-owned catalog that Desktop actually loads. Live
|
|
974
|
+
// /models remains freshness telemetry and a fallback when no managed file exists.
|
|
975
|
+
const codexLbModelCatalog = opts.codexLbModelCatalog || (persistedCatalog?.ok
|
|
976
|
+
? {
|
|
977
|
+
ok: true,
|
|
978
|
+
models: Array.isArray(persistedCatalog.gpt56_models) ? persistedCatalog.gpt56_models : [],
|
|
979
|
+
blockers: [],
|
|
980
|
+
source: 'persisted_model_catalog_json',
|
|
981
|
+
tools_transport: persistedCatalog.tools_transport || null
|
|
982
|
+
}
|
|
983
|
+
: liveCatalog
|
|
984
|
+
? { ...liveCatalog, source: liveCatalog.source || 'live_models' }
|
|
985
|
+
: persistedCatalog
|
|
986
|
+
? {
|
|
987
|
+
ok: false,
|
|
988
|
+
models: Array.isArray(persistedCatalog.gpt56_models) ? persistedCatalog.gpt56_models : [],
|
|
989
|
+
blockers: persistedCatalog.blockers || ['codex_lb_persisted_catalog_invalid'],
|
|
990
|
+
source: 'persisted_model_catalog_json'
|
|
991
|
+
}
|
|
992
|
+
: null);
|
|
954
993
|
const expectedCodexLbModels = ['gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol'];
|
|
955
994
|
const catalogModels = Array.isArray(codexLbModelCatalog?.models) ? codexLbModelCatalog.models.map(String) : [];
|
|
956
995
|
const expectedCodexLbModelsPresent = expectedCodexLbModels.every((model) => catalogModels.includes(model));
|
|
@@ -964,6 +1003,9 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
964
1003
|
...(codexLbProviderPresent && !codexLbProviderContractOk ? ['codex_lb_provider_contract_drift'] : []),
|
|
965
1004
|
...(codexLbApiKeySource !== 'missing' ? [] : ['codex_lb_api_key_missing']),
|
|
966
1005
|
...(codexLbBaseUrlSource !== 'missing' ? [] : ['codex_lb_base_url_missing']),
|
|
1006
|
+
...(codexLbSelectedDefault && !managedCatalogConfigured && !expectedCodexLbModelsPresent
|
|
1007
|
+
? ['codex_lb_model_catalog_json_unselected']
|
|
1008
|
+
: []),
|
|
967
1009
|
...(codexLbProviderContractOk && codexLbApiKeySource !== 'missing' && codexLbBaseUrlSource !== 'missing' && !expectedCodexLbModelsPresent
|
|
968
1010
|
? ['codex_lb_gpt_5_6_catalog_unverified']
|
|
969
1011
|
: [])
|
|
@@ -1022,9 +1064,13 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
1022
1064
|
base_url_source: codexLbBaseUrlSource,
|
|
1023
1065
|
model_catalog_checked: Boolean(codexLbModelCatalog),
|
|
1024
1066
|
model_catalog_ok: codexLbModelCatalog?.ok === true,
|
|
1067
|
+
model_catalog_source: codexLbModelCatalog?.source || null,
|
|
1068
|
+
model_catalog_json_configured: managedCatalogConfigured,
|
|
1069
|
+
model_catalog_json_path: managedCatalogConfigured ? managedCatalogPath : configuredCatalogPath || null,
|
|
1025
1070
|
model_catalog_models: catalogModels,
|
|
1026
1071
|
expected_models: expectedCodexLbModels,
|
|
1027
1072
|
expected_models_present: expectedCodexLbModelsPresent,
|
|
1073
|
+
tools_transport: codexLbModelCatalog?.tools_transport || null,
|
|
1028
1074
|
model_catalog_blockers: codexLbModelCatalog?.blockers || [],
|
|
1029
1075
|
setup_command: setupCommand,
|
|
1030
1076
|
set_key_command: setKeyCommand,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { ui as cliUi } from '../../cli/cli-theme.js';
|
|
3
|
-
import { createMission, findLatestMission, getOrCreateSessionMission, loadStateForSession, loadMission, sessionStateKey, setCurrent, updateCurrentIfMissionAndRun } from '../mission.js';
|
|
3
|
+
import { createMission, findLatestMission, getOrCreateExplicitNarutoMission, getOrCreateSessionMission, loadStateForSession, loadMission, sessionStateKey, setCurrent, updateCurrentIfMissionAndRun } from '../mission.js';
|
|
4
4
|
import { closeWorkOrderLedgerForRouteResult, createAndWriteWorkOrderLedgerForPrompt } from '../work-order-ledger.js';
|
|
5
5
|
import { appendJsonl, exists, nowIso, readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
6
6
|
import { SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME, bindTrustworthySubagentParentSummaryToRun, persistOrReuseTrustworthySubagentParentSummary, readSubagentEvents, writeSubagentEvidence } from '../subagents/subagent-evidence.js';
|
|
@@ -54,6 +54,8 @@ async function narutoRun(parsed) {
|
|
|
54
54
|
const mission = await resolveRunMission(root, parsed, sessionKey);
|
|
55
55
|
if (!mission)
|
|
56
56
|
return missingRunMission(parsed);
|
|
57
|
+
if (!mission.ok)
|
|
58
|
+
return blockedRunMission(parsed, mission.blockers);
|
|
57
59
|
const admission = await withNarutoMissionRunAdmission({
|
|
58
60
|
missionId: mission.id,
|
|
59
61
|
missionDir: mission.dir,
|
|
@@ -70,6 +72,8 @@ async function narutoRunTransaction(parsed, root, appSession, sessionKey, resolv
|
|
|
70
72
|
const mission = resolvedMission || await resolveRunMission(root, parsed, sessionKey);
|
|
71
73
|
if (!mission)
|
|
72
74
|
return missingRunMission(parsed);
|
|
75
|
+
if (!mission.ok)
|
|
76
|
+
return blockedRunMission(parsed, mission.blockers);
|
|
73
77
|
const { id, dir } = mission;
|
|
74
78
|
if (appSession && sessionKey) {
|
|
75
79
|
const pending = await readPendingAppNarutoRun(root, { id, dir }, sessionKey, parsed.prompt);
|
|
@@ -442,8 +446,14 @@ function narutoHelp(parsed) {
|
|
|
442
446
|
}
|
|
443
447
|
async function resolveRunMission(root, parsed, sessionKey = null) {
|
|
444
448
|
if (parsed.missionId && parsed.missionId !== 'latest') {
|
|
445
|
-
const
|
|
446
|
-
|
|
449
|
+
const resolved = await getOrCreateExplicitNarutoMission(root, {
|
|
450
|
+
requestedId: parsed.missionId,
|
|
451
|
+
prompt: parsed.prompt,
|
|
452
|
+
sessionKey
|
|
453
|
+
});
|
|
454
|
+
if (!resolved.ok)
|
|
455
|
+
return { ok: false, blockers: resolved.blockers };
|
|
456
|
+
return { ok: true, id: resolved.id, dir: resolved.dir };
|
|
447
457
|
}
|
|
448
458
|
if (sessionKey) {
|
|
449
459
|
const resolved = await getOrCreateSessionMission(root, {
|
|
@@ -459,10 +469,10 @@ async function resolveRunMission(root, parsed, sessionKey = null) {
|
|
|
459
469
|
: null;
|
|
460
470
|
}
|
|
461
471
|
});
|
|
462
|
-
return { id: resolved.id, dir: resolved.dir };
|
|
472
|
+
return { ok: true, id: String(resolved.id), dir: String(resolved.dir) };
|
|
463
473
|
}
|
|
464
474
|
const created = await createMission(root, { mode: 'naruto', prompt: parsed.prompt, sessionKey });
|
|
465
|
-
return { id: created.id, dir: created.dir };
|
|
475
|
+
return { ok: true, id: String(created.id), dir: String(created.dir) };
|
|
466
476
|
}
|
|
467
477
|
async function resolveReadMission(parsed) {
|
|
468
478
|
const root = await sksRoot();
|
|
@@ -696,6 +706,14 @@ function missingRunMission(parsed) {
|
|
|
696
706
|
blockers: [`naruto_mission_not_found:${parsed.missionId}`]
|
|
697
707
|
}, () => console.error(`Naruto mission not found: ${parsed.missionId}`), true);
|
|
698
708
|
}
|
|
709
|
+
function blockedRunMission(parsed, blockers) {
|
|
710
|
+
return emit(parsed, {
|
|
711
|
+
schema: NARUTO_RESULT_SCHEMA,
|
|
712
|
+
ok: false,
|
|
713
|
+
status: 'blocked',
|
|
714
|
+
blockers
|
|
715
|
+
}, () => console.error(`Naruto mission blocked: ${blockers.join(', ')}`), true);
|
|
716
|
+
}
|
|
699
717
|
function emit(parsed, result, human, failed = false) {
|
|
700
718
|
if (failed)
|
|
701
719
|
process.exitCode = 1;
|
|
@@ -34,6 +34,8 @@ import { recordOfficialSubagentParentOutcomesTelemetry, recordOfficialSubagentZe
|
|
|
34
34
|
import { bindTrustworthySubagentParentSummaryToRun, normalizeSubagentEvent, normalizeSubagentParentSummary, persistOrReuseTrustworthySubagentParentSummary, readSubagentEvents, recordSubagentEvent, SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME, writeSubagentEvidence } from './subagents/subagent-evidence.js';
|
|
35
35
|
import { officialSubagentPreparationInProgress, withOfficialSubagentLifecycleLock, writeNarutoGate } from './subagents/official-subagent-preparation.js';
|
|
36
36
|
import { effectiveSubagentTarget, normalizeLegacySubagentCountFields, refreshSubagentWaveLifecycle, subagentCountContractBlockers } from './subagents/wave-lifecycle.js';
|
|
37
|
+
import { buildWaveParentGuidance, renderWaveParentGuidance } from './subagents/wave-parent-guidance.js';
|
|
38
|
+
import { managedOfficialSubagentRoleByName } from './managed-assets/managed-assets-manifest.js';
|
|
37
39
|
import { SSOT_GUARD_ARTIFACT, validateSsotGuardArtifact } from './safety/ssot-guard.js';
|
|
38
40
|
const LIGHT_ROUTE_STOP_ARTIFACT = 'light-route-stop.json';
|
|
39
41
|
const CODEX_GIT_ACTION_STOP_ARTIFACT = 'codex-git-action-stop-bypass.json';
|
|
@@ -134,16 +136,60 @@ async function hookSubagentStart(root, state, payload = {}, sessionKey = null) {
|
|
|
134
136
|
const config = await readOfficialSubagentConfig(root);
|
|
135
137
|
const budget = resolveSubagentThreadBudget({ configuredMaxThreads: config.maxThreads });
|
|
136
138
|
const active = subagentRouteContext(state);
|
|
139
|
+
const routingContext = await sealedSubagentRoutingContext(artifactDir, payload);
|
|
137
140
|
const resourceGuard = [
|
|
138
141
|
`SKS subagent policy: Codex [agents].max_threads is ${budget.maxThreads}.`,
|
|
139
142
|
'Use max_depth=1. Subagents must not spawn subagents.',
|
|
140
143
|
'Do not duplicate an already assigned slice.',
|
|
141
144
|
'Parallel writes require disjoint paths; serialize overlapping paths.',
|
|
142
|
-
'
|
|
145
|
+
'Finish only your assigned slice, return a concise result, then stop so the root parent can close this thread.'
|
|
143
146
|
].join(' ');
|
|
144
|
-
const additionalContext = [coreEngineeringDirectiveReferenceText(), resourceGuard, active].filter(Boolean).join('\n\n');
|
|
147
|
+
const additionalContext = [coreEngineeringDirectiveReferenceText(), resourceGuard, routingContext, active].filter(Boolean).join('\n\n');
|
|
145
148
|
return { continue: true, additionalContext };
|
|
146
149
|
}
|
|
150
|
+
async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
|
|
151
|
+
const plan = await readJson(path.join(artifactDir, 'subagent-plan.json'), null).catch(() => null);
|
|
152
|
+
if (!plan || plan.workflow !== 'official_codex_subagent')
|
|
153
|
+
return '';
|
|
154
|
+
const agentName = extractSubagentAgentName(payload);
|
|
155
|
+
const agents = plan.agents && typeof plan.agents === 'object' ? plan.agents : {};
|
|
156
|
+
const planned = agentName && agents[agentName] ? agents[agentName] : null;
|
|
157
|
+
const role = agentName ? managedOfficialSubagentRoleByName(agentName) : null;
|
|
158
|
+
const model = String(planned?.routed_model || planned?.model || role?.model || '').trim();
|
|
159
|
+
const effort = String(planned?.routed_model_reasoning_effort || planned?.model_reasoning_effort || role?.model_reasoning_effort || '').trim();
|
|
160
|
+
if (!agentName && !model)
|
|
161
|
+
return '';
|
|
162
|
+
return [
|
|
163
|
+
'SKS sealed child routing:',
|
|
164
|
+
agentName ? `- custom agent: ${agentName}` : null,
|
|
165
|
+
model ? `- model: ${model}` : null,
|
|
166
|
+
effort ? `- model_reasoning_effort: ${effort}` : null,
|
|
167
|
+
'- keep this sealed profile; do not retarget model/effort or spawn nested agents'
|
|
168
|
+
].filter(Boolean).join('\n');
|
|
169
|
+
}
|
|
170
|
+
function extractSubagentAgentName(payload = {}) {
|
|
171
|
+
const candidates = [
|
|
172
|
+
payload.agent_type,
|
|
173
|
+
payload.agentType,
|
|
174
|
+
payload.subagent_type,
|
|
175
|
+
payload.subagentType,
|
|
176
|
+
payload.agent_name,
|
|
177
|
+
payload.agentName,
|
|
178
|
+
payload.agent,
|
|
179
|
+
payload.role,
|
|
180
|
+
payload.payload?.agent_type,
|
|
181
|
+
payload.payload?.agentType,
|
|
182
|
+
payload.payload?.subagent_type,
|
|
183
|
+
payload.data?.agent_type,
|
|
184
|
+
payload.input?.agent_type
|
|
185
|
+
];
|
|
186
|
+
for (const value of candidates) {
|
|
187
|
+
const name = String(value || '').trim();
|
|
188
|
+
if (name)
|
|
189
|
+
return name;
|
|
190
|
+
}
|
|
191
|
+
return '';
|
|
192
|
+
}
|
|
147
193
|
function subagentRouteContext(state = {}) {
|
|
148
194
|
if (!state?.route && !state?.mode)
|
|
149
195
|
return '';
|
|
@@ -486,7 +532,6 @@ function activeGoalOverlayContext(state = {}, route = null) {
|
|
|
486
532
|
].join('\n');
|
|
487
533
|
}
|
|
488
534
|
async function hookPreTool(root, state, payload, noQuestion, sessionKey = null) {
|
|
489
|
-
void sessionKey;
|
|
490
535
|
if (needsMutationSafetyCheck(payload)) {
|
|
491
536
|
const madSksImmutableDecision = await checkMadSksImmutableModification(root, state, payload);
|
|
492
537
|
if (madSksImmutableDecision.action === 'block') {
|
|
@@ -510,8 +555,37 @@ async function hookPreTool(root, state, payload, noQuestion, sessionKey = null)
|
|
|
510
555
|
return agentRecursionDecision;
|
|
511
556
|
if (noQuestion && looksInteractiveCommand(command))
|
|
512
557
|
return { decision: 'block', reason: interactiveCommandReason(command) };
|
|
558
|
+
const waveGuidance = await parentWaveGuidanceContext(root, state, sessionKey).catch(() => '');
|
|
559
|
+
if (waveGuidance) {
|
|
560
|
+
return {
|
|
561
|
+
continue: true,
|
|
562
|
+
additionalContext: waveGuidance,
|
|
563
|
+
systemMessage: visibleHookMessage('pre-tool', 'SKS Naruto wave lifecycle requires root-parent follow-up.')
|
|
564
|
+
};
|
|
565
|
+
}
|
|
513
566
|
return { continue: true };
|
|
514
567
|
}
|
|
568
|
+
async function parentWaveGuidanceContext(root, state = {}, sessionKey = null) {
|
|
569
|
+
if (!state?.mission_id && !state?.official_subagent_run_id)
|
|
570
|
+
return '';
|
|
571
|
+
const isNaruto = String(state?.mode || '').toUpperCase() === 'NARUTO'
|
|
572
|
+
|| String(state?.route || state?.route_command || '').replace(/^\$/, '').toUpperCase() === 'NARUTO'
|
|
573
|
+
|| state?.subagents_required === true;
|
|
574
|
+
if (!isNaruto)
|
|
575
|
+
return '';
|
|
576
|
+
// Child worker hooks must not receive parent wave spawn instructions.
|
|
577
|
+
if (agentWorkerHookContext(state, {}))
|
|
578
|
+
return '';
|
|
579
|
+
const artifactDir = officialSubagentArtifactDir(root, state, sessionKey);
|
|
580
|
+
const plan = await readJson(path.join(artifactDir, 'subagent-plan.json'), null).catch(() => null);
|
|
581
|
+
if (!plan || plan.workflow !== 'official_codex_subagent')
|
|
582
|
+
return '';
|
|
583
|
+
const lifecycle = plan.wave_lifecycle || null;
|
|
584
|
+
const guidance = lifecycle?.parent_guidance || buildWaveParentGuidance(lifecycle);
|
|
585
|
+
if (!guidance?.required)
|
|
586
|
+
return '';
|
|
587
|
+
return renderWaveParentGuidance(guidance);
|
|
588
|
+
}
|
|
515
589
|
function agentWorkerHookRecursionDecision(state = {}, payload = {}, command = '') {
|
|
516
590
|
if (!agentWorkerHookContext(state, payload))
|
|
517
591
|
return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sha256 } from '../fsx.js';
|
|
2
2
|
import { subagentModelProfile } from '../subagents/model-policy.js';
|
|
3
3
|
export const MANAGED_ASSET_SCHEMA_VERSION = 1;
|
|
4
|
-
export const MANAGED_ASSET_VERSION = '
|
|
4
|
+
export const MANAGED_ASSET_VERSION = '7.0.2';
|
|
5
5
|
export const MANAGED_ASSET_MARKER = 'SKS-MANAGED-ASSET';
|
|
6
6
|
export const MANAGED_OFFICIAL_SUBAGENT_MARKER = 'SKS-MANAGED-OFFICIAL-SUBAGENT';
|
|
7
7
|
/** Internal cleanup tombstones for SKS-owned role files retired from the installed catalog. */
|
package/dist/core/mission.js
CHANGED
|
@@ -21,11 +21,81 @@ export function sessionStateKey(sessionKey = 'default') {
|
|
|
21
21
|
export function stateFileForSession(root, sessionKey) {
|
|
22
22
|
return path.join(stateSessionsDir(root), `${sessionStateKey(sessionKey)}.json`);
|
|
23
23
|
}
|
|
24
|
-
export
|
|
25
|
-
|
|
24
|
+
export function validateExternallyReservedMissionId(rawId) {
|
|
25
|
+
const id = String(rawId ?? '');
|
|
26
|
+
if (!id.startsWith('M-'))
|
|
27
|
+
return { ok: false, reason: 'missing_m_prefix' };
|
|
28
|
+
if (id.length < 3 || id.length > 160)
|
|
29
|
+
return { ok: false, reason: 'length' };
|
|
30
|
+
if (id.trim() !== id)
|
|
31
|
+
return { ok: false, reason: 'normalization' };
|
|
32
|
+
if (id.includes('/') || id.includes('\\') || id.includes('\0') || /\s/.test(id) || id.includes('..')) {
|
|
33
|
+
return { ok: false, reason: 'path' };
|
|
34
|
+
}
|
|
35
|
+
if (!/^[A-Za-z0-9._-]+$/.test(id))
|
|
36
|
+
return { ok: false, reason: 'charset' };
|
|
37
|
+
return { ok: true, id };
|
|
38
|
+
}
|
|
39
|
+
export async function createMission(root, { mode, prompt, sessionKey, requestedId }) {
|
|
40
|
+
return withStateLock(root, () => createMissionUnlocked(root, { mode, prompt, sessionKey, requestedId }));
|
|
41
|
+
}
|
|
42
|
+
export async function getOrCreateExplicitNarutoMission(root, input) {
|
|
43
|
+
const validated = validateExternallyReservedMissionId(input.requestedId);
|
|
44
|
+
if (!validated.ok) {
|
|
45
|
+
return { ok: false, blockers: [`naruto_mission_id_invalid:${validated.reason}`] };
|
|
46
|
+
}
|
|
47
|
+
const normalizedPrompt = String(input.prompt || '').trim();
|
|
48
|
+
return withStateLock(root, async () => {
|
|
49
|
+
let loaded = await loadMission(root, validated.id).catch(() => null);
|
|
50
|
+
if (!loaded) {
|
|
51
|
+
try {
|
|
52
|
+
const created = await createMissionUnlocked(root, {
|
|
53
|
+
mode: 'naruto',
|
|
54
|
+
prompt: normalizedPrompt,
|
|
55
|
+
sessionKey: input.sessionKey,
|
|
56
|
+
requestedId: validated.id
|
|
57
|
+
});
|
|
58
|
+
return { ok: true, id: String(created.id), dir: String(created.dir) };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error?.code !== 'mission_already_exists')
|
|
62
|
+
throw error;
|
|
63
|
+
loaded = await loadMission(root, validated.id).catch(() => null);
|
|
64
|
+
if (!loaded) {
|
|
65
|
+
return { ok: false, blockers: [`naruto_mission_not_found:${validated.id}`] };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (String(loaded.mission?.mode || '') !== 'naruto') {
|
|
70
|
+
return { ok: false, blockers: ['naruto_mission_identity_conflict:mission_mode'] };
|
|
71
|
+
}
|
|
72
|
+
if (String(loaded.mission?.prompt || '').trim() !== normalizedPrompt) {
|
|
73
|
+
return { ok: false, blockers: ['naruto_mission_identity_conflict:mission_prompt'] };
|
|
74
|
+
}
|
|
75
|
+
return { ok: true, id: String(loaded.id), dir: String(loaded.dir) };
|
|
76
|
+
});
|
|
26
77
|
}
|
|
27
|
-
async function createMissionUnlocked(root, { mode, prompt, sessionKey }) {
|
|
28
|
-
|
|
78
|
+
async function createMissionUnlocked(root, { mode, prompt, sessionKey, requestedId }) {
|
|
79
|
+
let id;
|
|
80
|
+
if (requestedId != null && String(requestedId).length > 0) {
|
|
81
|
+
const validated = validateExternallyReservedMissionId(requestedId);
|
|
82
|
+
if (!validated.ok) {
|
|
83
|
+
const err = new Error(`invalid_mission_id:${validated.reason}`);
|
|
84
|
+
err.code = 'invalid_mission_id';
|
|
85
|
+
err.reason = validated.reason;
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
id = validated.id;
|
|
89
|
+
if (await exists(path.join(missionDir(root, id), 'mission.json'))) {
|
|
90
|
+
const err = new Error('mission_already_exists');
|
|
91
|
+
err.code = 'mission_already_exists';
|
|
92
|
+
err.id = id;
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
id = missionId();
|
|
98
|
+
}
|
|
29
99
|
const dir = missionDir(root, id);
|
|
30
100
|
await ensureDir(dir);
|
|
31
101
|
await ensureDir(path.join(dir, 'bus'));
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const DEFAULT_MAX_PACK_BYTES =
|
|
1
|
+
export const DEFAULT_MAX_PACK_BYTES = 2464 * 1024;
|
|
2
2
|
export const DEFAULT_MAX_UNPACKED_BYTES = 11_141_120;
|
|
@@ -162,12 +162,12 @@ export function projectNarutoProofSnapshot(input) {
|
|
|
162
162
|
mission_id: missionId,
|
|
163
163
|
workflow: 'official_codex_subagent',
|
|
164
164
|
workflow_run_id: workflowRunId,
|
|
165
|
+
blockers: uniqueBlockers,
|
|
165
166
|
evidence: persistedEvidence,
|
|
166
167
|
summary,
|
|
167
168
|
gate,
|
|
168
169
|
result,
|
|
169
|
-
proof_fingerprint: proofFingerprint
|
|
170
|
-
...(uniqueBlockers.length > 0 ? { blockers: uniqueBlockers } : {})
|
|
170
|
+
proof_fingerprint: proofFingerprint
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
173
|
export function validateNarutoProofStatus(value) {
|
|
@@ -58,17 +58,20 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
|
|
|
58
58
|
const maxDepth = resolveLayeredValue(projectLayer.agents.max_depth, globalLayer.agents.max_depth, DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH, positiveInteger);
|
|
59
59
|
const jobMaxRuntimeSeconds = resolveLayeredValue(projectLayer.agents.job_max_runtime_seconds, globalLayer.agents.job_max_runtime_seconds, DEFAULT_OFFICIAL_SUBAGENT_JOB_MAX_RUNTIME_SECONDS, positiveInteger);
|
|
60
60
|
const interruptMessage = resolveLayeredValue(projectLayer.agents.interrupt_message, globalLayer.agents.interrupt_message, DEFAULT_OFFICIAL_SUBAGENT_INTERRUPT_MESSAGE, booleanValue);
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// Official Naruto admits only depth=1. Coerce higher values so they cannot
|
|
62
|
+
// contradict the launcher hard-enforcement or look "supported".
|
|
63
|
+
const depthCoerced = maxDepth.value > 1;
|
|
64
|
+
const warnings = depthCoerced
|
|
65
|
+
? [`official_subagent_max_depth_coerced_to_one:${maxDepth.value}:${maxDepth.source}`]
|
|
63
66
|
: [];
|
|
64
67
|
return {
|
|
65
68
|
maxThreads: maxThreads.value,
|
|
66
|
-
maxDepth: maxDepth.value,
|
|
69
|
+
maxDepth: depthCoerced ? DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH : maxDepth.value,
|
|
67
70
|
jobMaxRuntimeSeconds: jobMaxRuntimeSeconds.value,
|
|
68
71
|
interruptMessage: interruptMessage.value,
|
|
69
72
|
sources: {
|
|
70
73
|
maxThreads: maxThreads.source,
|
|
71
|
-
maxDepth: maxDepth.source,
|
|
74
|
+
maxDepth: depthCoerced ? 'default' : maxDepth.source,
|
|
72
75
|
jobMaxRuntimeSeconds: jobMaxRuntimeSeconds.source,
|
|
73
76
|
interruptMessage: interruptMessage.source
|
|
74
77
|
},
|
|
@@ -85,7 +88,7 @@ export function officialSubagentConfigWarnings(text = '', inheritedText = '') {
|
|
|
85
88
|
return [];
|
|
86
89
|
const maxDepth = resolveLayeredValue(project.agents.max_depth, inherited.agents.max_depth, DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH, positiveInteger);
|
|
87
90
|
return maxDepth.value > 1
|
|
88
|
-
? [`
|
|
91
|
+
? [`official_subagent_max_depth_coerced_to_one:${maxDepth.value}:${maxDepth.source}`]
|
|
89
92
|
: [];
|
|
90
93
|
}
|
|
91
94
|
export async function readInheritedOfficialSubagentConfigText(projectConfigPath, opts = {}) {
|
|
@@ -10,6 +10,7 @@ import { NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from './model-policy.js';
|
|
|
10
10
|
import { MAX_ON_DEMAND_SUBAGENT_ROLE_COUNT, officialSubagentFanoutPolicy, officialSubagentOnDemandRolePlan, officialSubagentRoleCatalog, recommendOfficialSubagentRoles } from './agent-catalog.js';
|
|
11
11
|
import { resolveSubagentThreadBudget } from './thread-budget.js';
|
|
12
12
|
import { createSubagentWaveLifecycle } from './wave-lifecycle.js';
|
|
13
|
+
import { decideOfficialSubagentModel } from '../agents/agent-effort-policy.js';
|
|
13
14
|
import { readBoundedTriwikiAttention } from './triwiki-attention.js';
|
|
14
15
|
import { SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME, normalizeSubagentParentSummary, writeSubagentEvidence } from './subagent-evidence.js';
|
|
15
16
|
import { sksPrefixedDollarCommand, unprefixedSksSkillName } from '../routes/dollar-prefix.js';
|
|
@@ -113,6 +114,28 @@ async function prepareOfficialSubagentMissionLocked(input) {
|
|
|
113
114
|
recommendedAgents: suggestedAgents
|
|
114
115
|
});
|
|
115
116
|
const selectedAgentPlan = officialSubagentOnDemandRolePlan(suggestedAgents);
|
|
117
|
+
const agentRouting = Object.fromEntries(Object.entries(selectedAgentPlan).map(([name, config]) => {
|
|
118
|
+
const decision = decideOfficialSubagentModel({
|
|
119
|
+
persona: {
|
|
120
|
+
role: name,
|
|
121
|
+
naruto_role: name,
|
|
122
|
+
write_policy: config.sandbox_mode === 'read-only' ? 'read-only' : 'route-local-artifact',
|
|
123
|
+
read_only: config.sandbox_mode === 'read-only'
|
|
124
|
+
},
|
|
125
|
+
prompt: goal,
|
|
126
|
+
agentId: name,
|
|
127
|
+
readonly: config.sandbox_mode === 'read-only'
|
|
128
|
+
});
|
|
129
|
+
return [name, {
|
|
130
|
+
...config,
|
|
131
|
+
// Catalog TOML remains the spawn-type contract; dynamic decision records why
|
|
132
|
+
// this role maps onto the sealed four-profile matrix for this goal.
|
|
133
|
+
routed_model: decision.model,
|
|
134
|
+
routed_model_reasoning_effort: decision.model_reasoning_effort,
|
|
135
|
+
routed_model_policy: decision.model_selection_reason,
|
|
136
|
+
routing_dynamic: true
|
|
137
|
+
}];
|
|
138
|
+
}));
|
|
116
139
|
const agentCatalog = onDemandAgentCatalogMetadata(selectedAgentPlan);
|
|
117
140
|
const ssotGuard = buildSsotGuard({ route: input.route, mode: mode === 'naruto' ? 'NARUTO' : 'OFFICIAL_SUBAGENT', task: goal });
|
|
118
141
|
const ssotGuardValidation = validateSsotGuardArtifact(ssotGuard);
|
|
@@ -164,7 +187,7 @@ async function prepareOfficialSubagentMissionLocked(input) {
|
|
|
164
187
|
model_reasoning_effort: NARUTO_PARENT_EFFORT
|
|
165
188
|
},
|
|
166
189
|
agent_catalog: agentCatalog,
|
|
167
|
-
agents:
|
|
190
|
+
agents: agentRouting,
|
|
168
191
|
verification_budget: verification,
|
|
169
192
|
verification_checks: [],
|
|
170
193
|
verification: { budget: verification },
|
|
@@ -88,11 +88,12 @@ Subagent rules:
|
|
|
88
88
|
- if paths overlap, run those slices serially
|
|
89
89
|
- reject duplicate slice fingerprints and homogeneous clone work; diversity may come from roles, disjoint shards, or different tool surfaces
|
|
90
90
|
- security, database, release, authorization, and irreversible-effect checks are protected strata; aggregate speed or accuracy never offsets a failed protected gate
|
|
91
|
-
- after each settled wave: collect results, close completed threads, refresh evidence and the wave lifecycle ledger, rescan the ready DAG, then launch the next defensible direct-child wave when
|
|
91
|
+
- after each settled wave: collect results, close completed threads, refresh evidence and the wave lifecycle ledger, read \`wave_lifecycle.next_parent_actions\` / \`parent_guidance\`, rescan the ready DAG, then launch the next defensible direct-child wave when \`remaining_to_start > 0\`
|
|
92
92
|
- recovered thread capacity is reusable by later root-owned waves; completed child threads do not permanently consume the mission fan-out budget
|
|
93
|
+
- when PreTool/UserPrompt guidance says \`spawn_next_direct_child_wave_upto:N\`, spawn that next wave immediately with sealed custom-agent model/effort profiles; do not wait for another user message
|
|
93
94
|
- automatic targets may resize between waves when the ready DAG changes, but update plan/evidence before spawning; explicit operator and route-owned counts remain exact
|
|
94
95
|
- wait for every final planned subagent before integrating
|
|
95
|
-
- close completed threads after collecting results
|
|
96
|
+
- close completed threads after collecting results so capacity returns to the root parent
|
|
96
97
|
${parentDecompositionRequired ? `- decomposition status: parent_required
|
|
97
98
|
- before spawning, decompose the goal into independent, non-overlapping slices
|
|
98
99
|
- do not invent write scopes merely to reach the requested count
|
|
@@ -39,7 +39,14 @@ const OFFICIAL_SUBAGENT_CHILD_ENV_ALLOWLIST = Object.freeze([
|
|
|
39
39
|
'CODEX_CI',
|
|
40
40
|
'CODEX_MANAGED_BY_NPM',
|
|
41
41
|
'CODEX_INTERNAL_ORIGINATOR',
|
|
42
|
-
'CODEX_SANDBOX_NETWORK_DISABLED'
|
|
42
|
+
'CODEX_SANDBOX_NETWORK_DISABLED',
|
|
43
|
+
'SKS_AGENT_MODE',
|
|
44
|
+
'ACAS_AGENT_SLUG',
|
|
45
|
+
'ACAS_AGENT_WORKSPACE',
|
|
46
|
+
'ALFREDO_AGENT_SOULS_FILE',
|
|
47
|
+
'ACAS_CHROME_PATH',
|
|
48
|
+
'ACAS_HTML_TO_PDF_ENGINE',
|
|
49
|
+
'ACAS_HTML_TO_PDF_ALLOW_CHROME_CLI_FALLBACK'
|
|
43
50
|
]);
|
|
44
51
|
const SECRET_ENV_KEY_RE = /(?:api[_-]?key|token|secret|password|credential|authorization|cookie)/i;
|
|
45
52
|
const SECRET_BEARING_ENV_KEYS = new Set([
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
3
|
import { readSubagentEvents } from './subagent-evidence.js';
|
|
4
4
|
import { MAX_AUTOMATIC_SUBAGENT_COUNT } from './agent-catalog.js';
|
|
5
|
+
import { buildWaveParentGuidance } from './wave-parent-guidance.js';
|
|
5
6
|
export const SUBAGENT_WAVE_LIFECYCLE_SCHEMA = 'sks.subagent-wave-lifecycle.v1';
|
|
6
7
|
export function subagentCountPolicy(plan) {
|
|
7
8
|
const lifecycle = plan?.wave_lifecycle;
|
|
@@ -89,6 +90,15 @@ export function createSubagentWaveLifecycle(input) {
|
|
|
89
90
|
remaining_to_start: normalizeCount(input.targetSubagents),
|
|
90
91
|
post_wave_rescan_required: false,
|
|
91
92
|
recovered_capacity: 0,
|
|
93
|
+
next_parent_actions: [],
|
|
94
|
+
parent_guidance: buildWaveParentGuidance({
|
|
95
|
+
remaining_to_start: normalizeCount(input.targetSubagents),
|
|
96
|
+
open_threads: 0,
|
|
97
|
+
recovered_capacity: 0,
|
|
98
|
+
post_wave_rescan_required: false,
|
|
99
|
+
current_wave: 0,
|
|
100
|
+
completed_waves: 0
|
|
101
|
+
}),
|
|
92
102
|
waves: [],
|
|
93
103
|
last_event: null,
|
|
94
104
|
updated_at: nowIso()
|
|
@@ -188,6 +198,14 @@ function projectLifecycle(previous, input) {
|
|
|
188
198
|
const postWaveRescanRequired = Boolean(lastWave?.status === 'settled'
|
|
189
199
|
&& openThreads === 0
|
|
190
200
|
&& remainingToStart > 0);
|
|
201
|
+
const parentGuidance = buildWaveParentGuidance({
|
|
202
|
+
remaining_to_start: remainingToStart,
|
|
203
|
+
open_threads: openThreads,
|
|
204
|
+
recovered_capacity: cumulativeSettled,
|
|
205
|
+
post_wave_rescan_required: postWaveRescanRequired,
|
|
206
|
+
current_wave: lastWave?.wave || 0,
|
|
207
|
+
completed_waves: waves.filter((wave) => wave.status === 'settled').length
|
|
208
|
+
});
|
|
191
209
|
return {
|
|
192
210
|
...previous,
|
|
193
211
|
workflow_run_id: input.workflowRunId,
|
|
@@ -205,6 +223,8 @@ function projectLifecycle(previous, input) {
|
|
|
205
223
|
remaining_to_start: remainingToStart,
|
|
206
224
|
post_wave_rescan_required: postWaveRescanRequired,
|
|
207
225
|
recovered_capacity: cumulativeSettled,
|
|
226
|
+
next_parent_actions: parentGuidance.actions,
|
|
227
|
+
parent_guidance: parentGuidance,
|
|
208
228
|
waves,
|
|
209
229
|
last_event: input.lastEvent,
|
|
210
230
|
updated_at: nowIso()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const WAVE_PARENT_GUIDANCE_SCHEMA = 'sks.subagent-wave-parent-guidance.v1';
|
|
2
|
+
export function buildWaveParentGuidance(lifecycle) {
|
|
3
|
+
const remaining = Math.max(0, Math.floor(Number(lifecycle?.remaining_to_start || 0)));
|
|
4
|
+
const openThreads = Math.max(0, Math.floor(Number(lifecycle?.open_threads || 0)));
|
|
5
|
+
const recovered = Math.max(0, Math.floor(Number(lifecycle?.recovered_capacity || 0)));
|
|
6
|
+
const rescan = lifecycle?.post_wave_rescan_required === true;
|
|
7
|
+
const actions = [];
|
|
8
|
+
if (openThreads > 0) {
|
|
9
|
+
actions.push('close_completed_child_threads_after_collecting_results');
|
|
10
|
+
}
|
|
11
|
+
if (rescan || remaining > 0) {
|
|
12
|
+
actions.push('refresh_wave_lifecycle_and_ready_dag');
|
|
13
|
+
actions.push(`spawn_next_direct_child_wave_upto:${Math.max(1, Math.min(remaining || recovered, recovered || remaining || 1))}`);
|
|
14
|
+
}
|
|
15
|
+
if (remaining === 0 && openThreads === 0 && !rescan) {
|
|
16
|
+
actions.push('integrate_settled_child_results_and_emit_parent_summary');
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
schema: WAVE_PARENT_GUIDANCE_SCHEMA,
|
|
20
|
+
required: actions.some((action) => action.startsWith('spawn_next_') || action.startsWith('close_')),
|
|
21
|
+
actions: [...new Set(actions)],
|
|
22
|
+
remaining_to_start: remaining,
|
|
23
|
+
open_threads: openThreads,
|
|
24
|
+
recovered_capacity: recovered,
|
|
25
|
+
post_wave_rescan_required: rescan,
|
|
26
|
+
current_wave: Math.max(0, Math.floor(Number(lifecycle?.current_wave || 0))),
|
|
27
|
+
completed_waves: Math.max(0, Math.floor(Number(lifecycle?.completed_waves || 0)))
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function renderWaveParentGuidance(guidance) {
|
|
31
|
+
if (!guidance.required && guidance.actions.length === 0)
|
|
32
|
+
return '';
|
|
33
|
+
return [
|
|
34
|
+
'SKS Naruto wave lifecycle (root parent only):',
|
|
35
|
+
`- current_wave=${guidance.current_wave}; completed_waves=${guidance.completed_waves}`,
|
|
36
|
+
`- open_threads=${guidance.open_threads}; remaining_to_start=${guidance.remaining_to_start}; recovered_capacity=${guidance.recovered_capacity}`,
|
|
37
|
+
`- post_wave_rescan_required=${guidance.post_wave_rescan_required}`,
|
|
38
|
+
...guidance.actions.map((action) => `- action: ${action}`),
|
|
39
|
+
'- max_depth=1: children must not spawn children; only this root may launch later direct-child waves',
|
|
40
|
+
'- after collecting results, close completed child threads so recovered capacity can be reused'
|
|
41
|
+
].join('\n');
|
|
42
|
+
}
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '
|
|
1
|
+
export const PACKAGE_VERSION = '7.0.2';
|