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,179 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { isOpenRouterModelId } from '../imagegen/imagegen-config.js';
|
|
6
|
+
import { latestTierModelSet } from './model-tiers.js';
|
|
7
|
+
export const OPENROUTER_ONLY_SCHEMA = 'sks.openrouter-only.v1';
|
|
8
|
+
export const OPENROUTER_ONLY_FILENAME = 'sks-openrouter-only.json';
|
|
9
|
+
export const MAX_SUBAGENT_MODELS = 16;
|
|
10
|
+
export const MAX_SUBAGENT_CRITERIA_CHARS = 240;
|
|
11
|
+
export const SUBAGENT_MODEL_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
|
|
12
|
+
export function openRouterOnlyStatePath(input = {}) {
|
|
13
|
+
const env = input.env || process.env;
|
|
14
|
+
return path.join(input.home || env.HOME || os.homedir(), '.codex', 'sks', OPENROUTER_ONLY_FILENAME);
|
|
15
|
+
}
|
|
16
|
+
export function defaultOpenRouterOnlyState() {
|
|
17
|
+
return { schema: OPENROUTER_ONLY_SCHEMA, enabled: false, subagent_models: [], restore: null, updated_at: null };
|
|
18
|
+
}
|
|
19
|
+
export function canonicalChildModelId(value) {
|
|
20
|
+
return String(value ?? '').trim().toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
export function isSubagentModelEffort(value) {
|
|
23
|
+
return typeof value === 'string' && SUBAGENT_MODEL_EFFORTS.includes(value);
|
|
24
|
+
}
|
|
25
|
+
function cleanCriteria(value) {
|
|
26
|
+
return String(value ?? '')
|
|
27
|
+
.replace(/[\u0000-\u001f\u007f]+/g, ' ')
|
|
28
|
+
.replace(/\s+/g, ' ')
|
|
29
|
+
.trim()
|
|
30
|
+
.slice(0, MAX_SUBAGENT_CRITERIA_CHARS);
|
|
31
|
+
}
|
|
32
|
+
export function normalizeSubagentModelList(raw) {
|
|
33
|
+
const rows = Array.isArray(raw) ? raw : [];
|
|
34
|
+
const issues = [];
|
|
35
|
+
const entries = [];
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
rows.forEach((row, index) => {
|
|
38
|
+
const record = row && typeof row === 'object' ? row : {};
|
|
39
|
+
const model = String(record.model ?? '').trim();
|
|
40
|
+
if (!isOpenRouterModelId(model)) {
|
|
41
|
+
issues.push({ index, code: 'subagent_model_id_invalid' });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const key = canonicalChildModelId(model);
|
|
45
|
+
if (seen.has(key)) {
|
|
46
|
+
issues.push({ index, code: 'subagent_model_duplicate' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const effort = record.reasoning_effort ?? null;
|
|
50
|
+
if (effort !== null && effort !== '' && !isSubagentModelEffort(effort)) {
|
|
51
|
+
issues.push({ index, code: 'subagent_model_effort_invalid' });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (entries.length >= MAX_SUBAGENT_MODELS) {
|
|
55
|
+
issues.push({ index, code: 'subagent_model_list_too_long' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
seen.add(key);
|
|
59
|
+
entries.push({
|
|
60
|
+
model: key,
|
|
61
|
+
criteria: cleanCriteria(record.criteria),
|
|
62
|
+
reasoning_effort: isSubagentModelEffort(effort) ? effort : null,
|
|
63
|
+
default: record.default === true
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
const firstDefault = entries.findIndex((entry) => entry.default);
|
|
67
|
+
entries.forEach((entry, index) => { entry.default = index === (firstDefault === -1 ? 0 : firstDefault); });
|
|
68
|
+
return { entries, issues };
|
|
69
|
+
}
|
|
70
|
+
function normalizeRestore(raw) {
|
|
71
|
+
if (!raw || typeof raw !== 'object')
|
|
72
|
+
return null;
|
|
73
|
+
const record = raw;
|
|
74
|
+
const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
75
|
+
return { previous_model: text(record.previous_model), applied_model: text(record.applied_model) };
|
|
76
|
+
}
|
|
77
|
+
export function normalizeOpenRouterOnlyState(raw) {
|
|
78
|
+
if (!raw || typeof raw !== 'object')
|
|
79
|
+
return defaultOpenRouterOnlyState();
|
|
80
|
+
const record = raw;
|
|
81
|
+
if (record.schema !== OPENROUTER_ONLY_SCHEMA)
|
|
82
|
+
return defaultOpenRouterOnlyState();
|
|
83
|
+
return {
|
|
84
|
+
schema: OPENROUTER_ONLY_SCHEMA,
|
|
85
|
+
enabled: record.enabled === true,
|
|
86
|
+
subagent_models: normalizeSubagentModelList(record.subagent_models).entries,
|
|
87
|
+
restore: normalizeRestore(record.restore),
|
|
88
|
+
updated_at: typeof record.updated_at === 'string' ? record.updated_at : null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function readOpenRouterOnlyStateSync(input = {}) {
|
|
92
|
+
try {
|
|
93
|
+
return normalizeOpenRouterOnlyState(JSON.parse(fs.readFileSync(openRouterOnlyStatePath(input), 'utf8')));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return defaultOpenRouterOnlyState();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function readOpenRouterOnlyState(input = {}) {
|
|
100
|
+
return readOpenRouterOnlyStateSync(input);
|
|
101
|
+
}
|
|
102
|
+
export async function writeOpenRouterOnlyState(update, input = {}) {
|
|
103
|
+
const current = readOpenRouterOnlyStateSync(input);
|
|
104
|
+
const next = normalizeOpenRouterOnlyState({
|
|
105
|
+
...current,
|
|
106
|
+
...update,
|
|
107
|
+
schema: OPENROUTER_ONLY_SCHEMA,
|
|
108
|
+
updated_at: nowIso()
|
|
109
|
+
});
|
|
110
|
+
await writeJsonAtomic(openRouterOnlyStatePath(input), next, { mode: 0o600 });
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
113
|
+
export function defaultSubagentEntry(state) {
|
|
114
|
+
return state.subagent_models.find((entry) => entry.default) || state.subagent_models[0] || null;
|
|
115
|
+
}
|
|
116
|
+
export function subagentEntryForModel(state, model) {
|
|
117
|
+
const key = canonicalChildModelId(model);
|
|
118
|
+
if (!key)
|
|
119
|
+
return null;
|
|
120
|
+
return state.subagent_models.find((entry) => canonicalChildModelId(entry.model) === key) || null;
|
|
121
|
+
}
|
|
122
|
+
function openRouterRoutedModels(input) {
|
|
123
|
+
const env = input.env || process.env;
|
|
124
|
+
const file = path.join(input.home || env.HOME || os.homedir(), '.codex', 'sks', 'sks-bridge-route-policy.json');
|
|
125
|
+
try {
|
|
126
|
+
const routes = JSON.parse(fs.readFileSync(file, 'utf8'))?.model_routes;
|
|
127
|
+
if (!routes || typeof routes !== 'object')
|
|
128
|
+
return null;
|
|
129
|
+
const routed = new Set();
|
|
130
|
+
for (const [key, route] of Object.entries(routes)) {
|
|
131
|
+
if (route?.provider_id === 'openrouter')
|
|
132
|
+
routed.add(canonicalChildModelId(key.replace(/^openrouter:/i, '')));
|
|
133
|
+
}
|
|
134
|
+
return routed.size ? routed : null;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function routableSubagentState(state, input = {}) {
|
|
141
|
+
const routed = openRouterRoutedModels(input);
|
|
142
|
+
if (!routed)
|
|
143
|
+
return { state, unroutable: [] };
|
|
144
|
+
const keep = state.subagent_models.filter((entry) => routed.has(canonicalChildModelId(entry.model)));
|
|
145
|
+
const unroutable = state.subagent_models.filter((entry) => !keep.includes(entry)).map((entry) => entry.model);
|
|
146
|
+
if (!unroutable.length)
|
|
147
|
+
return { state, unroutable };
|
|
148
|
+
const preferred = keep.findIndex((entry) => entry.default);
|
|
149
|
+
const entries = keep.map((entry, index) => ({ ...entry, default: index === (preferred === -1 ? 0 : preferred) }));
|
|
150
|
+
return { state: { ...state, subagent_models: entries }, unroutable };
|
|
151
|
+
}
|
|
152
|
+
export function effectiveChildModelAllowlist(input = {}) {
|
|
153
|
+
const stored = readOpenRouterOnlyStateSync(input);
|
|
154
|
+
if (stored.enabled) {
|
|
155
|
+
const { state, unroutable } = routableSubagentState(stored, input);
|
|
156
|
+
return {
|
|
157
|
+
mode: 'openrouter_only',
|
|
158
|
+
models: state.subagent_models.map((entry) => entry.model),
|
|
159
|
+
entries: state.subagent_models,
|
|
160
|
+
default_model: defaultSubagentEntry(state)?.model ?? null,
|
|
161
|
+
unroutable
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { mode: 'tiers', models: [...latestTierModelSet(input)], entries: [], default_model: null };
|
|
165
|
+
}
|
|
166
|
+
export function isAllowedChildModel(model, allowlist) {
|
|
167
|
+
const key = canonicalChildModelId(model);
|
|
168
|
+
if (!key)
|
|
169
|
+
return false;
|
|
170
|
+
if (allowlist.mode === 'tiers')
|
|
171
|
+
return allowlist.models.includes(String(model ?? '').trim());
|
|
172
|
+
return allowlist.models.some((candidate) => canonicalChildModelId(candidate) === key);
|
|
173
|
+
}
|
|
174
|
+
export function allowlistedChildModel(model, allowlist) {
|
|
175
|
+
const key = canonicalChildModelId(model);
|
|
176
|
+
if (!key)
|
|
177
|
+
return null;
|
|
178
|
+
return allowlist.models.find((candidate) => canonicalChildModelId(candidate) === key) ?? null;
|
|
179
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { chooseChildModels } from '../decisions/child-model-choice.js';
|
|
2
|
+
import { MAX_JEV_ROUTING_ROLES } from '../decisions/routing.js';
|
|
3
|
+
import { SUBAGENT_MODEL_EFFORTS, defaultSubagentEntry, readOpenRouterOnlyStateSync, routableSubagentState } from './child-model-allowlist.js';
|
|
4
|
+
import { latestTierModelSet } from './model-tiers.js';
|
|
5
|
+
import { readCodexMainModel } from './naruto-host-credentials.js';
|
|
6
|
+
import { READ_ONLY_LIST_ROLE } from './read-only-list-role.js';
|
|
7
|
+
export function readChildModelPlanContext(location = {}) {
|
|
8
|
+
const stored = readOpenRouterOnlyStateSync(location);
|
|
9
|
+
if (!stored.enabled) {
|
|
10
|
+
return { allowlist: { mode: 'tiers', models: [...latestTierModelSet(location)], entries: [], default_model: null }, list: null };
|
|
11
|
+
}
|
|
12
|
+
const { state, unroutable } = routableSubagentState(stored, location);
|
|
13
|
+
const allowlist = {
|
|
14
|
+
mode: 'openrouter_only',
|
|
15
|
+
models: state.subagent_models.map((entry) => entry.model),
|
|
16
|
+
entries: state.subagent_models,
|
|
17
|
+
default_model: defaultSubagentEntry(state)?.model ?? null,
|
|
18
|
+
unroutable
|
|
19
|
+
};
|
|
20
|
+
return { allowlist, list: { state, allowlist, mainModel: readCodexMainModel(location) } };
|
|
21
|
+
}
|
|
22
|
+
export function listEntryEffort(entry, roleEffort) {
|
|
23
|
+
if (entry.reasoning_effort)
|
|
24
|
+
return entry.reasoning_effort;
|
|
25
|
+
const effort = String(roleEffort || '');
|
|
26
|
+
return SUBAGENT_MODEL_EFFORTS.includes(effort) ? effort : null;
|
|
27
|
+
}
|
|
28
|
+
function routedRow(row, choice) {
|
|
29
|
+
return {
|
|
30
|
+
...row,
|
|
31
|
+
routed_provider: 'openrouter',
|
|
32
|
+
routed_model: choice?.entry.model ?? null,
|
|
33
|
+
routed_model_reasoning_effort: choice ? listEntryEffort(choice.entry, row.model_reasoning_effort) : null,
|
|
34
|
+
routed_model_policy: choice?.source === 'jev' ? 'openrouter_only_jev' : 'openrouter_only_default',
|
|
35
|
+
routing_dynamic: true,
|
|
36
|
+
role_model_preference_source: 'ignored_openrouter_only',
|
|
37
|
+
openrouter_only_choice: choice
|
|
38
|
+
? { model: choice.entry.model, source: choice.source, reason: choice.reason, default_entry: choice.entry.default }
|
|
39
|
+
: { model: null, source: 'default', reason: 'subagent_list_empty', default_entry: false }
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function listBaselineAgentRouting(agentRouting, list) {
|
|
43
|
+
const fallback = defaultSubagentEntry(list.state);
|
|
44
|
+
const choice = fallback ? { entry: fallback, source: 'default', reason: 'baseline' } : null;
|
|
45
|
+
return Object.fromEntries(Object.entries(agentRouting).map(([name, row]) => [name, routedRow(row, choice)]));
|
|
46
|
+
}
|
|
47
|
+
export function childModelLanes(input) {
|
|
48
|
+
const assigned = new Set(input.slices.map((slice) => String(slice.agent || '')).filter(Boolean));
|
|
49
|
+
const names = Object.keys(input.agents);
|
|
50
|
+
const ordered = [...names.filter((name) => assigned.has(name)), ...names.filter((name) => !assigned.has(name))];
|
|
51
|
+
return ordered.slice(0, MAX_JEV_ROUTING_ROLES).map((name) => {
|
|
52
|
+
const own = input.slices.filter((slice) => slice.agent === name);
|
|
53
|
+
const work = own.length
|
|
54
|
+
? own.map((slice) => `${slice.title}: ${slice.description}${slice.readOnly ? ' (read-only)' : ''}`).join('\n')
|
|
55
|
+
: `Role ${name}: ${String(input.agents[name]?.description || name)}`;
|
|
56
|
+
return { id: name, role: name, task: work, requestedModel: null };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function chooseListRoleModels(input) {
|
|
60
|
+
const lanes = childModelLanes(input);
|
|
61
|
+
try {
|
|
62
|
+
const choices = await chooseChildModels({
|
|
63
|
+
root: input.root,
|
|
64
|
+
workflowId: 'plan-child-models',
|
|
65
|
+
goal: input.goal || 'Plan official subagent roles.',
|
|
66
|
+
lanes,
|
|
67
|
+
state: input.list.state,
|
|
68
|
+
...(input.env ? { env: input.env } : {})
|
|
69
|
+
});
|
|
70
|
+
return { lanes: lanes.map((lane) => lane.id), choices };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
const fallback = defaultSubagentEntry(input.list.state);
|
|
74
|
+
const choices = {};
|
|
75
|
+
if (fallback)
|
|
76
|
+
for (const lane of lanes)
|
|
77
|
+
choices[lane.id] = { entry: fallback, source: 'default', reason: 'transport_error' };
|
|
78
|
+
return { lanes: lanes.map((lane) => lane.id), choices };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function uncappedReason(lanes, choices) {
|
|
82
|
+
const reasons = new Set(lanes.map((lane) => choices[lane]?.reason).filter((reason) => Boolean(reason)));
|
|
83
|
+
const decided = lanes.some((lane) => choices[lane]?.source === 'jev' || choices[lane]?.reason === 'keep_baseline');
|
|
84
|
+
return !decided && reasons.size === 1 ? [...reasons][0] : 'role_cap';
|
|
85
|
+
}
|
|
86
|
+
export function applyListRoleModels(agents, chosen, list, readOnlyRoleInstalled = null) {
|
|
87
|
+
const fallback = defaultSubagentEntry(list.state);
|
|
88
|
+
const lanes = chosen?.lanes || [];
|
|
89
|
+
const routed = new Set(lanes);
|
|
90
|
+
const capped = chosen ? uncappedReason(lanes, chosen.choices) : 'role_cap';
|
|
91
|
+
const next = {};
|
|
92
|
+
const roles = {};
|
|
93
|
+
const jevModels = {};
|
|
94
|
+
for (const [name, row] of Object.entries(agents)) {
|
|
95
|
+
const choice = chosen?.choices[name]
|
|
96
|
+
?? (fallback ? { entry: fallback, source: 'default', reason: routed.has(name) ? 'keep_baseline' : capped } : null);
|
|
97
|
+
next[name] = routedRow(row, choice);
|
|
98
|
+
roles[name] = next[name].openrouter_only_choice;
|
|
99
|
+
if (choice?.source === 'jev')
|
|
100
|
+
jevModels[name] = choice.entry.model;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
agents: next,
|
|
104
|
+
jevModels,
|
|
105
|
+
evidence: {
|
|
106
|
+
enabled: true,
|
|
107
|
+
main_model: list.mainModel,
|
|
108
|
+
default_subagent_model: fallback?.model ?? null,
|
|
109
|
+
subagent_models: list.state.subagent_models.map((entry) => ({ ...entry })),
|
|
110
|
+
routed_roles: [...lanes],
|
|
111
|
+
jev_decided_roles: Object.keys(jevModels),
|
|
112
|
+
roles,
|
|
113
|
+
read_only_role: { name: READ_ONLY_LIST_ROLE.codex_name, installed: readOnlyRoleInstalled },
|
|
114
|
+
warnings: readOnlyRoleInstalled === false ? ['openrouter_only_read_only_role_missing'] : []
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { codexUserConfigPath, readTopLevelTomlString } from '../codex-app/codex-model-catalog.js';
|
|
3
|
+
import { isOpenRouterModelId } from '../imagegen/imagegen-config.js';
|
|
1
4
|
import { defaultSubagentModel } from './model-policy.js';
|
|
2
5
|
import { codexListedEfforts, latestTierModelSet } from './model-tiers.js';
|
|
6
|
+
import { SUBAGENT_MODEL_EFFORTS, defaultSubagentEntry, readOpenRouterOnlyStateSync, subagentEntryForModel } from './child-model-allowlist.js';
|
|
3
7
|
export const NARUTO_AUTH_MODES = ['managed', 'host'];
|
|
4
8
|
const IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
5
9
|
const RETIRED_DIRECT_MANAGED_PROVIDERS = new Set(['codex-lb', 'openrouter']);
|
|
6
10
|
export const NARUTO_EFFORT_TIERS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
11
|
+
export function readCodexMainModel(location = {}) {
|
|
12
|
+
try {
|
|
13
|
+
return readTopLevelTomlString(fs.readFileSync(codexUserConfigPath(location), 'utf8'), 'model')?.trim() || null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function readNarutoOpenRouterOnlyContext(location = {}) {
|
|
20
|
+
const state = readOpenRouterOnlyStateSync(location);
|
|
21
|
+
if (!state.enabled)
|
|
22
|
+
return null;
|
|
23
|
+
return { state, mainModel: readCodexMainModel(location) };
|
|
24
|
+
}
|
|
7
25
|
function readFlagValue(args, name) {
|
|
8
26
|
const inline = args.find((arg) => arg.startsWith(`${name}=`));
|
|
9
27
|
if (inline)
|
|
@@ -78,12 +96,15 @@ export function resolveNarutoCredentialPolicy(input) {
|
|
|
78
96
|
sources.forcedLoginMethod = args.includes('--no-forced-login-method') ? 'flag' : 'env';
|
|
79
97
|
warnings.push('naruto_forced_login_method_released_without_host_auth_mode');
|
|
80
98
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
const openRouterOnly = input.openRouterOnly === undefined ? readNarutoOpenRouterOnlyContext() : input.openRouterOnly;
|
|
100
|
+
const models = openRouterOnly
|
|
101
|
+
? openRouterOnlyModelFlags(input, openRouterOnly)
|
|
102
|
+
: [
|
|
103
|
+
{ key: 'parentModel', flag: '--parent-model', envKey: 'SKS_NARUTO_PARENT_MODEL', fallback: input.defaultParentModel, effort: false },
|
|
104
|
+
{ key: 'parentEffort', flag: '--parent-effort', envKey: 'SKS_NARUTO_PARENT_EFFORT', fallback: input.defaultParentEffort, effort: true },
|
|
105
|
+
{ key: 'subagentModel', flag: '--subagent-model', envKey: 'SKS_NARUTO_SUBAGENT_MODEL', fallback: defaultSubagentModel(), effort: false },
|
|
106
|
+
{ key: 'subagentEffort', flag: '--subagent-effort', envKey: 'SKS_NARUTO_SUBAGENT_EFFORT', fallback: input.defaultSubagentEffort, effort: true }
|
|
107
|
+
];
|
|
87
108
|
const resolvedModels = {};
|
|
88
109
|
for (const entry of models) {
|
|
89
110
|
const raw = resolve(args, env, entry.flag, entry.envKey);
|
|
@@ -94,7 +115,7 @@ export function resolveNarutoCredentialPolicy(input) {
|
|
|
94
115
|
}
|
|
95
116
|
const ok = entry.effort
|
|
96
117
|
? NARUTO_EFFORT_TIERS.includes(raw.value)
|
|
97
|
-
: validIdentifier(raw.value);
|
|
118
|
+
: validIdentifier(raw.value) || (openRouterOnly !== null && isOpenRouterModelId(raw.value));
|
|
98
119
|
if (!ok) {
|
|
99
120
|
blockers.push(`naruto_${String(entry.key)}_invalid:${raw.value.slice(0, 32)}`);
|
|
100
121
|
resolvedModels[entry.key] = entry.fallback;
|
|
@@ -104,12 +125,17 @@ export function resolveNarutoCredentialPolicy(input) {
|
|
|
104
125
|
resolvedModels[entry.key] = raw.value;
|
|
105
126
|
sources[entry.key] = raw.source;
|
|
106
127
|
}
|
|
107
|
-
if (
|
|
108
|
-
blockers
|
|
109
|
-
|
|
128
|
+
if (openRouterOnly) {
|
|
129
|
+
validateOpenRouterOnlyModels(openRouterOnly, resolvedModels, sources, blockers);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
if (!latestTierModelSet().has(String(resolvedModels.subagentModel))) {
|
|
133
|
+
blockers.push('naruto_subagent_model_must_be_current');
|
|
134
|
+
resolvedModels.subagentModel = defaultSubagentModel();
|
|
135
|
+
}
|
|
136
|
+
validateGpt56EffortPair('parent', String(resolvedModels.parentModel), String(resolvedModels.parentEffort), blockers);
|
|
137
|
+
validateGpt56EffortPair('subagent', String(resolvedModels.subagentModel), String(resolvedModels.subagentEffort), blockers);
|
|
110
138
|
}
|
|
111
|
-
validateGpt56EffortPair('parent', String(resolvedModels.parentModel), String(resolvedModels.parentEffort), blockers);
|
|
112
|
-
validateGpt56EffortPair('subagent', String(resolvedModels.subagentModel), String(resolvedModels.subagentEffort), blockers);
|
|
113
139
|
const envKeyRaw = resolve(args, env, '--provider-env-key', 'SKS_NARUTO_PROVIDER_ENV_KEY');
|
|
114
140
|
let providerEnvKey = null;
|
|
115
141
|
sources.providerEnvKey = 'default';
|
|
@@ -141,11 +167,53 @@ export function resolveNarutoCredentialPolicy(input) {
|
|
|
141
167
|
parentEffort: String(resolvedModels.parentEffort),
|
|
142
168
|
subagentModel: String(resolvedModels.subagentModel),
|
|
143
169
|
subagentEffort: String(resolvedModels.subagentEffort),
|
|
170
|
+
childModelMode: openRouterOnly ? 'openrouter_only' : 'tiers',
|
|
144
171
|
sources,
|
|
145
172
|
warnings,
|
|
146
|
-
blockers
|
|
173
|
+
blockers,
|
|
174
|
+
hint: openRouterOnly && blockers.some((blocker) => OPENROUTER_ONLY_BLOCKER.test(blocker)) ? OPENROUTER_ONLY_HINT : null
|
|
147
175
|
};
|
|
148
176
|
}
|
|
177
|
+
const OPENROUTER_ONLY_BLOCKER = /^naruto_(parent_model_not_openrouter|subagent_model_not_in_list|subagent_model_list_empty|parent_effort_unsupported_openrouter|subagent_effort_unsupported_openrouter)(:|$)/;
|
|
178
|
+
const OPENROUTER_ONLY_HINT = 'OpenRouter Only Mode is on: the parent runs the OpenRouter main model in Codex config.toml and children run only models on the subagent list (Control Center > Subagent Models, or `sks bridge subagent-models list`); pass --parent-model=<vendor/model>, --subagent-model=<a listed model>, and efforts low|medium|high|xhigh';
|
|
179
|
+
const OPENROUTER_ONLY_PARENT_EFFORT = 'xhigh';
|
|
180
|
+
function openRouterOnlyEffort(value, fallback) {
|
|
181
|
+
return value && SUBAGENT_MODEL_EFFORTS.includes(value) ? value : fallback;
|
|
182
|
+
}
|
|
183
|
+
function openRouterOnlyModelFlags(input, context) {
|
|
184
|
+
const listDefault = defaultSubagentEntry(context.state);
|
|
185
|
+
return [
|
|
186
|
+
{ key: 'parentModel', flag: '--parent-model', envKey: 'SKS_NARUTO_PARENT_MODEL', fallback: context.mainModel || '', effort: false },
|
|
187
|
+
{ key: 'parentEffort', flag: '--parent-effort', envKey: 'SKS_NARUTO_PARENT_EFFORT', fallback: openRouterOnlyEffort(input.defaultParentEffort, OPENROUTER_ONLY_PARENT_EFFORT), effort: true },
|
|
188
|
+
{ key: 'subagentModel', flag: '--subagent-model', envKey: 'SKS_NARUTO_SUBAGENT_MODEL', fallback: listDefault?.model || '', effort: false },
|
|
189
|
+
{ key: 'subagentEffort', flag: '--subagent-effort', envKey: 'SKS_NARUTO_SUBAGENT_EFFORT', fallback: openRouterOnlyEffort(listDefault?.reasoning_effort ?? input.defaultSubagentEffort, 'medium'), effort: true }
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
function validateOpenRouterOnlyModels(context, resolved, sources, blockers) {
|
|
193
|
+
const parent = String(resolved.parentModel || '');
|
|
194
|
+
const parentLabel = parent.slice(0, 64) || 'unset';
|
|
195
|
+
if (!isOpenRouterModelId(parent))
|
|
196
|
+
blockers.push(`naruto_parent_model_not_openrouter:${parentLabel}`);
|
|
197
|
+
const listDefault = defaultSubagentEntry(context.state);
|
|
198
|
+
if (!listDefault) {
|
|
199
|
+
blockers.push('naruto_subagent_model_list_empty');
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const listed = subagentEntryForModel(context.state, resolved.subagentModel);
|
|
203
|
+
if (!listed)
|
|
204
|
+
blockers.push(`naruto_subagent_model_not_in_list:${String(resolved.subagentModel || '').slice(0, 64)}`);
|
|
205
|
+
const entry = listed ?? listDefault;
|
|
206
|
+
resolved.subagentModel = entry.model;
|
|
207
|
+
if (sources.subagentEffort === 'default' && entry.reasoning_effort)
|
|
208
|
+
resolved.subagentEffort = entry.reasoning_effort;
|
|
209
|
+
}
|
|
210
|
+
for (const scope of ['parent', 'subagent']) {
|
|
211
|
+
const effort = String(resolved[`${scope}Effort`] || '');
|
|
212
|
+
if (!SUBAGENT_MODEL_EFFORTS.includes(effort)) {
|
|
213
|
+
blockers.push(`naruto_${scope}_effort_unsupported_openrouter:${effort}:allowed_${SUBAGENT_MODEL_EFFORTS.join('_or_')}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
149
217
|
function validateGpt56EffortPair(scope, model, effort, blockers) {
|
|
150
218
|
const allowed = codexListedEfforts(model);
|
|
151
219
|
if (allowed && !allowed.includes(effort)) {
|
|
@@ -172,6 +240,7 @@ export function narutoCredentialPolicyReceipt(policy) {
|
|
|
172
240
|
parent_effort: policy.parentEffort,
|
|
173
241
|
subagent_model: policy.subagentModel,
|
|
174
242
|
subagent_effort: policy.subagentEffort,
|
|
243
|
+
child_model_mode: policy.childModelMode,
|
|
175
244
|
sources: policy.sources,
|
|
176
245
|
warnings: policy.warnings,
|
|
177
246
|
blockers: policy.blockers,
|
|
@@ -7,6 +7,8 @@ import { ensureConfinedDirectory, inspectConfinedPath } from '../managed-path-sa
|
|
|
7
7
|
import { MANAGED_OFFICIAL_SUBAGENT_ROLES, managedOfficialSubagentRoleContent, managedOfficialSubagentRoleOwnsText } from '../managed-assets/managed-assets-manifest.js';
|
|
8
8
|
import { DEFAULT_SUBAGENT_EFFORT, defaultSubagentModel as latestDefaultSubagentModel } from './model-policy.js';
|
|
9
9
|
import { latestTierModelSet } from './model-tiers.js';
|
|
10
|
+
import { allowlistedChildModel, effectiveChildModelAllowlist } from './child-model-allowlist.js';
|
|
11
|
+
import { READ_ONLY_LIST_ROLE, readOnlyListRoleContent, readOnlyListRoleOwnsText } from './read-only-list-role.js';
|
|
10
12
|
import { HARD_NARUTO_MAX_THREADS } from './thread-budget.js';
|
|
11
13
|
import { escapeRegExp } from '../text/regex.js';
|
|
12
14
|
export const DEFAULT_OFFICIAL_SUBAGENT_MAX_THREADS = 256;
|
|
@@ -52,8 +54,14 @@ export function mergeOfficialSubagentConfigResult(text = '', opts = {}) {
|
|
|
52
54
|
next = upsertDefaultUnlessInherited(next, inheritedAgents, 'enabled', `enabled = ${DEFAULT_OFFICIAL_SUBAGENT_ENABLED}`);
|
|
53
55
|
next = upsertDefaultUnlessInherited(next, inheritedAgents, 'max_depth', `max_depth = ${DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH}`);
|
|
54
56
|
next = upsertDefaultUnlessInherited(next, inheritedAgents, 'interrupt_message', `interrupt_message = ${DEFAULT_OFFICIAL_SUBAGENT_INTERRUPT_MESSAGE}`);
|
|
57
|
+
const childModels = opts.childModels ?? effectiveChildModelAllowlist();
|
|
55
58
|
const existingDefault = /^\s*default_subagent_model\s*=\s*"([^"]*)"/m.exec(next)?.[1] || '';
|
|
56
|
-
|
|
59
|
+
const childDefault = childModels.mode === 'openrouter_only'
|
|
60
|
+
? childModels.default_model
|
|
61
|
+
: latestTierModelSet().has(existingDefault) ? existingDefault : defaultOfficialSubagentModel();
|
|
62
|
+
if (childDefault) {
|
|
63
|
+
next = upsertTomlTableKey(next, 'agents', `default_subagent_model = "${childDefault}"`);
|
|
64
|
+
}
|
|
57
65
|
next = upsertDefaultUnlessInherited(next, inheritedAgents, 'default_subagent_reasoning_effort', `default_subagent_reasoning_effort = "${DEFAULT_OFFICIAL_SUBAGENT_REASONING_EFFORT}"`);
|
|
58
66
|
next = mergeOfficialMultiAgentV2FeatureConfig(next, {
|
|
59
67
|
sksOwned: opts.sksOwned === true,
|
|
@@ -140,17 +148,23 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
|
|
|
140
148
|
const effectiveMaxThreads = Math.min(maxThreads.value, HARD_NARUTO_MAX_THREADS);
|
|
141
149
|
const maxDepth = resolveLayeredValue(projectLayer.agents.max_depth, globalLayer.agents.max_depth, DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH, positiveInteger);
|
|
142
150
|
const interruptMessage = resolveLayeredValue(projectLayer.agents.interrupt_message, globalLayer.agents.interrupt_message, DEFAULT_OFFICIAL_SUBAGENT_INTERRUPT_MESSAGE, booleanValue);
|
|
143
|
-
const
|
|
151
|
+
const childModels = opts.childModels ?? effectiveChildModelAllowlist({ home });
|
|
152
|
+
const listDefault = childModels.mode === 'openrouter_only' ? childModels.default_model : null;
|
|
153
|
+
const defaultSubagentModel = resolveLayeredValue(projectLayer.agents.default_subagent_model, globalLayer.agents.default_subagent_model, listDefault ?? defaultOfficialSubagentModel(), nonEmptyString);
|
|
144
154
|
const defaultSubagentReasoningEffort = resolveLayeredValue(projectLayer.agents.default_subagent_reasoning_effort, globalLayer.agents.default_subagent_reasoning_effort, DEFAULT_OFFICIAL_SUBAGENT_REASONING_EFFORT, nonEmptyString);
|
|
145
155
|
const multiAgentV2 = resolveMultiAgentV2Layer(projectLayer.features.multi_agent_v2, globalLayer.features.multi_agent_v2, effectiveMaxThreads);
|
|
146
156
|
const effectiveMultiAgentV2 = {
|
|
147
157
|
...multiAgentV2.value,
|
|
148
158
|
maxConcurrentThreadsPerSession: Math.min(multiAgentV2.value.maxConcurrentThreadsPerSession, HARD_NARUTO_MAX_THREADS + 1)
|
|
149
159
|
};
|
|
150
|
-
const
|
|
160
|
+
const childDefault = coercedChildDefault(String(defaultSubagentModel.value), childModels);
|
|
161
|
+
const modelCoerced = childDefault.coerced;
|
|
151
162
|
const depthCoerced = maxDepth.value > 1;
|
|
152
163
|
const warnings = [
|
|
153
|
-
...(modelCoerced
|
|
164
|
+
...(modelCoerced
|
|
165
|
+
? [`${childModels.mode === 'openrouter_only' ? 'official_subagent_model_coerced_to_list_default' : 'official_subagent_model_coerced_to_latest'}:${defaultSubagentModel.value}:${defaultSubagentModel.source}`]
|
|
166
|
+
: []),
|
|
167
|
+
...(childModels.mode === 'openrouter_only' && !listDefault ? ['official_subagent_model_list_empty_openrouter_only'] : []),
|
|
154
168
|
...(depthCoerced ? [`official_subagent_max_depth_coerced_to_one:${maxDepth.value}:${maxDepth.source}`] : []),
|
|
155
169
|
...capacityNormalizationWarnings(maxThreads, multiAgentV2),
|
|
156
170
|
...(projectLayer.legacyWarnings),
|
|
@@ -162,7 +176,7 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
|
|
|
162
176
|
maxDepth: depthCoerced ? DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH : maxDepth.value,
|
|
163
177
|
jobMaxRuntimeSeconds: null,
|
|
164
178
|
interruptMessage: interruptMessage.value,
|
|
165
|
-
defaultSubagentModel:
|
|
179
|
+
defaultSubagentModel: childDefault.model,
|
|
166
180
|
defaultSubagentReasoningEffort: defaultSubagentReasoningEffort.value,
|
|
167
181
|
multiAgentV2: effectiveMultiAgentV2,
|
|
168
182
|
sources: {
|
|
@@ -180,6 +194,17 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
|
|
|
180
194
|
warnings
|
|
181
195
|
};
|
|
182
196
|
}
|
|
197
|
+
function coercedChildDefault(value, childModels) {
|
|
198
|
+
if (childModels.mode === 'openrouter_only') {
|
|
199
|
+
if (!childModels.default_model)
|
|
200
|
+
return { model: value, coerced: false };
|
|
201
|
+
const listed = allowlistedChildModel(value, childModels);
|
|
202
|
+
return listed ? { model: listed, coerced: false } : { model: childModels.default_model, coerced: true };
|
|
203
|
+
}
|
|
204
|
+
return latestTierModelSet().has(value)
|
|
205
|
+
? { model: value, coerced: false }
|
|
206
|
+
: { model: defaultOfficialSubagentModel(), coerced: true };
|
|
207
|
+
}
|
|
183
208
|
export function officialSubagentConfigWarnings(text = '', inheritedText = '') {
|
|
184
209
|
const project = configLayer(text, 'project');
|
|
185
210
|
const inherited = configLayer(inheritedText, 'global');
|
|
@@ -212,7 +237,31 @@ export function resolveInheritedOfficialSubagentConfigPath(projectConfigPath, op
|
|
|
212
237
|
return globalConfigPath === path.resolve(projectConfigPath) ? null : globalConfigPath;
|
|
213
238
|
}
|
|
214
239
|
export async function installOfficialSubagentAgentConfigs(root, opts = {}) {
|
|
215
|
-
|
|
240
|
+
const childModels = opts.childModels ?? effectiveChildModelAllowlist();
|
|
241
|
+
return installOfficialSubagentAgentConfigsAt(root, '.codex/agents', {
|
|
242
|
+
...(opts.apply === undefined ? {} : { apply: opts.apply }),
|
|
243
|
+
readOnlyListRole: childModels.mode === 'openrouter_only'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function managedRoleFiles(readOnlyListRole) {
|
|
247
|
+
return [
|
|
248
|
+
...MANAGED_OFFICIAL_SUBAGENT_ROLES.map((role) => ({
|
|
249
|
+
filename: role.filename,
|
|
250
|
+
codexName: role.codex_name,
|
|
251
|
+
expected: managedOfficialSubagentRoleContent(role),
|
|
252
|
+
owns: (text) => managedOfficialSubagentRoleOwnsText(text, role),
|
|
253
|
+
marker: role.ownership_marker
|
|
254
|
+
})),
|
|
255
|
+
...(readOnlyListRole
|
|
256
|
+
? [{
|
|
257
|
+
filename: READ_ONLY_LIST_ROLE.filename,
|
|
258
|
+
codexName: READ_ONLY_LIST_ROLE.codex_name,
|
|
259
|
+
expected: readOnlyListRoleContent(),
|
|
260
|
+
owns: readOnlyListRoleOwnsText,
|
|
261
|
+
marker: READ_ONLY_LIST_ROLE.ownership_marker
|
|
262
|
+
}]
|
|
263
|
+
: [])
|
|
264
|
+
];
|
|
216
265
|
}
|
|
217
266
|
export async function refreshGlobalOfficialSubagentAgentConfigs(codexHome, opts = {}) {
|
|
218
267
|
const home = path.resolve(codexHome);
|
|
@@ -223,6 +272,8 @@ export async function refreshGlobalOfficialSubagentAgentConfigs(codexHome, opts
|
|
|
223
272
|
}
|
|
224
273
|
async function installOfficialSubagentAgentConfigsAt(root, relativeDir, opts) {
|
|
225
274
|
const apply = opts.apply !== false;
|
|
275
|
+
const roleFiles = managedRoleFiles(opts.readOnlyListRole === true);
|
|
276
|
+
const installedAgents = roleFiles.map((role) => role.codexName);
|
|
226
277
|
const agentsDir = path.join(path.resolve(root), relativeDir);
|
|
227
278
|
const missing = [];
|
|
228
279
|
const existing = [];
|
|
@@ -262,8 +313,8 @@ async function installOfficialSubagentAgentConfigsAt(root, relativeDir, opts) {
|
|
|
262
313
|
schema: 'sks.official-subagent-agent-install.v1',
|
|
263
314
|
ok: false,
|
|
264
315
|
apply,
|
|
265
|
-
installed_agents:
|
|
266
|
-
missing:
|
|
316
|
+
installed_agents: installedAgents,
|
|
317
|
+
missing: roleFiles.map((role) => role.filename),
|
|
267
318
|
existing,
|
|
268
319
|
stale,
|
|
269
320
|
created,
|
|
@@ -277,10 +328,10 @@ async function installOfficialSubagentAgentConfigsAt(root, relativeDir, opts) {
|
|
|
277
328
|
}
|
|
278
329
|
if (apply && !opts.existingOnly)
|
|
279
330
|
await ensureConfinedDirectory(root, agentsDir);
|
|
280
|
-
for (const role of
|
|
331
|
+
for (const role of roleFiles) {
|
|
281
332
|
const absolute = path.join(agentsDir, role.filename);
|
|
282
333
|
const relative = `${relativeDir}/${role.filename}`;
|
|
283
|
-
const expected =
|
|
334
|
+
const expected = role.expected;
|
|
284
335
|
const inspected = await inspectConfinedPath(root, absolute).catch(() => null);
|
|
285
336
|
if (!inspected) {
|
|
286
337
|
preserved.push(relative);
|
|
@@ -320,7 +371,7 @@ async function installOfficialSubagentAgentConfigsAt(root, relativeDir, opts) {
|
|
|
320
371
|
generatedFiles.push(relative);
|
|
321
372
|
continue;
|
|
322
373
|
}
|
|
323
|
-
if (
|
|
374
|
+
if (role.owns(current)) {
|
|
324
375
|
stale.push(relative);
|
|
325
376
|
if (apply) {
|
|
326
377
|
await writeTextAtomic(absolute, expected);
|
|
@@ -330,7 +381,7 @@ async function installOfficialSubagentAgentConfigsAt(root, relativeDir, opts) {
|
|
|
330
381
|
continue;
|
|
331
382
|
}
|
|
332
383
|
preserved.push(relative);
|
|
333
|
-
manualBlockers.push(manualCollisionBlocker(relative, current, role));
|
|
384
|
+
manualBlockers.push(manualCollisionBlocker(relative, current, role.marker));
|
|
334
385
|
}
|
|
335
386
|
const remainingMissing = apply
|
|
336
387
|
? missing.filter((filename) => !created.includes(`${relativeDir}/${filename}`))
|
|
@@ -339,7 +390,7 @@ async function installOfficialSubagentAgentConfigsAt(root, relativeDir, opts) {
|
|
|
339
390
|
schema: 'sks.official-subagent-agent-install.v1',
|
|
340
391
|
ok: manualBlockers.length === 0 && (apply ? remainingMissing.length === 0 : true),
|
|
341
392
|
apply,
|
|
342
|
-
installed_agents:
|
|
393
|
+
installed_agents: installedAgents,
|
|
343
394
|
missing,
|
|
344
395
|
existing,
|
|
345
396
|
stale,
|
|
@@ -383,8 +434,8 @@ export async function backupInvalidToml(file, text, tag) {
|
|
|
383
434
|
await writeTextAtomic(backupPath, String(text || ''), { mode: 0o600 });
|
|
384
435
|
return backupPath;
|
|
385
436
|
}
|
|
386
|
-
function manualCollisionBlocker(relative, text,
|
|
387
|
-
const markerPresent = String(text || '').includes(
|
|
437
|
+
function manualCollisionBlocker(relative, text, marker) {
|
|
438
|
+
const markerPresent = String(text || '').includes(marker);
|
|
388
439
|
return markerPresent
|
|
389
440
|
? `manual_modified_official_subagent_config:${relative}`
|
|
390
441
|
: `manual_user_owned_official_subagent_collision:${relative}`;
|