sneakoscope 10.3.7 → 10.3.8
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 +12 -181
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +61 -1
- package/dist/commands/codex-app.js +4 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/native-worker-backend-router.js +55 -0
- package/dist/core/codex-app/codex-app-restart-policy.js +20 -0
- package/dist/core/codex-app/codex-context-window.js +61 -24
- package/dist/core/codex-app/menubar/constants.js +4 -0
- package/dist/core/codex-compat/codex-hook-output-builders.js +3 -1
- package/dist/core/codex-lb/bridge-cli-contract.js +2 -0
- package/dist/core/codex-lb/desktop-bridge/exclusive-provider-guard.js +59 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +23 -15
- package/dist/core/codex-lb/desktop-bridge/index.js +1 -0
- package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +10 -3
- package/dist/core/codex-lb/desktop-bridge/security.js +63 -1
- package/dist/core/codex-lb/desktop-bridge/server.js +5 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +5 -1
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +31 -5
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +23 -4
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-catalog.js +39 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +286 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-main-model.js +101 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-status.js +68 -0
- package/dist/core/codex-lb/desktop-controller-v3/status.js +8 -5
- package/dist/core/codex-lb/desktop-controller-v3.js +26 -2
- package/dist/core/codex-lb/desktop-service.js +13 -2
- package/dist/core/commands/naruto-command.js +1 -1
- package/dist/core/decisions/child-model-choice.js +107 -0
- package/dist/core/decisions/cli.js +19 -2
- package/dist/core/decisions/routing.js +5 -4
- package/dist/core/hooks-runtime/hook-context.js +21 -7
- package/dist/core/hooks-runtime/hook-io.js +3 -2
- package/dist/core/hooks-runtime/jev-spawn-routing.js +83 -5
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -2
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +18 -1
- package/dist/core/hooks-runtime/subagent-context.js +13 -2
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +49 -6
- package/dist/core/hooks-runtime.js +12 -5
- package/dist/core/managed-assets/managed-assets-manifest.js +9 -4
- package/dist/core/subagents/child-model-allowlist.js +179 -0
- package/dist/core/subagents/child-model-plan.js +117 -0
- package/dist/core/subagents/naruto-host-credentials.js +82 -13
- package/dist/core/subagents/official-subagent-config.js +66 -15
- package/dist/core/subagents/official-subagent-preparation.js +45 -12
- package/dist/core/subagents/official-subagent-prompt.js +82 -11
- package/dist/core/subagents/official-subagent-runner.js +3 -0
- package/dist/core/subagents/read-only-list-role.js +73 -0
- package/dist/core/subagents/role-model-preferences.js +54 -17
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -0
- package/dist/native/sks-menubar/Sources/OpenRouterOnlyState.swift +314 -0
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +2 -1
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +15 -3
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouterOnly.swift +162 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +36 -18
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +10 -4
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +2 -0
- package/dist/native/sks-menubar/Sources/SubagentModelsModels.swift +201 -0
- package/dist/native/sks-menubar/Sources/SubagentModelsViewController.swift +373 -0
- package/package.json +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { consultJevOptions } from './integration.js';
|
|
2
|
+
import { canonicalChildModelId, defaultSubagentEntry, subagentEntryForModel } from '../subagents/child-model-allowlist.js';
|
|
3
|
+
const QUESTION_BUDGET_BYTES = 16_000;
|
|
4
|
+
const TASK_CHARS = 1200;
|
|
5
|
+
function optionId(index) {
|
|
6
|
+
return `m${index + 1}`;
|
|
7
|
+
}
|
|
8
|
+
function optionSummary(entry) {
|
|
9
|
+
const criteria = entry.criteria || 'General work; no special criteria.';
|
|
10
|
+
return `${entry.model}${entry.default ? ' (default)' : ''}: ${criteria}`;
|
|
11
|
+
}
|
|
12
|
+
function laneQuestionId(lane, index) {
|
|
13
|
+
const slug = String(lane.id || '').toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 24);
|
|
14
|
+
return `child_model_${slug || index}`.slice(0, 40);
|
|
15
|
+
}
|
|
16
|
+
export function childModelQuestion(id, entries, lane) {
|
|
17
|
+
const options = {};
|
|
18
|
+
entries.forEach((entry, index) => { options[optionId(index)] = optionSummary(entry); });
|
|
19
|
+
const fallback = entries.findIndex((entry) => entry.default);
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
instructions: 'Choose the model on the user\'s list that should run the child task in state.task. Each option is the user\'s rule for when to use that model; follow those rules. When several fit equally, choose the default.',
|
|
23
|
+
options,
|
|
24
|
+
state: {
|
|
25
|
+
task: String(lane.task || '').slice(0, TASK_CHARS),
|
|
26
|
+
role: lane.role || null,
|
|
27
|
+
requested_model: lane.requestedModel || null,
|
|
28
|
+
default_option: optionId(fallback === -1 ? 0 : fallback)
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function fallbackChildModel(state, requestedModel, reason) {
|
|
33
|
+
const requested = subagentEntryForModel(state, requestedModel);
|
|
34
|
+
if (requested)
|
|
35
|
+
return { entry: requested, source: 'requested', reason };
|
|
36
|
+
const fallback = defaultSubagentEntry(state);
|
|
37
|
+
return fallback ? { entry: fallback, source: 'default', reason } : null;
|
|
38
|
+
}
|
|
39
|
+
export async function chooseChildModels(input) {
|
|
40
|
+
const entries = input.state.subagent_models;
|
|
41
|
+
const result = {};
|
|
42
|
+
if (!entries.length)
|
|
43
|
+
return result;
|
|
44
|
+
const fallbackAll = (reason) => {
|
|
45
|
+
for (const lane of input.lanes) {
|
|
46
|
+
const choice = fallbackChildModel(input.state, lane.requestedModel, reason);
|
|
47
|
+
if (choice)
|
|
48
|
+
result[lane.id] = choice;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
if (entries.length === 1)
|
|
53
|
+
return fallbackAll('single_entry');
|
|
54
|
+
const asked = [];
|
|
55
|
+
const questions = [];
|
|
56
|
+
let bytes = 0;
|
|
57
|
+
input.lanes.forEach((lane, index) => {
|
|
58
|
+
const questionId = laneQuestionId(lane, index);
|
|
59
|
+
if (asked.some((row) => row.questionId === questionId))
|
|
60
|
+
return;
|
|
61
|
+
const question = childModelQuestion(questionId, entries, lane);
|
|
62
|
+
const size = Buffer.byteLength(JSON.stringify(question));
|
|
63
|
+
if (bytes + size > QUESTION_BUDGET_BYTES)
|
|
64
|
+
return;
|
|
65
|
+
bytes += size;
|
|
66
|
+
asked.push({ lane, questionId });
|
|
67
|
+
questions.push(question);
|
|
68
|
+
});
|
|
69
|
+
const decision = questions.length
|
|
70
|
+
? await consultJevOptions({
|
|
71
|
+
root: input.root,
|
|
72
|
+
workflowId: input.workflowId,
|
|
73
|
+
goal: input.goal,
|
|
74
|
+
questions,
|
|
75
|
+
facts: { openrouter_only: true, subagent_models: entries.map((entry) => canonicalChildModelId(entry.model)) },
|
|
76
|
+
...(input.env ? { env: input.env } : {}),
|
|
77
|
+
...(input.deadlineMs === undefined ? {} : { deadlineMs: input.deadlineMs })
|
|
78
|
+
})
|
|
79
|
+
: { called: false, choices: {}, reason: 'budget' };
|
|
80
|
+
for (const lane of input.lanes) {
|
|
81
|
+
const questionId = asked.find((row) => row.lane === lane)?.questionId;
|
|
82
|
+
const picked = questionId ? decision.choices[questionId] : undefined;
|
|
83
|
+
const pickedIndex = picked && /^m\d+$/.test(picked) ? Number(picked.slice(1)) - 1 : -1;
|
|
84
|
+
const entry = pickedIndex >= 0 ? entries[pickedIndex] : undefined;
|
|
85
|
+
if (entry) {
|
|
86
|
+
result[lane.id] = { entry, source: 'jev', reason: 'applied' };
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const reason = !questionId ? 'budget' : decision.reason === 'applied' ? 'keep_baseline' : decision.reason;
|
|
90
|
+
const choice = fallbackChildModel(input.state, lane.requestedModel, reason);
|
|
91
|
+
if (choice)
|
|
92
|
+
result[lane.id] = choice;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
export async function chooseChildModel(input) {
|
|
97
|
+
const choices = await chooseChildModels({
|
|
98
|
+
root: input.root,
|
|
99
|
+
workflowId: 'spawn-child-model',
|
|
100
|
+
goal: input.task || 'Spawn a child agent.',
|
|
101
|
+
lanes: [{ id: 'spawn', task: input.task, role: input.role ?? null, requestedModel: input.requestedModel ?? null }],
|
|
102
|
+
state: input.state,
|
|
103
|
+
...(input.env ? { env: input.env } : {}),
|
|
104
|
+
...(input.deadlineMs === undefined ? {} : { deadlineMs: input.deadlineMs })
|
|
105
|
+
});
|
|
106
|
+
return choices.spawn ?? null;
|
|
107
|
+
}
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { printJson } from '../../cli/output.js';
|
|
4
4
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
|
|
6
|
+
import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
|
|
6
7
|
import { jevEnabled, readDecisionConfig, writeDecisionConfig, jevCapabilityActive } from './config.js';
|
|
7
8
|
import { buildEvaluationReport } from './evaluation.js';
|
|
8
9
|
import { OPENROUTER_DECISIONS_ENDPOINT, OPENROUTER_DECISIONS_MODEL, requestOpenRouterDecision } from './openrouter.js';
|
|
@@ -103,7 +104,10 @@ export async function runDecisionCommand(args, env = process.env) {
|
|
|
103
104
|
output(report, json, () => [
|
|
104
105
|
`mode: ${report.mode} consent: ${report.consentCloud} credential: ${report.credential.present ? report.credential.source : 'missing'}`,
|
|
105
106
|
`model: ${report.model} endpoint: ${report.endpoint}`,
|
|
106
|
-
`capabilities: context=${report.capabilities.context.ready ? 'ready' : 'off'} plan=${report.capabilities.plan.ready ? 'ready' : 'off'} recovery=${report.capabilities.recovery.reason}
|
|
107
|
+
`capabilities: context=${report.capabilities.context.ready ? 'ready' : 'off'} plan=${report.capabilities.plan.ready ? 'ready' : 'off'} recovery=${report.capabilities.recovery.reason}`,
|
|
108
|
+
...(report.openrouter_only.enabled
|
|
109
|
+
? [`openrouter-only: on · child models by ${report.openrouter_only.child_model_decision} · ${report.openrouter_only.subagent_model_count} listed`]
|
|
110
|
+
: [])
|
|
107
111
|
]);
|
|
108
112
|
return 0;
|
|
109
113
|
}
|
|
@@ -139,6 +143,11 @@ export async function statusReport(env) {
|
|
|
139
143
|
: !jevEnabled(config)
|
|
140
144
|
? 'enable'
|
|
141
145
|
: 'ready';
|
|
146
|
+
const childModels = effectiveChildModelAllowlist({ env });
|
|
147
|
+
const openRouterOnly = childModels.mode === 'openrouter_only';
|
|
148
|
+
const childTierPoints = new Set(['spawn_tier', 'worker_tier', 'role_tiers', 'role_omission']);
|
|
149
|
+
const decisionPoints = ['turn_route', 'turn_tier', 'spawn_tier', 'worker_tier', 'role_tiers', 'role_omission', 'plan', 'context', 'parent_edit_delegation', 'image_need', 'image_parameters', 'qa_effort_escalation']
|
|
150
|
+
.filter((point) => !openRouterOnly || !childTierPoints.has(point));
|
|
142
151
|
return {
|
|
143
152
|
schema: 'sks.jev-decision-status.v1',
|
|
144
153
|
ok: true,
|
|
@@ -158,8 +167,16 @@ export async function statusReport(env) {
|
|
|
158
167
|
recovery: config.capabilities.recovery
|
|
159
168
|
},
|
|
160
169
|
decision_points: jevEnabled(config)
|
|
161
|
-
? [
|
|
170
|
+
? [...decisionPoints, ...(openRouterOnly ? ['child_model'] : [])]
|
|
162
171
|
: [],
|
|
172
|
+
openrouter_only: {
|
|
173
|
+
enabled: openRouterOnly,
|
|
174
|
+
subagent_model_count: openRouterOnly ? childModels.models.length : 0,
|
|
175
|
+
default_subagent_model: childModels.default_model,
|
|
176
|
+
child_model_decision: !openRouterOnly
|
|
177
|
+
? null
|
|
178
|
+
: jevEnabled(config) && resolved.key && childModels.models.length > 1 ? 'jev' : 'default_entry'
|
|
179
|
+
},
|
|
163
180
|
recovery: RECOVERY_CAPABILITY,
|
|
164
181
|
nextStep,
|
|
165
182
|
notes: [
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { effortForTier,
|
|
1
|
+
import { effortForTier, resolveLatestModelTiers } from '../subagents/model-tiers.js';
|
|
2
|
+
import { effectiveChildModelAllowlist, isAllowedChildModel } from '../subagents/child-model-allowlist.js';
|
|
2
3
|
import { routingTier } from './types.js';
|
|
3
4
|
const ROLE_ID = /^[a-z][a-z0-9_]{0,40}$/;
|
|
4
5
|
export const MAX_JEV_ROUTING_ROLES = 8;
|
|
@@ -16,14 +17,14 @@ export function buildRoutingCandidates(input) {
|
|
|
16
17
|
}
|
|
17
18
|
return out;
|
|
18
19
|
}
|
|
19
|
-
export function applySealedRouting(agents, selected) {
|
|
20
|
+
export function applySealedRouting(agents, selected, allowlist) {
|
|
20
21
|
if (!selected || !agents)
|
|
21
22
|
return { ...(agents || {}) };
|
|
22
23
|
const next = { ...agents };
|
|
23
|
-
const
|
|
24
|
+
const childModels = allowlist ?? effectiveChildModelAllowlist();
|
|
24
25
|
for (const [name, effort] of Object.entries(selected.efforts)) {
|
|
25
26
|
const model = selected.models[name];
|
|
26
|
-
if (!model || !
|
|
27
|
+
if (!model || !isAllowedChildModel(model, childModels))
|
|
27
28
|
continue;
|
|
28
29
|
const row = next[name];
|
|
29
30
|
if (!row || row.routing_dynamic !== true || row.routed_model_policy === 'user_role_model_preference')
|
|
@@ -8,6 +8,8 @@ import { renderAuthoritativeSksSkillContext } from '../codex-native/sks-skill-pa
|
|
|
8
8
|
import { resolveManagedSkillSourcesForAdmission } from './managed-skill-admission.js';
|
|
9
9
|
import { managedSkillDigestBlocksEnforced } from '../verification-profile.js';
|
|
10
10
|
import { looksLikeActiveContinuationPrompt } from './naruto-decision-gate.js';
|
|
11
|
+
import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
|
|
12
|
+
import { renderChildModelCriteria, SUBAGENT_MODELS_SETTINGS_HINT } from './subagent-spawn-policy.js';
|
|
11
13
|
import { extractUserPrompt, looksLikeCodexGitAction, looksLikeCodexUiSettingsEvent } from './payload-signals.js';
|
|
12
14
|
const STANDALONE_PARENT_BASE_SKILLS = [
|
|
13
15
|
'sks-naruto',
|
|
@@ -15,14 +17,25 @@ const STANDALONE_PARENT_BASE_SKILLS = [
|
|
|
15
17
|
'sks-prompt-pipeline',
|
|
16
18
|
'sks-honest-mode'
|
|
17
19
|
];
|
|
18
|
-
const
|
|
20
|
+
const OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD = [
|
|
19
21
|
'SKS official-subagent spawn contract:',
|
|
20
22
|
'- Full-history forks (`fork_turns="all"`, including the omitted/default full-history mode) inherit the parent agent type, model, and reasoning effort.',
|
|
21
|
-
'- When selecting a custom `agent_type` or overriding `model`/`reasoning_effort`, set `fork_turns="none"` or a positive bounded turn count and put the complete bounded slice contract in `message`.'
|
|
23
|
+
'- When selecting a custom `agent_type` or overriding `model`/`reasoning_effort`, set `fork_turns="none"` or a positive bounded turn count and put the complete bounded slice contract in `message`.'
|
|
24
|
+
];
|
|
25
|
+
const OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT = [
|
|
26
|
+
...OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD,
|
|
22
27
|
'- SKS children must pass the slice contract `model` (the newest model of the role tier), its `reasoning_effort`, and `fork_turns=\"none\"` or a positive bounded turn count. When Jev mode is on, the SKS PreToolUse hook seals Jev\'s tier on every spawn, so do not tune model or effort yourself. A stored user role-model preference wins.'
|
|
23
28
|
].join('\n');
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
function openRouterOnlyChildModelLine(allowlist) {
|
|
30
|
+
if (!allowlist.models.length) {
|
|
31
|
+
return `- SKS OpenRouter Only mode is on and the subagent model list is empty: every spawn_agent call is denied until the user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
|
|
32
|
+
}
|
|
33
|
+
return `- SKS OpenRouter Only mode is on: every child runs a model from the user's subagent list, and the SKS PreToolUse hook denies any other model. The list, with the user's rule for each model: ${renderChildModelCriteria(allowlist)}. The hook routes each spawn_agent call to a list model (Jev picks by these rules when Jev mode is on; otherwise a listed requested model, else the default), so pass \`fork_turns="none"\` or a positive bounded turn count and do not tune model or effort yourself.`;
|
|
34
|
+
}
|
|
35
|
+
export function officialSubagentSpawnCompatibilityContext(allowlist = effectiveChildModelAllowlist()) {
|
|
36
|
+
if (allowlist.mode !== 'openrouter_only')
|
|
37
|
+
return OFFICIAL_SUBAGENT_SPAWN_COMPATIBILITY_CONTEXT;
|
|
38
|
+
return [...OFFICIAL_SUBAGENT_SPAWN_CONTRACT_HEAD, openRouterOnlyChildModelLine(allowlist)].join('\n');
|
|
26
39
|
}
|
|
27
40
|
export function attachOfficialSubagentSpawnCompatibilityContext(state, payload, result) {
|
|
28
41
|
if (result?.decision === 'block' || result?.sksTaskProfile === 'passthrough')
|
|
@@ -35,11 +48,12 @@ export function attachOfficialSubagentSpawnCompatibilityContext(state, payload,
|
|
|
35
48
|
if (!officialSubagentsRequiredForPromptOrState(state, prompt, result))
|
|
36
49
|
return result;
|
|
37
50
|
const existing = String(result?.additionalContext || '');
|
|
38
|
-
|
|
51
|
+
const spawnContract = officialSubagentSpawnCompatibilityContext();
|
|
52
|
+
if (existing.includes(spawnContract))
|
|
39
53
|
return result;
|
|
40
54
|
return {
|
|
41
55
|
...result,
|
|
42
|
-
additionalContext: [
|
|
56
|
+
additionalContext: [spawnContract, existing].filter(Boolean).join('\n\n')
|
|
43
57
|
};
|
|
44
58
|
}
|
|
45
59
|
export async function attachAuthoritativeSksSkillContext(root, state, payload, result) {
|
|
@@ -207,7 +221,7 @@ export async function hookActiveSkillContextRefresh(root, state, name) {
|
|
|
207
221
|
};
|
|
208
222
|
}
|
|
209
223
|
const spawnCompatibility = officialSubagentsRequiredForPromptOrState(state, '', {})
|
|
210
|
-
?
|
|
224
|
+
? officialSubagentSpawnCompatibilityContext()
|
|
211
225
|
: '';
|
|
212
226
|
const refresh = await activeAuthoritativeSksSkillRefresh(root, state, { includeContext: true });
|
|
213
227
|
if (refresh.blocked) {
|
|
@@ -32,8 +32,9 @@ export function normalizeHookResult(name, result = {}) {
|
|
|
32
32
|
if (out.decision === 'block' || out.permissionDecision === 'deny' || out.decision === 'deny') {
|
|
33
33
|
return buildPreToolUseDeny(reason, { systemMessage });
|
|
34
34
|
}
|
|
35
|
-
if (out.updatedInput)
|
|
36
|
-
return buildPreToolUseAllowRewrite(out.updatedInput, { systemMessage });
|
|
35
|
+
if (out.updatedInput) {
|
|
36
|
+
return buildPreToolUseAllowRewrite(out.updatedInput, { additionalContext: out.additionalContext, systemMessage });
|
|
37
|
+
}
|
|
37
38
|
return buildPreToolUseContinue({ additionalContext: out.additionalContext, systemMessage });
|
|
38
39
|
}
|
|
39
40
|
if (eventName === 'PermissionRequest') {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { consultJevTurnModel } from '../decisions/integration.js';
|
|
2
|
+
import { chooseChildModel, fallbackChildModel } from '../decisions/child-model-choice.js';
|
|
2
3
|
import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
|
|
4
|
+
import { OPENROUTER_ONLY_SCHEMA, effectiveChildModelAllowlist, isSubagentModelEffort } from '../subagents/child-model-allowlist.js';
|
|
3
5
|
import { subagentModelProfile } from '../subagents/model-policy.js';
|
|
4
6
|
import { effortForTier, latestModelForTier, latestTierModelSet } from '../subagents/model-tiers.js';
|
|
5
7
|
import { readRoleModelPreferences } from '../subagents/role-model-preferences.js';
|
|
8
|
+
import { READ_ONLY_LIST_ROLE } from '../subagents/read-only-list-role.js';
|
|
9
|
+
import { boundedForkTurns, fullHistoryForkContext, renderChildModelList, SUBAGENT_MODELS_SETTINGS_HINT } from './subagent-spawn-policy.js';
|
|
6
10
|
const SPAWN_TOOLS = new Set(['spawn_agent', 'collaboration.spawn_agent', 'functions.spawn_agent']);
|
|
7
11
|
function spawnInput(payload) {
|
|
8
12
|
const name = String(payload?.tool_name || payload?.toolName || payload?.tool?.name || '');
|
|
@@ -18,8 +22,16 @@ function narutoParent(state) {
|
|
|
18
22
|
const route = String(state?.route || state?.route_command || '').replace(/^\$/, '').toUpperCase();
|
|
19
23
|
return mode === 'NARUTO' || route === 'NARUTO' || state?.subagents_required === true;
|
|
20
24
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
25
|
+
function itemTexts(items) {
|
|
26
|
+
if (!Array.isArray(items))
|
|
27
|
+
return [];
|
|
28
|
+
return items
|
|
29
|
+
.filter((item) => item && typeof item === 'object' && item.type === 'text')
|
|
30
|
+
.map((item) => item.text);
|
|
31
|
+
}
|
|
32
|
+
function spawnTask(input, opts = {}) {
|
|
33
|
+
const items = opts.items ? itemTexts(input.items) : [];
|
|
34
|
+
return [input.message, input.prompt, input.task, ...items, input.agent_type, input.agentType]
|
|
23
35
|
.map((value) => String(value || '').trim())
|
|
24
36
|
.filter(Boolean)
|
|
25
37
|
.join('\n')
|
|
@@ -33,11 +45,68 @@ export function roleTierFallback(agentType) {
|
|
|
33
45
|
}
|
|
34
46
|
return { model: latestModelForTier('deep'), effort: effortForTier('deep') };
|
|
35
47
|
}
|
|
36
|
-
export async function
|
|
37
|
-
if (!narutoParent(state))
|
|
38
|
-
return null;
|
|
48
|
+
export async function jevSpawnRouting(root, state, payload) {
|
|
39
49
|
const input = spawnInput(payload);
|
|
40
50
|
if (!input)
|
|
51
|
+
return { input: null, route: null };
|
|
52
|
+
const allowlist = effectiveChildModelAllowlist();
|
|
53
|
+
if (allowlist.mode === 'openrouter_only')
|
|
54
|
+
return openRouterOnlySpawnRouting(root, state, input, allowlist);
|
|
55
|
+
return { input: await tierSpawnRewrite(root, state, input), route: null };
|
|
56
|
+
}
|
|
57
|
+
export async function jevSpawnModelRewrite(root, state, payload) {
|
|
58
|
+
return (await jevSpawnRouting(root, state, payload)).input;
|
|
59
|
+
}
|
|
60
|
+
function listState(allowlist) {
|
|
61
|
+
return { schema: OPENROUTER_ONLY_SCHEMA, enabled: true, subagent_models: allowlist.entries, restore: null, updated_at: null };
|
|
62
|
+
}
|
|
63
|
+
async function openRouterOnlySpawnRouting(root, parentState, input, allowlist) {
|
|
64
|
+
if (fullHistoryForkContext(input))
|
|
65
|
+
return { input: null, route: null };
|
|
66
|
+
const forkTurns = input.fork_turns === undefined && narutoParent(parentState) ? 'none' : input.fork_turns;
|
|
67
|
+
if (!boundedForkTurns(forkTurns))
|
|
68
|
+
return { input: null, route: null };
|
|
69
|
+
const state = listState(allowlist);
|
|
70
|
+
const requested = String(input.model || '').trim() || null;
|
|
71
|
+
const agent = String(input.agent_type || input.agentType || '').trim() || null;
|
|
72
|
+
const task = spawnTask(input, { items: true });
|
|
73
|
+
const choice = task
|
|
74
|
+
? await chooseChildModel({ root, task, role: agent, requestedModel: requested, state })
|
|
75
|
+
.catch(() => fallbackChildModel(state, requested, 'consult_failed'))
|
|
76
|
+
: fallbackChildModel(state, requested, 'empty_task');
|
|
77
|
+
if (!choice)
|
|
78
|
+
return { input: null, route: null };
|
|
79
|
+
const route = {
|
|
80
|
+
mode: 'openrouter_only',
|
|
81
|
+
model: choice.entry.model,
|
|
82
|
+
source: choice.source,
|
|
83
|
+
reason: choice.reason
|
|
84
|
+
};
|
|
85
|
+
const next = { ...input, model: choice.entry.model };
|
|
86
|
+
const managed = agent ? managedOfficialSubagentRoleByName(agent) : null;
|
|
87
|
+
if (managed) {
|
|
88
|
+
delete next.agentType;
|
|
89
|
+
if (managed.sandbox === 'read-only')
|
|
90
|
+
next.agent_type = READ_ONLY_LIST_ROLE.codex_name;
|
|
91
|
+
else
|
|
92
|
+
delete next.agent_type;
|
|
93
|
+
}
|
|
94
|
+
const effort = choice.entry.reasoning_effort
|
|
95
|
+
|| (isSubagentModelEffort(input.reasoning_effort) ? input.reasoning_effort : null);
|
|
96
|
+
if (effort)
|
|
97
|
+
next.reasoning_effort = effort;
|
|
98
|
+
else
|
|
99
|
+
delete next.reasoning_effort;
|
|
100
|
+
next.fork_turns = forkTurns;
|
|
101
|
+
const unchanged = !managed
|
|
102
|
+
&& input.model === next.model
|
|
103
|
+
&& input.reasoning_effort === next.reasoning_effort
|
|
104
|
+
&& Object.hasOwn(input, 'reasoning_effort') === Object.hasOwn(next, 'reasoning_effort')
|
|
105
|
+
&& input.fork_turns === next.fork_turns;
|
|
106
|
+
return { input: unchanged ? null : next, route };
|
|
107
|
+
}
|
|
108
|
+
async function tierSpawnRewrite(root, state, input) {
|
|
109
|
+
if (!narutoParent(state))
|
|
41
110
|
return null;
|
|
42
111
|
const agent = String(input.agent_type || input.agentType || '').trim();
|
|
43
112
|
if (agent) {
|
|
@@ -67,3 +136,12 @@ export async function jevSpawnModelRewrite(root, state, payload) {
|
|
|
67
136
|
...forkTurns
|
|
68
137
|
};
|
|
69
138
|
}
|
|
139
|
+
export function openRouterOnlyJevTurnLine(allowlist, effort, orchestrationRequired) {
|
|
140
|
+
const hint = effort
|
|
141
|
+
? `Jev rated this ${orchestrationRequired ? 'task' : 'turn'} as ${effort}-effort work; the parent model, effort, and service tier stay as the user set them. `
|
|
142
|
+
: '';
|
|
143
|
+
const list = allowlist.entries.length
|
|
144
|
+
? `every spawn_agent child runs a model from the user's subagent list (${renderChildModelList(allowlist)}); Jev picks the list model for each spawn from the user's criteria, and any other model is denied.`
|
|
145
|
+
: `the subagent model list is empty, so spawn_agent is denied until the user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
|
|
146
|
+
return `${hint}SKS OpenRouter Only mode: ${list}`;
|
|
147
|
+
}
|
|
@@ -308,7 +308,8 @@ async function refreshOfficialSubagentCompletionArtifactsLocked(root, state, par
|
|
|
308
308
|
}
|
|
309
309
|
const previousGate = existingGate || {};
|
|
310
310
|
const parentModel = plan.observed_parent_model || state.observed_parent_model || null;
|
|
311
|
-
const
|
|
311
|
+
const expectedParentModel = typeof plan.parent_model_policy === 'string' && plan.parent_model_policy ? plan.parent_model_policy : narutoParentModel();
|
|
312
|
+
const parentModelMismatch = previousGate.parent_model_match === false || observedParentModelMismatch(parentModel, expectedParentModel);
|
|
312
313
|
const blockers = [...new Set([
|
|
313
314
|
...evidence.blockers,
|
|
314
315
|
...(Array.isArray(previousGate.config_blockers) ? previousGate.config_blockers.map(String) : []),
|
|
@@ -329,7 +330,7 @@ async function refreshOfficialSubagentCompletionArtifactsLocked(root, state, par
|
|
|
329
330
|
route: '$Naruto',
|
|
330
331
|
status: passed ? 'completed' : evidence.status,
|
|
331
332
|
parent: {
|
|
332
|
-
model:
|
|
333
|
+
model: expectedParentModel,
|
|
333
334
|
model_reasoning_effort: NARUTO_PARENT_EFFORT,
|
|
334
335
|
observed_model: parentModel,
|
|
335
336
|
observed_model_match: parentModel ? !parentModelMismatch : null
|
|
@@ -28,6 +28,14 @@ const SHELL_WRITE_RE = new RegExp([
|
|
|
28
28
|
const PATCH_FILE_RE = /\*\*\*\s+(?:Update|Add|Delete|Move to)\s+File:\s*([^\n\r]+)/g;
|
|
29
29
|
const PATCH_HEADER_RE = /\*\*\*\s+(?:Update|Add|Delete|Move to)\s+File:/;
|
|
30
30
|
const SHELL_PATH_TOKEN_RE = /(?:^|\s)((?:\.{1,2}\/|\/|~\/)?[\w@.+-]+(?:\/[\w@.+-]+)+|[\w@+-]+\.[a-z0-9]{1,8})(?=\s|$)/gi;
|
|
31
|
+
function spawnRouteOf(raw) {
|
|
32
|
+
if (!raw || typeof raw !== 'object')
|
|
33
|
+
return null;
|
|
34
|
+
const row = raw;
|
|
35
|
+
const text = (value) => String(value ?? '').trim().slice(0, 200);
|
|
36
|
+
const route = { mode: text(row.mode), model: text(row.model), source: text(row.source), reason: text(row.reason) };
|
|
37
|
+
return route.mode && route.model && route.source ? route : null;
|
|
38
|
+
}
|
|
31
39
|
export function isSpawnToolPayload(payload = {}) {
|
|
32
40
|
return SPAWN_TOOL_RE.test(toolNameOf(payload));
|
|
33
41
|
}
|
|
@@ -193,6 +201,7 @@ export async function readParentOrchestrationLedger(root, missionId, workflowRun
|
|
|
193
201
|
return fresh;
|
|
194
202
|
if (workflowRunId && raw.workflow_run_id && String(raw.workflow_run_id) !== workflowRunId)
|
|
195
203
|
return fresh;
|
|
204
|
+
const spawnRoute = spawnRouteOf(raw.last_spawn_route);
|
|
196
205
|
return {
|
|
197
206
|
...fresh,
|
|
198
207
|
workflow_run_id: workflowRunId || (typeof raw.workflow_run_id === 'string' ? raw.workflow_run_id : null),
|
|
@@ -207,6 +216,7 @@ export async function readParentOrchestrationLedger(root, missionId, workflowRun
|
|
|
207
216
|
last_tool: typeof raw.last_tool === 'string' ? raw.last_tool : null,
|
|
208
217
|
last_targets: Array.isArray(raw.last_targets) ? raw.last_targets.map((row) => String(row)).slice(0, MAX_TARGETS) : [],
|
|
209
218
|
last_jev_reason: typeof raw.last_jev_reason === 'string' ? raw.last_jev_reason : null,
|
|
219
|
+
...(spawnRoute ? { last_spawn_route: spawnRoute } : {}),
|
|
210
220
|
updated_at: typeof raw.updated_at === 'string' ? raw.updated_at : null
|
|
211
221
|
};
|
|
212
222
|
}
|
|
@@ -227,7 +237,14 @@ export async function recordParentOrchestrationSpawn(root, state = {}, opts = {}
|
|
|
227
237
|
const ledger = await readParentOrchestrationLedger(root, missionId, workflowRunId);
|
|
228
238
|
if (opts.atLeastOne && ledger.spawns > 0)
|
|
229
239
|
return ledger;
|
|
230
|
-
const
|
|
240
|
+
const spawnRoute = spawnRouteOf(opts.spawnRoute);
|
|
241
|
+
const { last_spawn_route: _previousRoute, ...rest } = ledger;
|
|
242
|
+
const next = {
|
|
243
|
+
...rest,
|
|
244
|
+
spawns: ledger.spawns + 1,
|
|
245
|
+
last_action: 'spawn',
|
|
246
|
+
...(spawnRoute ? { last_spawn_route: spawnRoute } : {})
|
|
247
|
+
};
|
|
231
248
|
await writeLedger(root, next);
|
|
232
249
|
return next;
|
|
233
250
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { readJson } from '../fsx.js';
|
|
3
3
|
import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
|
|
4
|
-
import { latestModelForTier
|
|
4
|
+
import { latestModelForTier } from '../subagents/model-tiers.js';
|
|
5
|
+
import { effectiveChildModelAllowlist } from '../subagents/child-model-allowlist.js';
|
|
5
6
|
import { jevEnabled, readDecisionConfig } from '../decisions/config.js';
|
|
7
|
+
import { renderChildModelList } from './subagent-spawn-policy.js';
|
|
6
8
|
export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
|
|
7
9
|
const plan = await readJson(path.join(artifactDir, 'subagent-plan.json'), null).catch(() => null);
|
|
8
10
|
if (!plan || plan.workflow !== 'official_codex_subagent')
|
|
@@ -13,6 +15,15 @@ export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
|
|
|
13
15
|
const role = agentName ? managedOfficialSubagentRoleByName(agentName) : null;
|
|
14
16
|
if (!agentName)
|
|
15
17
|
return '';
|
|
18
|
+
const allowlist = effectiveChildModelAllowlist();
|
|
19
|
+
if (allowlist.mode === 'openrouter_only') {
|
|
20
|
+
return [
|
|
21
|
+
'SKS sealed child routing:',
|
|
22
|
+
`- custom agent: ${agentName}`,
|
|
23
|
+
`- model and model_reasoning_effort: sealed on the spawn call from the OpenRouter Only subagent list (${renderChildModelList(allowlist) || 'empty'})`,
|
|
24
|
+
'- keep this sealed profile; do not retarget model/effort or spawn nested agents'
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
16
27
|
const narutoPlan = plan.mode === 'naruto' || plan.route === '$Naruto';
|
|
17
28
|
const jevSealed = await readDecisionConfig().then(jevEnabled).catch(() => false);
|
|
18
29
|
if (jevSealed) {
|
|
@@ -24,7 +35,7 @@ export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
|
|
|
24
35
|
].join('\n');
|
|
25
36
|
}
|
|
26
37
|
const plannedModel = String(planned?.routed_model || planned?.model || '').trim();
|
|
27
|
-
const sealedModels =
|
|
38
|
+
const sealedModels = new Set(allowlist.models);
|
|
28
39
|
const model = narutoPlan && sealedModels.has(plannedModel) ? plannedModel : (role?.model || latestModelForTier('deep'));
|
|
29
40
|
const effort = String((model === plannedModel
|
|
30
41
|
? planned?.routed_model_reasoning_effort || planned?.model_reasoning_effort
|
|
@@ -1,16 +1,59 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
|
|
2
|
+
import { READ_ONLY_LIST_ROLE } from '../subagents/read-only-list-role.js';
|
|
3
|
+
import { effectiveChildModelAllowlist, isAllowedChildModel } from '../subagents/child-model-allowlist.js';
|
|
2
4
|
const SPAWN_TOOLS = new Set(['spawn_agent', 'collaboration.spawn_agent', 'functions.spawn_agent']);
|
|
5
|
+
export const SUBAGENT_MODELS_SETTINGS_HINT = 'SKS Control Center > Subagent Models';
|
|
6
|
+
export function renderChildModelList(allowlist) {
|
|
7
|
+
if (allowlist.mode !== 'openrouter_only')
|
|
8
|
+
return allowlist.models.join(', ');
|
|
9
|
+
return allowlist.entries.map((entry) => `${entry.model}${entry.default ? ' (default)' : ''}`).join(', ');
|
|
10
|
+
}
|
|
11
|
+
export function renderChildModelCriteria(allowlist) {
|
|
12
|
+
if (allowlist.mode !== 'openrouter_only')
|
|
13
|
+
return allowlist.models.join(', ');
|
|
14
|
+
return allowlist.entries
|
|
15
|
+
.map((entry) => `${entry.model}${entry.default ? ' (default)' : ''}: ${entry.criteria || 'general work'}`)
|
|
16
|
+
.join('; ');
|
|
17
|
+
}
|
|
18
|
+
function openRouterOnlyBlockReason(allowlist) {
|
|
19
|
+
if (!allowlist.models.length && allowlist.mode === 'openrouter_only' && allowlist.unroutable?.length) {
|
|
20
|
+
return `SKS OpenRouter Only mode: no model on the subagent list has an OpenRouter route right now (${allowlist.unroutable.join(', ')}), so no child may be spawned. Ask the user to replace them in ${SUBAGENT_MODELS_SETTINGS_HINT}, then retry spawn_agent. Do not implement the slice in the parent instead.`;
|
|
21
|
+
}
|
|
22
|
+
if (!allowlist.models.length) {
|
|
23
|
+
return `SKS OpenRouter Only mode is on but the subagent model list is empty, so no child may be spawned. Ask the user to add models in ${SUBAGENT_MODELS_SETTINGS_HINT} (or to turn OpenRouter Only off), then retry spawn_agent. Do not implement the slice in the parent instead.`;
|
|
24
|
+
}
|
|
25
|
+
return `SKS OpenRouter Only mode: children may run only a model on the user's subagent list: ${renderChildModelList(allowlist)}. Every other model is denied. Retry spawn_agent with one of them (the SKS hook routes each spawn to a list model, by Jev when Jev mode is on) and fork_turns="none" or a positive bounded turn count. Include the complete slice contract in message; do not inherit the parent model. The user adds models in ${SUBAGENT_MODELS_SETTINGS_HINT}.`;
|
|
26
|
+
}
|
|
27
|
+
export function fullHistoryForkContext(input) {
|
|
28
|
+
return input.fork_context === true || String(input.fork_context ?? '').trim().toLowerCase() === 'true';
|
|
29
|
+
}
|
|
30
|
+
export function boundedForkTurns(value) {
|
|
31
|
+
return value === 'none' || /^[1-9]\d*$/.test(String(value || ''));
|
|
32
|
+
}
|
|
33
|
+
const FORK_BLOCK_REASON = 'SKS child spawns require fork_turns="none" or a positive bounded turn count. Retry with the complete slice contract in message; full-history/default forks cannot carry an explicit child model.';
|
|
3
34
|
export function subagentSpawnPolicyBlockReason(payload = {}) {
|
|
4
35
|
const name = String(payload.tool_name || payload.toolName || payload.tool?.name || '');
|
|
5
36
|
if (!SPAWN_TOOLS.has(name))
|
|
6
37
|
return null;
|
|
7
38
|
const input = payload.tool_input || payload.toolInput || payload.tool?.input || {};
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
10
|
-
|
|
39
|
+
const allowlist = effectiveChildModelAllowlist();
|
|
40
|
+
if (allowlist.mode === 'openrouter_only') {
|
|
41
|
+
if (!isAllowedChildModel(input.model, allowlist))
|
|
42
|
+
return openRouterOnlyBlockReason(allowlist);
|
|
43
|
+
if (fullHistoryForkContext(input))
|
|
44
|
+
return `${FORK_BLOCK_REASON} Omit fork_context (fork_context=true is a full-history fork).`;
|
|
45
|
+
const agent = String(input.agent_type || input.agentType || '').trim();
|
|
46
|
+
if (agent && managedOfficialSubagentRoleByName(agent)) {
|
|
47
|
+
return `SKS OpenRouter Only mode: agent_type "${agent}" pins a tier model, so it cannot run a list model. Omit agent_type and put the role brief in message; read-only slices pass agent_type="${READ_ONLY_LIST_ROLE.codex_name}".`;
|
|
48
|
+
}
|
|
11
49
|
}
|
|
12
|
-
|
|
13
|
-
|
|
50
|
+
else {
|
|
51
|
+
const allowed = new Set(allowlist.models);
|
|
52
|
+
if (!allowed.has(String(input.model || ''))) {
|
|
53
|
+
return `SKS children must name a current model: ${[...allowed].join(', ')} (the latest fast, balanced, context, or deep tier). Retry spawn_agent with the slice contract model, which the SKS hook re-seals when Jev mode is on, and fork_turns="none" or a positive bounded turn count. Include the complete slice contract in message; do not inherit the parent model.`;
|
|
54
|
+
}
|
|
14
55
|
}
|
|
56
|
+
if (!boundedForkTurns(input.fork_turns))
|
|
57
|
+
return FORK_BLOCK_REASON;
|
|
15
58
|
return null;
|
|
16
59
|
}
|
|
@@ -31,7 +31,8 @@ import { activeNarutoParentLaunchMissionId, claimStandaloneParentHostCapabilityR
|
|
|
31
31
|
import { classifyTaskProfile } from './runtime/task-profile.js';
|
|
32
32
|
import { resolveSubagentThreadBudget } from './subagents/thread-budget.js';
|
|
33
33
|
import { readOfficialSubagentConfig } from './subagents/official-subagent-config.js';
|
|
34
|
-
import {
|
|
34
|
+
import { jevSpawnRouting, openRouterOnlyJevTurnLine } from './hooks-runtime/jev-spawn-routing.js';
|
|
35
|
+
import { effectiveChildModelAllowlist } from './subagents/child-model-allowlist.js';
|
|
35
36
|
import { maybeReconcileManagedGuidancePreflight } from './hooks-runtime/managed-guidance-preflight.js';
|
|
36
37
|
import { evaluateParentOrchestrationGate, isSpawnToolPayload, recordParentOrchestrationSpawn } from './hooks-runtime/parent-orchestration-gate.js';
|
|
37
38
|
import { subagentSpawnPolicyBlockReason } from './hooks-runtime/subagent-spawn-policy.js';
|
|
@@ -295,11 +296,15 @@ async function attachJevTurnRouting(root, payload, result, orchestrationRequired
|
|
|
295
296
|
if (plan?.routeOverride) {
|
|
296
297
|
result = attachContextLine(result, `Jev routed this prompt to the ${plan.routeOverride.routeId} pipeline (the keyword router guessed ${plan.baselineRouteId || 'none'}).`);
|
|
297
298
|
}
|
|
298
|
-
const
|
|
299
|
+
const tierLine = decision.model
|
|
299
300
|
? orchestrationRequired
|
|
300
301
|
? `Jev rated this task as ${decision.model} (${decision.effort}). Use that as the default child seal; Jev re-seals every spawn_agent call and the parent model stays as the user set it.`
|
|
301
302
|
: `Jev rated this turn as ${decision.effort}-effort work (${decision.model} tier). Treat it as this turn's reasoning hint; the parent model, effort, and service tier stay as the user set them.`
|
|
302
303
|
: '';
|
|
304
|
+
const allowlist = effectiveChildModelAllowlist();
|
|
305
|
+
const line = allowlist.mode === 'openrouter_only'
|
|
306
|
+
? openRouterOnlyJevTurnLine(allowlist, decision.effort, orchestrationRequired)
|
|
307
|
+
: tierLine;
|
|
303
308
|
if (!line)
|
|
304
309
|
return { ...result, jev_turn: decision };
|
|
305
310
|
const additionalContext = [result.additionalContext, line].filter(Boolean).join('\n\n');
|
|
@@ -649,7 +654,8 @@ async function consumeActiveOfficialWorkflowQueue(root, missionId, workflowRunId
|
|
|
649
654
|
}
|
|
650
655
|
}
|
|
651
656
|
async function hookPreTool(root, state, payload, noQuestion, sessionKey = null) {
|
|
652
|
-
const
|
|
657
|
+
const jevSpawn = await jevSpawnRouting(root, state, payload).catch(() => null);
|
|
658
|
+
const jevSpawnInput = jevSpawn?.input ?? null;
|
|
653
659
|
const spawnPayload = jevSpawnInput
|
|
654
660
|
? { ...payload, tool_input: jevSpawnInput, toolInput: jevSpawnInput }
|
|
655
661
|
: payload;
|
|
@@ -724,8 +730,9 @@ async function hookPreTool(root, state, payload, noQuestion, sessionKey = null)
|
|
|
724
730
|
const hostCapabilityDecision = await enforceHostCapabilityPreTool(root, state, payload, sessionKey);
|
|
725
731
|
if (hostCapabilityDecision && hostCapabilityDecision.continue !== true)
|
|
726
732
|
return hostCapabilityDecision;
|
|
727
|
-
if (isSpawnToolPayload(spawnPayload))
|
|
728
|
-
await recordParentOrchestrationSpawn(root, state).catch(() => null);
|
|
733
|
+
if (isSpawnToolPayload(spawnPayload)) {
|
|
734
|
+
await recordParentOrchestrationSpawn(root, state, jevSpawn?.route ? { spawnRoute: jevSpawn.route } : {}).catch(() => null);
|
|
735
|
+
}
|
|
729
736
|
const waveGuidance = await parentWaveGuidanceContext(root, state, sessionKey).catch(() => '');
|
|
730
737
|
const escapeNote = orchestration?.action === 'escape' ? orchestration.message || '' : '';
|
|
731
738
|
const additionalContext = [skillRefresh.context, waveGuidance, escapeNote].filter(Boolean).join('\n\n');
|
|
@@ -551,21 +551,26 @@ export function managedOfficialSubagentRoleBody(role) {
|
|
|
551
551
|
].join('\n');
|
|
552
552
|
}
|
|
553
553
|
export function managedOfficialSubagentRoleContent(role) {
|
|
554
|
-
|
|
554
|
+
return managedOfficialSubagentFileContent(role.id, role.schema_version, managedOfficialSubagentRoleBody(role));
|
|
555
|
+
}
|
|
556
|
+
export function managedOfficialSubagentFileContent(id, schemaVersion, body) {
|
|
555
557
|
return [
|
|
556
558
|
`# ${MANAGED_OFFICIAL_SUBAGENT_MARKER}`,
|
|
557
|
-
`# sks_managed_schema = ${
|
|
558
|
-
`# sks_managed_id = "${
|
|
559
|
+
`# sks_managed_schema = ${schemaVersion}`,
|
|
560
|
+
`# sks_managed_id = "${id}"`,
|
|
559
561
|
`# sks_managed_body_sha256 = "${sha256(body)}"`,
|
|
560
562
|
'',
|
|
561
563
|
body
|
|
562
564
|
].join('\n');
|
|
563
565
|
}
|
|
564
566
|
export function managedOfficialSubagentRoleOwnsText(text, role) {
|
|
567
|
+
return managedOfficialSubagentFileOwnsText(text, role.id);
|
|
568
|
+
}
|
|
569
|
+
export function managedOfficialSubagentFileOwnsText(text, id) {
|
|
565
570
|
const source = String(text || '');
|
|
566
571
|
if (!source.includes(`# ${MANAGED_OFFICIAL_SUBAGENT_MARKER}`))
|
|
567
572
|
return false;
|
|
568
|
-
if (!source.includes(`sks_managed_id = "${
|
|
573
|
+
if (!source.includes(`sks_managed_id = "${id}"`))
|
|
569
574
|
return false;
|
|
570
575
|
const lines = source.split('\n');
|
|
571
576
|
const hashIndex = lines.findIndex((line) => /^#\s*sks_managed_body_sha256\s*=/.test(line.trim()));
|