sneakoscope 10.7.0 → 10.7.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 +27 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +55 -20
- package/dist/commands/bridge.js +14 -1
- package/dist/commands/codex.js +5 -1
- package/dist/commands/doctor-console.js +2 -0
- package/dist/commands/doctor.js +8 -9
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/agent-role-config.js +6 -2
- package/dist/core/agents/sks-model-pin-hygiene.js +150 -0
- package/dist/core/codex/codex-config-guard.js +18 -13
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +7 -1
- package/dist/core/codex-app/menubar/constants.js +0 -1
- package/dist/core/codex-lb/bridge-contracts.js +5 -0
- package/dist/core/codex-lb/combined-catalog/builder.js +4 -2
- package/dist/core/codex-lb/combined-catalog/latest-generation.js +7 -34
- package/dist/core/codex-lb/combined-catalog/normalize.js +3 -1
- package/dist/core/codex-lb/combined-catalog/persistence.js +4 -0
- package/dist/core/codex-lb/combined-catalog/storage.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/codex-lb-turn-state.js +35 -0
- package/dist/core/codex-lb/desktop-bridge/compaction-adapter.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +10 -4
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +138 -36
- package/dist/core/codex-lb/desktop-bridge/models-endpoint.js +62 -0
- package/dist/core/codex-lb/desktop-bridge/provider-handoff.js +88 -0
- package/dist/core/codex-lb/desktop-bridge/provider-request-compat.js +89 -0
- package/dist/core/codex-lb/desktop-bridge/rejection-log.js +61 -0
- package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +108 -16
- package/dist/core/codex-lb/desktop-bridge/security.js +101 -55
- package/dist/core/codex-lb/desktop-bridge/server.js +15 -2
- package/dist/core/codex-lb/desktop-bridge/types.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +3 -0
- package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +2 -6
- package/dist/core/codex-lb/desktop-bridge-migration.js +14 -2
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +125 -3
- package/dist/core/codex-lb/desktop-controller-v3/model-refresh.js +140 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +5 -5
- package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +14 -11
- package/dist/core/codex-lb/desktop-controller-v3/status.js +2 -3
- package/dist/core/codex-lb/desktop-controller-v3.js +3 -1
- package/dist/core/codex-lb/desktop-service.js +51 -17
- package/dist/core/codex-lb/model-refresh-after.js +16 -0
- package/dist/core/codex-lb/request-route-resolver.js +1 -1
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +0 -1
- package/dist/core/commands/basic-cli.js +5 -0
- package/dist/core/commands/menubar-command.js +5 -1
- package/dist/core/doctor/desktop-bridge-catalog-repair.js +20 -17
- package/dist/core/hooks-runtime/jev-spawn-routing.js +1 -5
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +6 -8
- package/dist/core/hooks-runtime.js +13 -9
- package/dist/core/init/skills.js +22 -3
- package/dist/core/provider/provider-context.js +4 -1
- package/dist/core/search/context.js +2 -2
- package/dist/core/subagents/model-tiers.js +19 -3
- package/dist/core/update/update-migration-state/desktop-bridge-catalog-repair-stage.js +4 -3
- package/dist/core/update/update-migration-state.js +10 -40
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +0 -1
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +32 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +15 -5
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +0 -2
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +3 -0
- package/package.json +2 -2
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +0 -49
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readText, writeJsonAtomic } from '../../fsx.js';
|
|
4
|
+
import { safeWriteCodexConfigToml } from '../../codex-runtime/codex-desktop-config-policy.js';
|
|
5
|
+
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
6
|
+
import { codexReadsBridgeModels, hasUserOwnedModelCatalog, setDesktopBridgeManagedModelCatalog } from '../../../cli/install-helpers-codex-lb-config.js';
|
|
7
|
+
import { captureCodexAuthSnapshot } from '../desktop-auth-invariant.js';
|
|
8
|
+
import { desktopBridgeModelsEtag } from '../desktop-bridge/models-endpoint.js';
|
|
9
|
+
export const MODEL_REFRESH_STATE_SCHEMA = 'sks.bridge-model-refresh-state.v1';
|
|
10
|
+
export const MODEL_REFRESH_DEBOUNCE_MS = 60_000;
|
|
11
|
+
export const MODEL_REFRESH_RECOVERY_COMMAND = 'sks bridge models refresh';
|
|
12
|
+
export function modelRefreshStatePath(home) {
|
|
13
|
+
return path.join(path.resolve(home), '.codex', 'sks', 'sks-bridge-model-refresh.json');
|
|
14
|
+
}
|
|
15
|
+
export async function readModelRefreshState(home) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(await readText(modelRefreshStatePath(home), ''));
|
|
18
|
+
return parsed?.schema === MODEL_REFRESH_STATE_SCHEMA && typeof parsed.refreshed_at === 'string'
|
|
19
|
+
&& typeof parsed.provider_registry_generation === 'string' && typeof parsed.generation === 'string'
|
|
20
|
+
&& Number.isSafeInteger(parsed.total)
|
|
21
|
+
? parsed
|
|
22
|
+
: null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function writeModelRefreshState(home, state) {
|
|
29
|
+
await writeJsonAtomic(modelRefreshStatePath(home), { schema: MODEL_REFRESH_STATE_SCHEMA, ...state }, { mode: 0o600 });
|
|
30
|
+
}
|
|
31
|
+
export function refreshedRecently(state, registryGeneration, nowMs) {
|
|
32
|
+
if (!state || state.provider_registry_generation !== registryGeneration)
|
|
33
|
+
return false;
|
|
34
|
+
const age = nowMs - Date.parse(state.refreshed_at);
|
|
35
|
+
return Number.isFinite(age) && age >= 0 && age < MODEL_REFRESH_DEBOUNCE_MS;
|
|
36
|
+
}
|
|
37
|
+
export function listedModelSlugs(catalog) {
|
|
38
|
+
if (!catalog)
|
|
39
|
+
return [];
|
|
40
|
+
return [...new Set(catalog.models
|
|
41
|
+
.filter((model) => !/^hid(?:e|den)$/i.test(String(model.visibility || 'list')))
|
|
42
|
+
.map((model) => String(model.slug || ''))
|
|
43
|
+
.filter(Boolean))].sort();
|
|
44
|
+
}
|
|
45
|
+
export function modelListDiff(before, after) {
|
|
46
|
+
const was = new Set(before);
|
|
47
|
+
const is = new Set(after);
|
|
48
|
+
return { added: after.filter((slug) => !was.has(slug)), removed: before.filter((slug) => !is.has(slug)) };
|
|
49
|
+
}
|
|
50
|
+
export function modelRefreshLine(input) {
|
|
51
|
+
const line = `Model list refreshed: ${input.total} models (+${input.added} −${input.removed}) generation ${input.generation.slice(0, 8)}`;
|
|
52
|
+
return input.restartCodex ? `${line}. Restart Codex to see the new model list.` : line;
|
|
53
|
+
}
|
|
54
|
+
export async function bridgeServesModels(baseUrl, catalog, options = {}) {
|
|
55
|
+
const expected = desktopBridgeModelsEtag(catalog.generation);
|
|
56
|
+
let failure = 'bridge_models_self_check_unreachable';
|
|
57
|
+
for (let attempt = 0; attempt < (options.attempts ?? 3); attempt += 1) {
|
|
58
|
+
if (attempt > 0)
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
60
|
+
failure = await new Promise((resolve) => {
|
|
61
|
+
let url;
|
|
62
|
+
try {
|
|
63
|
+
url = new URL(`${baseUrl.replace(/\/+$/, '')}/models`);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
resolve('bridge_models_self_check_url_invalid');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (url.protocol !== 'http:' || !['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname)) {
|
|
70
|
+
resolve('bridge_models_self_check_not_loopback');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const request = http.get(url, { timeout: options.timeoutMs ?? 3_000 }, (response) => {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
76
|
+
response.once('end', () => {
|
|
77
|
+
if (response.statusCode !== 200) {
|
|
78
|
+
resolve(`bridge_models_self_check_status_${response.statusCode}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (response.headers.etag !== expected) {
|
|
82
|
+
resolve('bridge_models_self_check_generation_mismatch');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const models = JSON.parse(Buffer.concat(chunks).toString('utf8')).models;
|
|
87
|
+
resolve(Array.isArray(models) && models.length === catalog.models.length ? null : 'bridge_models_self_check_rows_mismatch');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
resolve('bridge_models_self_check_body_invalid');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
response.once('error', () => resolve('bridge_models_self_check_unreachable'));
|
|
94
|
+
});
|
|
95
|
+
request.once('timeout', () => request.destroy());
|
|
96
|
+
request.once('error', () => resolve('bridge_models_self_check_unreachable'));
|
|
97
|
+
});
|
|
98
|
+
if (failure === null)
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return failure;
|
|
102
|
+
}
|
|
103
|
+
export async function alignCodexModelSource(input) {
|
|
104
|
+
const current = await readText(input.configPath, '');
|
|
105
|
+
if (hasUserOwnedModelCatalog(current)) {
|
|
106
|
+
return { source: 'user_model_catalog_json', changed: false, warnings: ['codex_model_list_pinned_by_user_model_catalog_json'] };
|
|
107
|
+
}
|
|
108
|
+
const baseUrl = readTopLevelTomlString(current, 'openai_base_url');
|
|
109
|
+
if (!baseUrl || (!codexReadsBridgeModels(current) && !readTopLevelTomlString(current, 'model_catalog_json'))) {
|
|
110
|
+
return { source: 'not_managed', changed: false, warnings: [] };
|
|
111
|
+
}
|
|
112
|
+
const auth = await captureCodexAuthSnapshot({ home: input.home, authPath: input.authPath }).catch(() => null);
|
|
113
|
+
const warnings = [];
|
|
114
|
+
let wantBridge = auth?.mode === 'chatgpt_oauth';
|
|
115
|
+
if (wantBridge) {
|
|
116
|
+
const failure = await input.selfCheck(baseUrl, input.catalog);
|
|
117
|
+
if (failure) {
|
|
118
|
+
warnings.push(failure);
|
|
119
|
+
wantBridge = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!wantBridge && !input.catalogPath) {
|
|
123
|
+
return { source: codexReadsBridgeModels(current) ? 'bridge_models' : 'model_catalog_json', changed: false, warnings: [...warnings, 'codex_model_catalog_path_unavailable'] };
|
|
124
|
+
}
|
|
125
|
+
const next = setDesktopBridgeManagedModelCatalog(current, wantBridge ? null : input.catalogPath);
|
|
126
|
+
const source = wantBridge ? 'bridge_models' : 'model_catalog_json';
|
|
127
|
+
if (next === current)
|
|
128
|
+
return { source, changed: false, warnings };
|
|
129
|
+
if (!input.allowConfigWrites) {
|
|
130
|
+
return {
|
|
131
|
+
source: codexReadsBridgeModels(current) ? 'bridge_models' : 'model_catalog_json',
|
|
132
|
+
changed: false,
|
|
133
|
+
warnings: [...warnings, `codex_model_source_change_deferred:${source}`]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const write = await safeWriteCodexConfigToml(input.configPath, current, next, 'desktop-bridge-model-source', { verifyUnchangedBeforeWrite: true });
|
|
137
|
+
if (!write.ok)
|
|
138
|
+
return { source: codexReadsBridgeModels(current) ? 'bridge_models' : 'model_catalog_json', changed: false, warnings: [...warnings, `codex_model_source_write_refused:${write.status}`] };
|
|
139
|
+
return { source, changed: true, warnings };
|
|
140
|
+
}
|
|
@@ -9,7 +9,7 @@ import { readActiveCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
|
9
9
|
import { MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
10
10
|
import { ensureUserReadOnlyListRole } from '../../subagents/read-only-list-role.js';
|
|
11
11
|
import { canonicalizeBridgeModelId } from '../route-index.js';
|
|
12
|
-
import {
|
|
12
|
+
import { syncAsRefresh } from './catalog.js';
|
|
13
13
|
import { hasOpenRouterRoute, openRouterOnlyStoreLocation } from './openrouter-only-catalog.js';
|
|
14
14
|
import { applyOpenRouterOnlyMainModel, openRouterOnlyMainModelTarget, restoreOpenRouterOnlyMainModel } from './openrouter-only-main-model.js';
|
|
15
15
|
import { openRouterOnlyProviderBlocker, openRouterOnlyStatusFromCore } from './openrouter-only-status.js';
|
|
@@ -116,10 +116,10 @@ async function enableOpenRouterOnly(noRestart, options) {
|
|
|
116
116
|
if (entries.length === 0) {
|
|
117
117
|
return finish('openrouter-only.set', false, ['openrouter_only_no_openrouter_models'], {}, options);
|
|
118
118
|
}
|
|
119
|
-
const sync = await
|
|
119
|
+
const sync = await syncAsRefresh({
|
|
120
120
|
...options,
|
|
121
121
|
settings: { ...(options.settings || {}), auth_priority_enabled: false }
|
|
122
|
-
}, {
|
|
122
|
+
}, 'openrouter-only.set', {
|
|
123
123
|
openRouterOnly: { ...before, enabled: true, subagent_models: entries },
|
|
124
124
|
onActivated: async () => {
|
|
125
125
|
await requireRoutableEntry(core.paths, { ...before, enabled: true, subagent_models: entries });
|
|
@@ -204,7 +204,7 @@ export async function turnOffOpenRouterOnly(options, behavior = {}) {
|
|
|
204
204
|
return { ok: true, changed: false, catalog_sync: null, main_model: null, blockers: [] };
|
|
205
205
|
}
|
|
206
206
|
const off = { ...before, enabled: false };
|
|
207
|
-
const sync = (commit) =>
|
|
207
|
+
const sync = (commit) => syncAsRefresh(options, 'openrouter-only.off', {
|
|
208
208
|
...(behavior.restartService === undefined ? {} : { restartService: behavior.restartService }),
|
|
209
209
|
openRouterOnly: off,
|
|
210
210
|
...(commit ? { onActivated: () => writeOpenRouterOnlyState({ enabled: false }, location) } : {})
|
|
@@ -319,7 +319,7 @@ export async function setSubagentModels(raw, noRestart, options, expectedProfile
|
|
|
319
319
|
}, options);
|
|
320
320
|
}
|
|
321
321
|
const exposedBefore = await exposedModelIds(paths);
|
|
322
|
-
const sync = await
|
|
322
|
+
const sync = await syncAsRefresh(options, 'subagent-models.set', {
|
|
323
323
|
openRouterOnly: { ...before, subagent_models: entries },
|
|
324
324
|
onActivated: async () => {
|
|
325
325
|
if (entries.length > 0)
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { bindCodexClientProvider } from '../../../cli/install-helpers-codex-lb-config.js';
|
|
2
|
-
import { safeWriteCodexConfigToml } from '../../codex-runtime/codex-desktop-config-policy.js';
|
|
3
|
-
import { readText } from '../../fsx.js';
|
|
4
1
|
import { testOpenRouterConnection } from '../../providers/openrouter/openrouter-account.js';
|
|
5
2
|
import { openRouterSecretPaths } from '../../providers/openrouter/openrouter-secret-store.js';
|
|
6
3
|
import { codexLbEnvPath, codexLbMetadataPath, readCodexLbModelCatalog } from '../codex-lb-env.js';
|
|
@@ -8,6 +5,14 @@ import { configureProviderCredential, recordProviderCredentialValidation, remove
|
|
|
8
5
|
import { configureBridgeProviderProfile, resolveBridgeProviderRegistry, setBridgeProviderEnabled } from '../provider-registry.js';
|
|
9
6
|
import { commandResult, activeProviderIds, controllerEnv, controllerPaths, nowIso, persistRuntimeSettings, providerCode, quiesceRunningBridge, resolveRawCredentials, stringArray, timeoutMs } from './shared.js';
|
|
10
7
|
import { desktopBridgeStatusV3, loadCore } from './status.js';
|
|
8
|
+
import { refreshModelCatalog } from './catalog.js';
|
|
9
|
+
async function refreshAfterProviderChange(reason, options) {
|
|
10
|
+
const core = await loadCore(options);
|
|
11
|
+
if (!core.activeCatalog.ok || !core.policy)
|
|
12
|
+
return null;
|
|
13
|
+
const { sync: _sync, ...summary } = await refreshModelCatalog(options, { reason, force: true, allowConfigWrites: true });
|
|
14
|
+
return summary;
|
|
15
|
+
}
|
|
11
16
|
export async function configureProvider(request, options) {
|
|
12
17
|
const paths = controllerPaths(options);
|
|
13
18
|
const restartDeferred = await quiesceRunningBridge(await loadCore(options), options);
|
|
@@ -37,12 +42,14 @@ export async function configureProvider(request, options) {
|
|
|
37
42
|
if (core.activeCatalog.ok && core.policy) {
|
|
38
43
|
await persistRuntimeSettings({ ...core, registry }, options, { restartService: false });
|
|
39
44
|
}
|
|
45
|
+
const modelRefresh = restartDeferred ? null : await refreshAfterProviderChange('provider.configure', options);
|
|
40
46
|
const status = await desktopBridgeStatusV3(options);
|
|
41
47
|
const blockers = restartDeferred ? ['desktop_bridge_restart_deferred_until_provider_validation'] : [];
|
|
42
48
|
return commandResult('provider.configure', true, status, {
|
|
43
49
|
configuration: configured,
|
|
44
50
|
restart_deferred: restartDeferred,
|
|
45
51
|
recovery_action: restartDeferred ? `bridge provider validate ${request.provider_id}` : null,
|
|
52
|
+
model_refresh: modelRefresh,
|
|
46
53
|
}, blockers, options);
|
|
47
54
|
}
|
|
48
55
|
export async function validateProvider(providerId, options) {
|
|
@@ -85,8 +92,9 @@ export async function validateProvider(providerId, options) {
|
|
|
85
92
|
});
|
|
86
93
|
}
|
|
87
94
|
}
|
|
95
|
+
const modelRefresh = ok ? await refreshAfterProviderChange('provider.validate', options) : null;
|
|
88
96
|
const status = await desktopBridgeStatusV3(options);
|
|
89
|
-
return commandResult('provider.validate', true, status, { provider_id: providerId, validated: ok, validation: publicValidationResult(result) }, ok ? [] : blockers, options);
|
|
97
|
+
return commandResult('provider.validate', true, status, { provider_id: providerId, validated: ok, validation: publicValidationResult(result), model_refresh: modelRefresh }, ok ? [] : blockers, options);
|
|
90
98
|
}
|
|
91
99
|
export async function setProviderState(providerId, enabled, operation, options) {
|
|
92
100
|
const paths = controllerPaths(options);
|
|
@@ -111,14 +119,9 @@ export async function setProviderState(providerId, enabled, operation, options)
|
|
|
111
119
|
});
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
|
-
|
|
115
|
-
const current = await readText(paths.configPath, '');
|
|
116
|
-
const next = bindCodexClientProvider(current, enabled ? 'codex-lb' : 'openai');
|
|
117
|
-
if (next !== current)
|
|
118
|
-
await safeWriteCodexConfigToml(paths.configPath, current, next, 'codex-lb-client-provider');
|
|
119
|
-
}
|
|
122
|
+
const modelRefresh = await refreshAfterProviderChange(operation, options);
|
|
120
123
|
const status = await desktopBridgeStatusV3(options);
|
|
121
|
-
return commandResult(operation, true, status, { provider_id: providerId, enabled }, [], options);
|
|
124
|
+
return commandResult(operation, true, status, { provider_id: providerId, enabled, model_refresh: modelRefresh }, [], options);
|
|
122
125
|
}
|
|
123
126
|
export async function removeCredential(providerId, options) {
|
|
124
127
|
const paths = controllerPaths(options);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, DESKTOP_BRIDGE_MANAGED_MARKER
|
|
1
|
+
import { DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, DESKTOP_BRIDGE_MANAGED_MARKER } from '../../../cli/install-helpers-codex-lb-config.js';
|
|
2
2
|
import { readActiveCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
3
3
|
import { desktopBridgeServiceStatus, desktopBridgeServicePaths, readDesktopBridgeServiceSettings } from '../desktop-service.js';
|
|
4
4
|
import { captureCodexAuthSnapshot } from '../desktop-auth-invariant.js';
|
|
@@ -276,8 +276,7 @@ function isManagedConfig(config) {
|
|
|
276
276
|
const top = String(config || '').split(/\n\s*\[/)[0] || '';
|
|
277
277
|
return [
|
|
278
278
|
DESKTOP_BRIDGE_MANAGED_MARKER,
|
|
279
|
-
DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER
|
|
280
|
-
DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER
|
|
279
|
+
DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER
|
|
281
280
|
].every((marker) => top.split(/\r?\n/).some((line) => line.trim() === marker))
|
|
282
281
|
&& /^\s*model_provider\s*=\s*"openai"\s*(?:#.*)?$/m.test(top);
|
|
283
282
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listSelectableModels, selectExposedModels, syncCatalog } from './desktop-controller-v3/catalog.js';
|
|
1
|
+
import { listSelectableModels, refreshModels, selectExposedModels, syncCatalog } from './desktop-controller-v3/catalog.js';
|
|
2
2
|
import { explainRoute, ensureDesktopBridge, repairDesktopBridge, rollbackDesktopBridge, setDefaultProvider, setOfficialModelsMode, setAuthPriority, unmanageDesktopBridge } from './desktop-controller-v3/lifecycle-commands.js';
|
|
3
3
|
import { configureProvider, removeCredential, setProviderState, validateProvider } from './desktop-controller-v3/provider-commands.js';
|
|
4
4
|
import { listSubagentModels, openRouterOnlyStatusCommand, setOpenRouterOnly, setSubagentModels } from './desktop-controller-v3/openrouter-only-commands.js';
|
|
@@ -55,6 +55,8 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
|
55
55
|
return listSelectableModels(options);
|
|
56
56
|
if (request.operation === 'models.select')
|
|
57
57
|
return selectExposedModels(request.public_ids, options);
|
|
58
|
+
if (request.operation === 'models.refresh')
|
|
59
|
+
return refreshModels(request, options);
|
|
58
60
|
if (request.operation === 'catalog.sync')
|
|
59
61
|
return syncCatalog(options);
|
|
60
62
|
if (request.operation === 'catalog.status') {
|
|
@@ -13,6 +13,8 @@ import { canonicalizeBridgeModelId, normalizeBridgeUpstreamModelId, sha256Stable
|
|
|
13
13
|
import { captureCodexAuthSnapshot } from './desktop-auth-invariant.js';
|
|
14
14
|
import { applyOfficialModelPassthrough, bridgeRoutePolicyPath, writeBridgeRoutingPolicy } from './provider-route-policy.js';
|
|
15
15
|
import { desktopBridgeRuntimeVersion, desktopBridgeRuntimeVersionStale } from './desktop-bridge/state.js';
|
|
16
|
+
import { REQUEST_COMPAT_INCLUDE_VALUE, REQUEST_COMPAT_MAX_EXTRA_INCLUDE } from './desktop-bridge/provider-request-compat.js';
|
|
17
|
+
import { bridgeRouteIndexPath, combinedBridgeCatalogPath, readActiveCombinedBridgeCatalog } from './combined-catalog.js';
|
|
16
18
|
import { PACKAGE_VERSION } from '../version.js';
|
|
17
19
|
import { canonicalChildModelId, readOpenRouterOnlyStateSync } from '../subagents/child-model-allowlist.js';
|
|
18
20
|
import { BRIDGE_OFFICIAL_ROUTE_ID } from './bridge-contracts.js';
|
|
@@ -133,7 +135,7 @@ export async function resolveDesktopBridgeActivationSettings(options = {}) {
|
|
|
133
135
|
const merged = { ...(persisted || {}), ...(options.settings || {}) };
|
|
134
136
|
const host = merged.listen_host || DEFAULT_DESKTOP_BRIDGE_HOST;
|
|
135
137
|
const port = merged.listen_port ?? await (options.selectAvailablePort || selectAvailableDesktopBridgePort)(host);
|
|
136
|
-
return defaultDesktopBridgeServiceSettings({ ...merged, ...(options.providerRegistry ? { provider_registry: options.providerRegistry } : {}), ...(options.routePolicy ? { route_policy: options.routePolicy } : {}), ...(options.providerSessionPins ? { provider_session_pins: [...options.providerSessionPins] } : {}), client_capability_sha256: sha256(clientCapability), listen_host: host, listen_port: port });
|
|
138
|
+
return defaultDesktopBridgeServiceSettings({ ...merged, ...(options.providerRegistry ? { provider_registry: withPersistedRequestCompat(options.providerRegistry, persisted?.provider_registry) } : {}), ...(options.routePolicy ? { route_policy: options.routePolicy } : {}), ...(options.providerSessionPins ? { provider_session_pins: [...options.providerSessionPins] } : {}), client_capability_sha256: sha256(clientCapability), listen_host: host, listen_port: port });
|
|
137
139
|
}
|
|
138
140
|
export async function readDesktopBridgeServiceSettings(file) {
|
|
139
141
|
let stat;
|
|
@@ -297,7 +299,8 @@ function normalizeEndpointForBinding(value) {
|
|
|
297
299
|
export async function resolveDesktopBridgeRuntimeConfig(options = {}) {
|
|
298
300
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
299
301
|
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
300
|
-
|
|
302
|
+
const persisted = await readDesktopBridgeServiceSettings(paths.settings_path);
|
|
303
|
+
let settings = defaultDesktopBridgeServiceSettings({ ...(persisted || {}), ...(options.settings || {}), ...(options.providerRegistry ? { provider_registry: withPersistedRequestCompat(options.providerRegistry, persisted?.provider_registry) } : {}), ...(options.routePolicy ? { route_policy: options.routePolicy } : {}), ...(options.providerSessionPins ? { provider_session_pins: [...options.providerSessionPins] } : {}) });
|
|
301
304
|
const clientCapability = options.clientCapability
|
|
302
305
|
? validateClientCapability(options.clientCapability)
|
|
303
306
|
: await readDesktopBridgeClientCapability(paths.client_capability_path);
|
|
@@ -313,23 +316,17 @@ export async function resolveDesktopBridgeRuntimeConfig(options = {}) {
|
|
|
313
316
|
if (!officialOnly && !providerRouteIds.some((id) => credentials.registry.providers[id].credential_state === 'ready'))
|
|
314
317
|
throw new Error('desktop_bridge_provider_credentials_unavailable');
|
|
315
318
|
const persistProviderSessionPins = options.persistProviderSessionPins
|
|
316
|
-
|| ((pins) => persistDesktopBridgeSessionPins(paths.settings_path,
|
|
319
|
+
|| ((pins) => persistDesktopBridgeSessionPins(paths.settings_path, pins));
|
|
317
320
|
const openRouterOnly = resolveDesktopBridgeOpenRouterOnly({ home, ...(options.env ? { env: options.env } : {}) });
|
|
318
321
|
const config = { providerRegistry: settings.provider_registry, routePolicy: settings.route_policy, providerSessionPins: settings.provider_session_pins, ...(options.resolveRequestRoute ? { resolveRequestRoute: options.resolveRequestRoute } : {}), persistProviderSessionPins, resolveProviderCredential: credentials.resolver, clientCapabilitySha256: settings.client_capability_sha256, listenHost: settings.listen_host, listenPort: settings.listen_port, allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, allowedOrigins: settings.allowed_origins, connectTimeoutMs: settings.connect_timeout_ms, idleTimeoutMs: settings.idle_timeout_ms, officialPassthrough: settings.official_passthrough.enabled ? { baseUrl: settings.official_passthrough.base_url } : null, ...(openRouterOnly ? { openRouterOnly } : {}) };
|
|
319
322
|
const primary = credentials.sources['codex-lb'] || credentials.sources.openrouter;
|
|
320
323
|
return { config, settings, loaded_env: credentials.loaded, credential_source: primary || null, credential_sources: credentials.sources, paths };
|
|
321
324
|
}
|
|
322
|
-
async function persistDesktopBridgeSessionPins(file,
|
|
325
|
+
async function persistDesktopBridgeSessionPins(file, pins) {
|
|
323
326
|
await withFileLock(settingsLockOptions(file), async () => {
|
|
324
327
|
const current = await readDesktopBridgeServiceSettings(file);
|
|
325
|
-
if (!current
|
|
326
|
-
|
|
327
|
-
|| current.listen_port !== expected.listen_port
|
|
328
|
-
|| current.provider_registry.generation !== expected.provider_registry.generation
|
|
329
|
-
|| current.route_policy.policy_generation !== expected.route_policy.policy_generation
|
|
330
|
-
|| current.route_policy.catalog_generation !== expected.route_policy.catalog_generation) {
|
|
331
|
-
throw new DesktopBridgeError('session_pin_route_unavailable');
|
|
332
|
-
}
|
|
328
|
+
if (!current)
|
|
329
|
+
return;
|
|
333
330
|
await writeDesktopBridgeServiceSettingsUnlocked(file, {
|
|
334
331
|
...current,
|
|
335
332
|
provider_session_pins: [...pins],
|
|
@@ -557,12 +554,20 @@ async function autoApplyOfficialModelsAtServe(runtime, home, options) {
|
|
|
557
554
|
runtime.config.routePolicy = converged;
|
|
558
555
|
process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.official_models_auto_applied', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, mode, policy_generation: converged.policy_generation, secret_fields_redacted: true })}\n`);
|
|
559
556
|
}
|
|
557
|
+
async function activeModelsCatalog(home) {
|
|
558
|
+
const codexHome = path.join(path.resolve(home), '.codex');
|
|
559
|
+
const active = await readActiveCombinedBridgeCatalog(combinedBridgeCatalogPath(codexHome), bridgeRouteIndexPath(codexHome)).catch(() => null);
|
|
560
|
+
if (!active?.ok || active.catalog.models.length === 0)
|
|
561
|
+
return null;
|
|
562
|
+
return { generation: active.catalog.generation, models: active.catalog.models };
|
|
563
|
+
}
|
|
560
564
|
export async function serveDesktopBridge(options = {}) {
|
|
561
565
|
let handle = null;
|
|
562
566
|
try {
|
|
563
567
|
const runtime = await resolveDesktopBridgeRuntimeConfig(options);
|
|
564
568
|
const serveHome = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
565
569
|
await autoApplyOfficialModelsAtServe(runtime, serveHome, options);
|
|
570
|
+
runtime.config.modelsCatalog = await activeModelsCatalog(serveHome);
|
|
566
571
|
const supervised = desktopBridgeIsSupervised();
|
|
567
572
|
const skewMarkerPath = path.join(path.dirname(runtime.paths.state_path), 'desktop-bridge-skew-restart.json');
|
|
568
573
|
const prepared = await preflightDesktopBridge(runtime.config);
|
|
@@ -701,8 +706,9 @@ function validateProviderSessionPins(value) {
|
|
|
701
706
|
const pin = value;
|
|
702
707
|
const allowed = new Set([
|
|
703
708
|
'thread_id', 'provider_id', 'public_model', 'upstream_model',
|
|
704
|
-
'catalog_generation', 'route_policy_generation', 'created_at'
|
|
709
|
+
'catalog_generation', 'route_policy_generation', 'created_at', 'handoff_from'
|
|
705
710
|
]);
|
|
711
|
+
const target = (value) => value === 'codex-lb' || value === 'openrouter' || value === BRIDGE_OFFICIAL_ROUTE_ID;
|
|
706
712
|
const threadId = typeof pin.thread_id === 'string' ? pin.thread_id.trim() : '';
|
|
707
713
|
if (Object.keys(pin).some((key) => !allowed.has(key))
|
|
708
714
|
|| !threadId
|
|
@@ -710,7 +716,8 @@ function validateProviderSessionPins(value) {
|
|
|
710
716
|
|| threadId.length > 256
|
|
711
717
|
|| !/^[A-Za-z0-9._:/-]+$/.test(threadId)
|
|
712
718
|
|| seen.has(threadId)
|
|
713
|
-
|| (pin.provider_id
|
|
719
|
+
|| !target(pin.provider_id)
|
|
720
|
+
|| (pin.handoff_from !== undefined && (!target(pin.handoff_from) || pin.handoff_from === pin.provider_id))
|
|
714
721
|
|| canonicalizeBridgeModelId(pin.public_model) !== pin.public_model
|
|
715
722
|
|| normalizeBridgeUpstreamModelId(pin.upstream_model) !== pin.upstream_model
|
|
716
723
|
|| typeof pin.catalog_generation !== 'string'
|
|
@@ -729,7 +736,8 @@ function validateProviderSessionPins(value) {
|
|
|
729
736
|
upstream_model: pin.upstream_model,
|
|
730
737
|
catalog_generation: pin.catalog_generation,
|
|
731
738
|
route_policy_generation: pin.route_policy_generation,
|
|
732
|
-
created_at: pin.created_at
|
|
739
|
+
created_at: pin.created_at,
|
|
740
|
+
...(pin.handoff_from === undefined ? {} : { handoff_from: pin.handoff_from })
|
|
733
741
|
};
|
|
734
742
|
});
|
|
735
743
|
}
|
|
@@ -781,11 +789,37 @@ function normalizeProviderRegistrySnapshot(value) {
|
|
|
781
789
|
const providers = Object.fromEntries(['codex-lb', 'openrouter'].map((providerId) => {
|
|
782
790
|
const provider = registry.providers[providerId];
|
|
783
791
|
const sourceCatalogGeneration = provider.source_catalog_generation ?? provider.catalog_generation ?? null;
|
|
784
|
-
const { catalog_generation: _legacyCatalogGeneration, ...rest } = provider;
|
|
785
|
-
|
|
792
|
+
const { catalog_generation: _legacyCatalogGeneration, request_compat: requestCompat, ...rest } = provider;
|
|
793
|
+
const normalized = { ...rest, source_catalog_generation: sourceCatalogGeneration };
|
|
794
|
+
if (requestCompat !== undefined)
|
|
795
|
+
normalized.request_compat = validateRequestCompat(requestCompat);
|
|
796
|
+
return [providerId, normalized];
|
|
786
797
|
}));
|
|
787
798
|
return { ...registry, providers };
|
|
788
799
|
}
|
|
800
|
+
function validateRequestCompat(value) {
|
|
801
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
802
|
+
throw new Error('desktop_bridge_settings_request_compat_invalid');
|
|
803
|
+
const row = value;
|
|
804
|
+
if (Object.keys(row).some((key) => key !== 'extra_include'))
|
|
805
|
+
throw new Error('desktop_bridge_settings_request_compat_invalid');
|
|
806
|
+
const values = row.extra_include ?? [];
|
|
807
|
+
if (!Array.isArray(values) || values.length > REQUEST_COMPAT_MAX_EXTRA_INCLUDE
|
|
808
|
+
|| values.some((entry) => typeof entry !== 'string' || !REQUEST_COMPAT_INCLUDE_VALUE.test(entry))) {
|
|
809
|
+
throw new Error('desktop_bridge_settings_request_compat_invalid');
|
|
810
|
+
}
|
|
811
|
+
return { extra_include: [...new Set(values)] };
|
|
812
|
+
}
|
|
813
|
+
function withPersistedRequestCompat(registry, persisted) {
|
|
814
|
+
let providers = null;
|
|
815
|
+
for (const id of ['codex-lb', 'openrouter']) {
|
|
816
|
+
const compat = persisted?.providers?.[id]?.request_compat;
|
|
817
|
+
if (!compat || registry.providers[id].request_compat)
|
|
818
|
+
continue;
|
|
819
|
+
providers = { ...(providers ?? registry.providers), [id]: { ...registry.providers[id], request_compat: compat } };
|
|
820
|
+
}
|
|
821
|
+
return providers ? { ...registry, generation: sha256Stable(providers), providers } : registry;
|
|
822
|
+
}
|
|
789
823
|
function overridePaths(base, options) { return { settings_path: options.settingsPath || base.settings_path, state_path: options.statePath || base.state_path, client_capability_path: options.clientCapabilityPath || base.client_capability_path, launch_agent_path: options.launchAgentPath || base.launch_agent_path, stdout_log_path: options.stdoutLogPath || base.stdout_log_path, stderr_log_path: options.stderrLogPath || base.stderr_log_path }; }
|
|
790
824
|
function launchDomain(uid = typeof process.getuid === 'function' ? process.getuid() : 0) { return `gui/${uid}`; }
|
|
791
825
|
function launchService(uid) { return `${launchDomain(uid)}/${DESKTOP_BRIDGE_LAUNCHD_LABEL}`; }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists } from '../fsx.js';
|
|
4
|
+
import { desktopBridgeServicePaths } from './desktop-service.js';
|
|
5
|
+
export async function refreshModelListAfter(reason) {
|
|
6
|
+
if (process.platform !== 'darwin')
|
|
7
|
+
return null;
|
|
8
|
+
if (process.env.NODE_TEST_CONTEXT !== undefined || process.env.SKS_TEST_ISOLATION === '1' || process.env.SKS_RELEASE_UPGRADE_SMOKE === '1')
|
|
9
|
+
return null;
|
|
10
|
+
const home = path.resolve(process.env.HOME || os.homedir());
|
|
11
|
+
if (!(await exists(desktopBridgeServicePaths(home).settings_path)))
|
|
12
|
+
return null;
|
|
13
|
+
const { executeDesktopBridgeCommandV3 } = await import('./desktop-controller-v3.js');
|
|
14
|
+
const command = await executeDesktopBridgeCommandV3({ operation: 'models.refresh', reason, force: false, allow_config_writes: true }, { home, env: process.env }).catch(() => null);
|
|
15
|
+
return command?.result?.model_refresh ?? null;
|
|
16
|
+
}
|
|
@@ -23,7 +23,7 @@ export function resolveBridgeRequestRoute(request, policy, options) {
|
|
|
23
23
|
return blocked(base, 'catalog_route_index_stale');
|
|
24
24
|
}
|
|
25
25
|
const threadPin = request.thread_id ? findSessionPin(options.session_pins, request.thread_id) : null;
|
|
26
|
-
const pin = threadPin && canonicalizeBridgeModelId(threadPin.public_model) === model ? threadPin : null;
|
|
26
|
+
const pin = threadPin && threadPin.provider_id !== 'openai' && canonicalizeBridgeModelId(threadPin.public_model) === model ? threadPin : null;
|
|
27
27
|
const pinAffinity = pin ? sessionPinAffinity(pin, policy, options.route_index, options.registry) : null;
|
|
28
28
|
if (pinAffinity === 'unprovable')
|
|
29
29
|
return blocked(base, 'session_pin_route_unavailable');
|
|
@@ -197,7 +197,6 @@ export async function safeWriteCodexConfigToml(configPath, current, next, tag =
|
|
|
197
197
|
configPath,
|
|
198
198
|
before: String(current || ''),
|
|
199
199
|
cause: tag,
|
|
200
|
-
removeTopLevelModeLocks: true,
|
|
201
200
|
...(opts.preserveFastUiKeys === undefined ? {} : { preserveFastUiKeys: opts.preserveFastUiKeys }),
|
|
202
201
|
...(opts.verifyUnchangedBeforeWrite === undefined ? {} : { verifyUnchangedBeforeWrite: opts.verifyUnchangedBeforeWrite }),
|
|
203
202
|
...(opts.expectedBeforeExists === undefined ? {} : { expectedBeforeExists: opts.expectedBeforeExists }),
|
|
@@ -330,6 +330,9 @@ export async function setupCommand(args = []) {
|
|
|
330
330
|
...[...(codexHooks?.activation?.blockers || []), ...(codexHooks?.project?.blockers || [])].map((blocker) => `codex_hooks:${blocker}`)
|
|
331
331
|
]
|
|
332
332
|
};
|
|
333
|
+
const modelRefresh = result.ok ? await (await import('../codex-lb/model-refresh-after.js')).refreshModelListAfter('setup') : null;
|
|
334
|
+
if (modelRefresh)
|
|
335
|
+
result.model_refresh = modelRefresh;
|
|
333
336
|
if (!result.ok)
|
|
334
337
|
process.exitCode = 1;
|
|
335
338
|
if (flag(args, '--json')) {
|
|
@@ -337,6 +340,8 @@ export async function setupCommand(args = []) {
|
|
|
337
340
|
return result;
|
|
338
341
|
}
|
|
339
342
|
console.log(`${result.ok ? 'Setup complete' : 'Setup blocked'}: ${root}`);
|
|
343
|
+
if (typeof modelRefresh?.line === 'string')
|
|
344
|
+
console.log(modelRefresh.line);
|
|
340
345
|
console.log(`Install scope: ${installScope}`);
|
|
341
346
|
console.log(`Codex CLI: ${cliTools.codex.status}${cliTools.codex.version ? ` ${cliTools.codex.version}` : ''}`);
|
|
342
347
|
console.log(`SKS Codex hooks: ${codexHooks?.active ? 'active for every Codex project' : installScope === 'project' ? 'this project only' : 'not active (run: sks doctor --fix)'}`);
|
|
@@ -31,7 +31,9 @@ export async function menubarCommand(subcommand = 'status', args = []) {
|
|
|
31
31
|
return result;
|
|
32
32
|
}
|
|
33
33
|
if (action === 'install') {
|
|
34
|
-
const
|
|
34
|
+
const installed = await installSksMenuBar({ root, ...(home ? { home } : {}), apply: true, launch: !flag(args, '--no-launch'), quiet: flag(args, '--json') });
|
|
35
|
+
const modelRefresh = await (await import('../codex-lb/model-refresh-after.js')).refreshModelListAfter('menubar-install');
|
|
36
|
+
const result = modelRefresh ? { ...installed, model_refresh: modelRefresh } : installed;
|
|
35
37
|
if (flag(args, '--json')) {
|
|
36
38
|
printJson(result);
|
|
37
39
|
if (!result.ok)
|
|
@@ -46,6 +48,8 @@ export async function menubarCommand(subcommand = 'status', args = []) {
|
|
|
46
48
|
console.log(`SKS menu bar install: ${result.status}`);
|
|
47
49
|
for (const actionLine of result.actions)
|
|
48
50
|
console.log(`- ${actionLine}`);
|
|
51
|
+
if (typeof modelRefresh?.line === 'string')
|
|
52
|
+
console.log(modelRefresh.line);
|
|
49
53
|
for (const warning of result.warnings)
|
|
50
54
|
console.log(`warning: ${warning}`);
|
|
51
55
|
for (const blocker of result.blockers)
|
|
@@ -101,10 +101,8 @@ export async function repairDoctorDesktopBridgeCatalog(input, deps = {}) {
|
|
|
101
101
|
fix: input.fix
|
|
102
102
|
});
|
|
103
103
|
const status = await statusImpl({ home: bridgeHome, env: process.env });
|
|
104
|
-
const blockers = (status?.readiness?.blockers || []).map(String);
|
|
105
|
-
const stale = blockers.filter((blocker) => blocker.endsWith('_catalog_stale'));
|
|
106
104
|
const reverify = { fix: input.fix, bridgeHome, restarted: restarted.restarted };
|
|
107
|
-
if (!status?.management?.managed
|
|
105
|
+
if (!status?.management?.managed) {
|
|
108
106
|
return reverifyTransportIfDegraded({
|
|
109
107
|
...base,
|
|
110
108
|
ok: restarted.blockers.length === 0,
|
|
@@ -114,28 +112,33 @@ export async function repairDoctorDesktopBridgeCatalog(input, deps = {}) {
|
|
|
114
112
|
}, reverify, statusImpl, executeImpl);
|
|
115
113
|
}
|
|
116
114
|
const attempts = [];
|
|
117
|
-
let
|
|
115
|
+
let refresh = null;
|
|
116
|
+
let stale = [];
|
|
118
117
|
for (let attempt = 1; attempt <= CATALOG_REPAIR_MAX_ATTEMPTS; attempt += 1) {
|
|
119
|
-
const
|
|
120
|
-
|
|
118
|
+
const command = await executeImpl({
|
|
119
|
+
operation: 'models.refresh',
|
|
120
|
+
reason: input.reason || (input.fix ? 'doctor-fix' : 'doctor'),
|
|
121
|
+
force: false,
|
|
122
|
+
allow_config_writes: input.fix
|
|
123
|
+
}, { home: bridgeHome, env: process.env });
|
|
124
|
+
refresh = command?.result?.model_refresh && typeof command.result.model_refresh === 'object' ? command.result.model_refresh : null;
|
|
121
125
|
const after = await statusImpl({ home: bridgeHome, env: process.env });
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (commandOk && stillStale.length === 0) {
|
|
125
|
-
syncedAndVerified = true;
|
|
126
|
+
stale = (after?.readiness?.blockers || []).map(String).filter((blocker) => blocker.endsWith('_catalog_stale'));
|
|
127
|
+
if (refresh?.ok === true && stale.length === 0)
|
|
126
128
|
break;
|
|
127
|
-
}
|
|
128
|
-
attempts.push(`catalog_sync_attempt_${attempt}:${commandOk ? `still_stale:${stillStale.join(',')}` : (sync?.execution?.blockers || ['failed']).map(String).join(',')}`);
|
|
129
|
+
attempts.push(`model_refresh_attempt_${attempt}:${refresh?.ok === true ? `still_stale:${stale.join(',')}` : (refresh?.blockers || command?.execution?.blockers || ['failed']).map(String).join(',')}`);
|
|
129
130
|
}
|
|
131
|
+
const refreshed = refresh?.ok === true;
|
|
130
132
|
return reverifyTransportIfDegraded({
|
|
131
133
|
...base,
|
|
132
|
-
ok:
|
|
133
|
-
repaired:
|
|
134
|
-
warnings: [...restarted.warnings, ...(
|
|
134
|
+
ok: stale.length === 0 && restarted.blockers.length === 0,
|
|
135
|
+
repaired: (refreshed && refresh?.skipped !== true) || restarted.restarted,
|
|
136
|
+
warnings: [...restarted.warnings, ...(refresh?.warnings || []).map(String), ...(stale.length === 0 ? attempts : [])],
|
|
135
137
|
blockers: [
|
|
136
|
-
...(
|
|
138
|
+
...(stale.length === 0 ? [] : ['desktop_bridge_catalog_still_stale_after_repair', ...attempts]),
|
|
137
139
|
...restarted.blockers
|
|
138
|
-
]
|
|
140
|
+
],
|
|
141
|
+
model_refresh: refresh
|
|
139
142
|
}, reverify, statusImpl, executeImpl);
|
|
140
143
|
}
|
|
141
144
|
catch (error) {
|
|
@@ -75,7 +75,7 @@ async function openRouterOnlySpawnRouting(root, parentState, input, allowlist) {
|
|
|
75
75
|
const requestedEffort = String(input.reasoning_effort || '') || null;
|
|
76
76
|
const agent = String(input.agent_type || input.agentType || '').trim() || null;
|
|
77
77
|
const task = spawnTask(input, { items: true });
|
|
78
|
-
const choice = task
|
|
78
|
+
const choice = task
|
|
79
79
|
? await chooseChildModel({ root, task, role: agent, requestedModel: requested, requestedEffort, state, profile: childModelListProfile(allowlist) })
|
|
80
80
|
.catch(() => fallbackChildModel(state, requested, 'consult_failed', requestedEffort))
|
|
81
81
|
: fallbackChildModel(state, requested, 'empty_task', requestedEffort);
|
|
@@ -144,10 +144,6 @@ async function tierSpawnRewrite(root, state, input) {
|
|
|
144
144
|
return next;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
if (boundPlan) {
|
|
148
|
-
const fallback = roleTierFallback(agent);
|
|
149
|
-
return { ...input, model: fallback.model, reasoning_effort: fallback.effort, ...forkTurns };
|
|
150
|
-
}
|
|
151
147
|
const decision = await consultJevTurnModel({ root, prompt: task, roleId: 'spawn' }).catch(() => null);
|
|
152
148
|
if (!decision?.called)
|
|
153
149
|
return null;
|
|
@@ -328,14 +328,12 @@ export async function evaluateParentOrchestrationGate(input) {
|
|
|
328
328
|
const message = ledger.escapes === 0 ? escapeMessage(missionId, intent, next) : null;
|
|
329
329
|
return { action: 'escape', reason: 'max_blocks_reached', message, jev: null };
|
|
330
330
|
}
|
|
331
|
-
const jev =
|
|
332
|
-
|
|
333
|
-
:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
targets: intent.targets
|
|
338
|
-
}).catch(() => ({ called: false, choice: null, reason: 'consult_failed' }));
|
|
331
|
+
const jev = await consultJevToolDelegation({
|
|
332
|
+
root,
|
|
333
|
+
missionGoal: missionGoal(state),
|
|
334
|
+
toolName: intent.toolName,
|
|
335
|
+
targets: intent.targets
|
|
336
|
+
}).catch(() => ({ called: false, choice: null, reason: 'consult_failed' }));
|
|
339
337
|
const base = {
|
|
340
338
|
...ledger,
|
|
341
339
|
last_tool: intent.toolName,
|