@bitkyc08/opencodex 2.10.2 → 2.11.0
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 +31 -0
- package/bin/ocx.mjs +10 -0
- package/gui/dist/assets/index-Bk-PN-70.css +1 -0
- package/gui/dist/assets/index-BynIEIV-.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -2
- package/src/adapters/cursor/effort-map.ts +11 -0
- package/src/adapters/cursor/live-transport.ts +11 -0
- package/src/adapters/cursor/native-exec-fs.ts +9 -6
- package/src/adapters/cursor/native-exec.ts +4 -2
- package/src/adapters/cursor/protobuf-events.ts +176 -4
- package/src/adapters/cursor/request-builder.ts +15 -4
- package/src/adapters/cursor/tool-definitions.ts +118 -2
- package/src/adapters/google.ts +15 -5
- package/src/adapters/openai-chat.ts +24 -2
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +9 -5
- package/src/chat/outbound.ts +4 -3
- package/src/claude/desktop-3p.ts +222 -2
- package/src/claude/outbound.ts +15 -6
- package/src/cli/account-api.ts +4 -0
- package/src/cli/account-extended.ts +112 -0
- package/src/cli/account.ts +23 -6
- package/src/cli/claude-desktop.ts +26 -3
- package/src/cli/config-command.ts +9 -0
- package/src/cli/help.ts +18 -2
- package/src/cli/index.ts +277 -55
- package/src/cli/models.ts +5 -1
- package/src/cli/provider.ts +8 -2
- package/src/cli/ready.ts +301 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/clients/config-export.ts +12 -19
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/auth-api.ts +83 -0
- package/src/codex/auth-context.ts +5 -2
- package/src/codex/catalog/provider-fetch.ts +11 -0
- package/src/codex/catalog/sync.ts +23 -1
- package/src/codex/codex-write-lock.ts +16 -4
- package/src/codex/desired-state.ts +37 -4
- package/src/codex/history-job.ts +15 -5
- package/src/codex/history-provider.ts +31 -14
- package/src/codex/history-worker.ts +28 -4
- package/src/codex/inject-coordination.ts +13 -1
- package/src/codex/inject.ts +360 -66
- package/src/codex/internal/history-writer.ts +1 -1
- package/src/codex/native-main-lock-file.ts +5 -1
- package/src/codex/native-main-owner.ts +17 -3
- package/src/codex/native-profile-manager.ts +19 -0
- package/src/codex/native-profile-startup.ts +8 -0
- package/src/codex/native-residue.ts +140 -27
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/refresh.ts +7 -0
- package/src/codex/routing.ts +177 -36
- package/src/codex/subagent-model-fallback.ts +34 -4
- package/src/codex/sync.ts +61 -0
- package/src/codex/upstream-host-health.ts +329 -31
- package/src/combos/request.ts +2 -0
- package/src/config.ts +221 -2
- package/src/images/loop.ts +1 -1
- package/src/integrations/native/ownership-preflight.ts +39 -2
- package/src/lib/bun-stream-caps.ts +3 -3
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/windows-secret-acl.ts +141 -39
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +18 -2
- package/src/oauth/key-providers.ts +12 -0
- package/src/providers/derive.ts +54 -2
- package/src/providers/free-directory.ts +6 -5
- package/src/providers/model-discovery.ts +9 -3
- package/src/providers/quota.ts +592 -0
- package/src/providers/registry.ts +316 -13
- package/src/responses/parser.ts +26 -10
- package/src/responses/reasoning-replay-cache.ts +1 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +2 -1
- package/src/server/auth-cors.ts +44 -13
- package/src/server/chat-completions.ts +0 -4
- package/src/server/claude-messages.ts +73 -15
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/index.ts +328 -111
- package/src/server/lifecycle.ts +36 -0
- package/src/server/management/agent-settings-routes.ts +147 -56
- package/src/server/management/config-routes.ts +7 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/native-integration-routes.ts +199 -20
- package/src/server/management/provider-routes.ts +41 -0
- package/src/server/management/routing-profile-routes.ts +234 -5
- package/src/server/management/system-restart.ts +12 -10
- package/src/server/management/system-routes.ts +20 -0
- package/src/server/management-auth.ts +51 -3
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +129 -4
- package/src/server/readiness.ts +99 -0
- package/src/server/relay.ts +113 -97
- package/src/server/request-log.ts +10 -4
- package/src/server/responses/compact.ts +107 -12
- package/src/server/responses/core.ts +220 -39
- package/src/server/responses-item-id-repair.ts +22 -3
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +25 -14
- package/src/server/ws-bridge.ts +27 -22
- package/src/service-manager-probe.ts +520 -10
- package/src/service.ts +134 -2
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +74 -9
- package/src/types.ts +68 -2
- package/src/update/index.ts +12 -0
- package/src/update/job.ts +392 -18
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/usage/log.ts +1 -1
- package/src/vision/index.ts +77 -2
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/parse.ts +4 -1
- package/gui/dist/assets/index-BKVqyYqT.js +0 -70
- package/gui/dist/assets/index-Ca_3269W.css +0 -1
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
fstatSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
readSync,
|
|
11
|
+
statSync,
|
|
12
|
+
} from "node:fs";
|
|
3
13
|
import type { Stats } from "node:fs";
|
|
4
14
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
15
|
|
|
@@ -69,6 +79,8 @@ const MODELS_CACHE_FILE_NAME = basename(CODEX_MODELS_CACHE_PATH);
|
|
|
69
79
|
const JOURNAL_FILE_NAME = "opencodex-journal.json";
|
|
70
80
|
const HISTORY_DATABASE_FILE_NAME = "state_5.sqlite";
|
|
71
81
|
const ROUTED_CATALOG_DESCRIPTION_PREFIX = "Routed via opencodex → ";
|
|
82
|
+
const MAX_ROLLOUT_INSPECTION_BYTES = 64 * 1024 * 1024;
|
|
83
|
+
const ROLLOUT_READ_CHUNK_BYTES = 64 * 1024;
|
|
72
84
|
|
|
73
85
|
function errorCode(error: unknown): string | undefined {
|
|
74
86
|
return (error as NodeJS.ErrnoException | undefined)?.code;
|
|
@@ -141,6 +153,53 @@ function indeterminate(
|
|
|
141
153
|
return { kind: "indeterminate", surface, path, reason };
|
|
142
154
|
}
|
|
143
155
|
|
|
156
|
+
function rolloutSessionMetaPayload(
|
|
157
|
+
line: string,
|
|
158
|
+
): { kind: "payload"; payload: Record<string, unknown> | null } | { kind: "malformed"; reason: string } {
|
|
159
|
+
let parsed: unknown;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(line);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return { kind: "malformed", reason: `malformed rollout JSONL: ${errorReason(error)}` };
|
|
164
|
+
}
|
|
165
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
166
|
+
return { kind: "malformed", reason: "rollout JSONL record is not an object" };
|
|
167
|
+
}
|
|
168
|
+
const record = parsed as Record<string, unknown>;
|
|
169
|
+
if (record.type !== "session_meta") return { kind: "payload", payload: null };
|
|
170
|
+
if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) {
|
|
171
|
+
return { kind: "malformed", reason: "session_meta payload has an unknown shape" };
|
|
172
|
+
}
|
|
173
|
+
return { kind: "payload", payload: record.payload as Record<string, unknown> };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function consumeRolloutLines(
|
|
177
|
+
surface: "history" | "history-backup",
|
|
178
|
+
path: string,
|
|
179
|
+
partial: string,
|
|
180
|
+
first: Record<string, unknown> | undefined,
|
|
181
|
+
latest: Record<string, unknown> | undefined,
|
|
182
|
+
): NativeRoutedResidueResult | { kind: "continue"; partial: string; first: Record<string, unknown> | undefined; latest: Record<string, unknown> | undefined } {
|
|
183
|
+
let rest = partial;
|
|
184
|
+
let newline = rest.indexOf("\n");
|
|
185
|
+
while (newline !== -1) {
|
|
186
|
+
const line = rest.slice(0, newline);
|
|
187
|
+
rest = rest.slice(newline + 1);
|
|
188
|
+
if (line.trim()) {
|
|
189
|
+
const payload = rolloutSessionMetaPayload(line);
|
|
190
|
+
if (payload.kind === "malformed") {
|
|
191
|
+
return indeterminate(surface, path, payload.reason);
|
|
192
|
+
}
|
|
193
|
+
if (payload.payload !== null) {
|
|
194
|
+
first ??= payload.payload;
|
|
195
|
+
latest = payload.payload;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
newline = rest.indexOf("\n");
|
|
199
|
+
}
|
|
200
|
+
return { kind: "continue", partial: rest, first, latest };
|
|
201
|
+
}
|
|
202
|
+
|
|
144
203
|
function classifyToml(
|
|
145
204
|
surface: "config" | "profile",
|
|
146
205
|
path: string,
|
|
@@ -358,50 +417,104 @@ function classifyReferencedRollout(
|
|
|
358
417
|
surface: "history" | "history-backup",
|
|
359
418
|
reference: RolloutReference,
|
|
360
419
|
): NativeRoutedResidueResult {
|
|
361
|
-
const
|
|
362
|
-
if (
|
|
420
|
+
const resolved = resolveRegularFile(reference.path);
|
|
421
|
+
if (resolved.kind === "absent") {
|
|
363
422
|
return indeterminate(surface, reference.path, "referenced rollout is absent");
|
|
364
423
|
}
|
|
365
|
-
if (
|
|
424
|
+
if (resolved.kind === "indeterminate") return indeterminate(surface, reference.path, resolved.reason);
|
|
425
|
+
|
|
426
|
+
let handle: number;
|
|
427
|
+
try {
|
|
428
|
+
handle = openSync(resolved.path, "r");
|
|
429
|
+
} catch (error) {
|
|
430
|
+
return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`);
|
|
431
|
+
}
|
|
366
432
|
|
|
367
433
|
let first: Record<string, unknown> | undefined;
|
|
368
434
|
let latest: Record<string, unknown> | undefined;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
435
|
+
let partial = "";
|
|
436
|
+
let totalRead = 0;
|
|
437
|
+
try {
|
|
438
|
+
const opened = fstatSync(handle);
|
|
439
|
+
if (opened.size > MAX_ROLLOUT_INSPECTION_BYTES) {
|
|
440
|
+
return indeterminate(
|
|
441
|
+
surface,
|
|
442
|
+
resolved.path,
|
|
443
|
+
`referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`,
|
|
444
|
+
);
|
|
376
445
|
}
|
|
377
|
-
|
|
378
|
-
|
|
446
|
+
const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
447
|
+
const buffer = Buffer.allocUnsafe(ROLLOUT_READ_CHUNK_BYTES);
|
|
448
|
+
while (totalRead < opened.size) {
|
|
449
|
+
const remaining = Math.min(buffer.length, opened.size - totalRead);
|
|
450
|
+
const count = readSync(handle, buffer, 0, remaining, totalRead);
|
|
451
|
+
if (count === 0) {
|
|
452
|
+
return indeterminate(surface, resolved.path, "rollout read ended before the observed size");
|
|
453
|
+
}
|
|
454
|
+
if (count < 0) {
|
|
455
|
+
return indeterminate(surface, resolved.path, "rollout read ended before the observed size");
|
|
456
|
+
}
|
|
457
|
+
totalRead += count;
|
|
458
|
+
partial += decoder.decode(buffer.subarray(0, count), { stream: true });
|
|
459
|
+
const consumed = consumeRolloutLines(surface, resolved.path, partial, first, latest);
|
|
460
|
+
if (consumed.kind !== "continue") return consumed;
|
|
461
|
+
partial = consumed.partial;
|
|
462
|
+
first = consumed.first;
|
|
463
|
+
latest = consumed.latest;
|
|
379
464
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
|
|
465
|
+
partial += decoder.decode();
|
|
466
|
+
const consumed = consumeRolloutLines(surface, resolved.path, partial, first, latest);
|
|
467
|
+
if (consumed.kind !== "continue") return consumed;
|
|
468
|
+
partial = consumed.partial;
|
|
469
|
+
first = consumed.first;
|
|
470
|
+
latest = consumed.latest;
|
|
471
|
+
if (partial.trim()) {
|
|
472
|
+
const payload = rolloutSessionMetaPayload(partial);
|
|
473
|
+
if (payload.kind === "malformed") {
|
|
474
|
+
return indeterminate(surface, resolved.path, payload.reason);
|
|
475
|
+
}
|
|
476
|
+
if (payload.payload !== null) {
|
|
477
|
+
first ??= payload.payload;
|
|
478
|
+
latest = payload.payload;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const after = fstatSync(handle);
|
|
482
|
+
if (!sameStat(resolved.stat, after)) {
|
|
483
|
+
return indeterminate(surface, resolved.path, "rollout changed while it was being observed");
|
|
484
|
+
}
|
|
485
|
+
const pathAfter = statSync(resolved.path);
|
|
486
|
+
if (!sameStat(resolved.stat, pathAfter)) {
|
|
487
|
+
return indeterminate(surface, resolved.path, "rollout pathname was replaced while it was being observed");
|
|
488
|
+
}
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (errorCode(error) === "ENOENT") {
|
|
491
|
+
return indeterminate(surface, resolved.path, "referenced rollout is absent");
|
|
492
|
+
}
|
|
493
|
+
return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`);
|
|
494
|
+
} finally {
|
|
495
|
+
try {
|
|
496
|
+
closeSync(handle);
|
|
497
|
+
} catch {
|
|
498
|
+
// Closing an already-closed descriptor cannot affect the classification.
|
|
384
499
|
}
|
|
385
|
-
const payload = record.payload as Record<string, unknown>;
|
|
386
|
-
first ??= payload;
|
|
387
|
-
latest = payload;
|
|
388
500
|
}
|
|
389
501
|
|
|
390
502
|
if (!first || !latest) {
|
|
391
|
-
return indeterminate(surface,
|
|
503
|
+
return indeterminate(surface, resolved.path, "referenced rollout has no session_meta metadata");
|
|
392
504
|
}
|
|
505
|
+
let hasOpenCodexProvider = false;
|
|
393
506
|
for (const [position, payload] of [["first", first], ["latest", latest]] as const) {
|
|
394
507
|
if (payload.id !== reference.id) {
|
|
395
|
-
return indeterminate(surface,
|
|
508
|
+
return indeterminate(surface, resolved.path, `${position} session_meta does not identify the referenced thread`);
|
|
396
509
|
}
|
|
397
510
|
if (typeof payload.model_provider !== "string" || !payload.model_provider) {
|
|
398
|
-
return indeterminate(surface,
|
|
399
|
-
}
|
|
400
|
-
if (payload.model_provider === "opencodex") {
|
|
401
|
-
return { kind: "residue", surface, path: read.path };
|
|
511
|
+
return indeterminate(surface, resolved.path, `${position} session_meta has no provider metadata`);
|
|
402
512
|
}
|
|
513
|
+
hasOpenCodexProvider = hasOpenCodexProvider || payload.model_provider === "opencodex";
|
|
403
514
|
}
|
|
404
|
-
return
|
|
515
|
+
return hasOpenCodexProvider
|
|
516
|
+
? { kind: "residue", surface, path: resolved.path }
|
|
517
|
+
: { kind: "clean" };
|
|
405
518
|
}
|
|
406
519
|
|
|
407
520
|
function classifyReferencedRollouts(
|
|
@@ -19,6 +19,11 @@ const MAX_STICKY_LIMIT = 100;
|
|
|
19
19
|
const DEFAULT_STRATEGY: OcxAccountPoolRotationStrategy = "quota";
|
|
20
20
|
const VALID_STRATEGIES = new Set<OcxAccountPoolRotationStrategy>(["quota", "round-robin", "fill-first"]);
|
|
21
21
|
|
|
22
|
+
/** Selection order for an account with no stored preference: one flat tier. */
|
|
23
|
+
export const DEFAULT_ACCOUNT_PRIORITY = 0;
|
|
24
|
+
export const MIN_ACCOUNT_PRIORITY = -100;
|
|
25
|
+
export const MAX_ACCOUNT_PRIORITY = 100;
|
|
26
|
+
|
|
22
27
|
/** Strict parse for management APIs — returns null instead of defaulting. */
|
|
23
28
|
export function parseAccountPoolStrategy(raw: unknown): OcxAccountPoolRotationStrategy | null {
|
|
24
29
|
if (typeof raw === "string" && VALID_STRATEGIES.has(raw as OcxAccountPoolRotationStrategy)) {
|
|
@@ -43,6 +48,71 @@ export function normalizeAccountPoolStickyLimit(raw: unknown): number {
|
|
|
43
48
|
return parseAccountPoolStickyLimit(raw) ?? DEFAULT_STICKY_LIMIT;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
/** Strict parse for management APIs — returns null instead of defaulting. */
|
|
52
|
+
export function parseAccountPriority(raw: unknown): number | null {
|
|
53
|
+
if (
|
|
54
|
+
typeof raw === "number"
|
|
55
|
+
&& Number.isInteger(raw)
|
|
56
|
+
&& raw >= MIN_ACCOUNT_PRIORITY
|
|
57
|
+
&& raw <= MAX_ACCOUNT_PRIORITY
|
|
58
|
+
) {
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeAccountPriority(raw: unknown): number {
|
|
65
|
+
return parseAccountPriority(raw) ?? DEFAULT_ACCOUNT_PRIORITY;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Narrow an already-eligible account list to the highest selection-order tier that
|
|
70
|
+
* still has usable quota. Priority is an *ordering* boundary layered on top of
|
|
71
|
+
* eligibility: it never admits an account the caller already filtered out, and it
|
|
72
|
+
* never keeps the pool on a tier whose every member is drained.
|
|
73
|
+
*
|
|
74
|
+
* Contract (each clause is load-bearing for "no behavior change when unconfigured"):
|
|
75
|
+
* - one distinct priority across `ids` (the unconfigured case) returns `ids` unchanged,
|
|
76
|
+
* so today's pick sequence is preserved byte for byte;
|
|
77
|
+
* - input order is preserved inside the returned tier, which keeps the `__main__`
|
|
78
|
+
* head-of-list bias and the first-index tie-break used by SWRR/lowest-usage;
|
|
79
|
+
* - every tier drained returns `ids` unchanged, reproducing today's
|
|
80
|
+
* stay-put-until-429 behavior rather than inventing a pick;
|
|
81
|
+
* - a `pinnedId` that is present *and* has headroom lowers the ceiling to its own
|
|
82
|
+
* tier, which is what makes a manual "use this now" survive round-robin and
|
|
83
|
+
* fill-first without any mutable selection state. A drained or absent pin is
|
|
84
|
+
* ignored, so the pin expires on its own once the account crosses the threshold.
|
|
85
|
+
*/
|
|
86
|
+
export function selectPriorityTier(
|
|
87
|
+
ids: readonly string[],
|
|
88
|
+
priorityOf: (id: string) => number,
|
|
89
|
+
hasHeadroom: (id: string) => boolean,
|
|
90
|
+
pinnedId?: string,
|
|
91
|
+
): readonly string[] {
|
|
92
|
+
// Readonly out as well as in: the no-change cases return the caller's own array, so a
|
|
93
|
+
// mutating caller would corrupt its input in exactly the cases that must not change.
|
|
94
|
+
const list = ids;
|
|
95
|
+
if (list.length <= 1) return list;
|
|
96
|
+
|
|
97
|
+
const priorities = list.map(priorityOf);
|
|
98
|
+
const firstPriority = priorities[0]!;
|
|
99
|
+
if (priorities.every(priority => priority === firstPriority)) return list;
|
|
100
|
+
|
|
101
|
+
let ceiling = Number.POSITIVE_INFINITY;
|
|
102
|
+
if (pinnedId !== undefined) {
|
|
103
|
+
const pinnedIndex = list.indexOf(pinnedId);
|
|
104
|
+
if (pinnedIndex >= 0 && hasHeadroom(pinnedId)) ceiling = priorities[pinnedIndex]!;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const tiers = [...new Set(priorities)].sort((a, b) => b - a);
|
|
108
|
+
for (const tier of tiers) {
|
|
109
|
+
if (tier > ceiling) continue;
|
|
110
|
+
const members = list.filter((_, index) => priorities[index] === tier);
|
|
111
|
+
if (members.some(hasHeadroom)) return members;
|
|
112
|
+
}
|
|
113
|
+
return list;
|
|
114
|
+
}
|
|
115
|
+
|
|
46
116
|
function getOrCreateState(poolKey: string): SelectionState {
|
|
47
117
|
let state = selectionState.get(poolKey);
|
|
48
118
|
if (!state) {
|
|
@@ -60,7 +130,7 @@ function cloneSelectionState(state: SelectionState): SelectionState {
|
|
|
60
130
|
};
|
|
61
131
|
}
|
|
62
132
|
|
|
63
|
-
function smoothWeightedIndex(ids: string[], state: SelectionState): number {
|
|
133
|
+
function smoothWeightedIndex(ids: readonly string[], state: SelectionState): number {
|
|
64
134
|
let best = -1;
|
|
65
135
|
let bestScore = Number.NEGATIVE_INFINITY;
|
|
66
136
|
let total = 0;
|
|
@@ -87,7 +157,7 @@ function smoothWeightedIndex(ids: string[], state: SelectionState): number {
|
|
|
87
157
|
* either the live map entry or a scratch/clone for dry-run peek.
|
|
88
158
|
*/
|
|
89
159
|
function pickRoundRobinFromState(
|
|
90
|
-
eligibleIds: string[],
|
|
160
|
+
eligibleIds: readonly string[],
|
|
91
161
|
stickyLimit: number,
|
|
92
162
|
state: SelectionState,
|
|
93
163
|
commitSticky: boolean,
|
|
@@ -118,7 +188,7 @@ function pickRoundRobinFromState(
|
|
|
118
188
|
|
|
119
189
|
export function pickRoundRobinAccount(
|
|
120
190
|
poolKey: string,
|
|
121
|
-
eligibleIds: string[],
|
|
191
|
+
eligibleIds: readonly string[],
|
|
122
192
|
stickyLimit: number,
|
|
123
193
|
): string | null {
|
|
124
194
|
return pickRoundRobinFromState(eligibleIds, stickyLimit, getOrCreateState(poolKey), true);
|
|
@@ -130,7 +200,7 @@ export function pickRoundRobinAccount(
|
|
|
130
200
|
*/
|
|
131
201
|
export function peekRoundRobinAccount(
|
|
132
202
|
poolKey: string,
|
|
133
|
-
eligibleIds: string[],
|
|
203
|
+
eligibleIds: readonly string[],
|
|
134
204
|
stickyLimit: number,
|
|
135
205
|
): string | null {
|
|
136
206
|
const live = selectionState.get(poolKey);
|
package/src/codex/refresh.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface CodexCatalogRefreshResult {
|
|
|
12
12
|
catalogWritten: boolean;
|
|
13
13
|
cacheSynced: boolean;
|
|
14
14
|
comboOmissions: ComboCatalogOmission[];
|
|
15
|
+
/** Desired OFF observed under K during the catalog commit; no cache write either. */
|
|
16
|
+
skippedReason?: "desired_disabled";
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
interface RefreshDeps {
|
|
@@ -45,6 +47,11 @@ export async function refreshCodexModelCatalog(
|
|
|
45
47
|
const catalogExists = deps.existsSync(result.path);
|
|
46
48
|
const catalogWritten = result.catalogWritten === true;
|
|
47
49
|
const comboOmissions = result.comboOmissions ?? [];
|
|
50
|
+
if (result.skippedReason === "desired_disabled") {
|
|
51
|
+
// The commit path observed OFF under K. Invalidate nothing: rewriting the
|
|
52
|
+
// models cache here would be exactly the routed-cache write the skip refused.
|
|
53
|
+
return { ...result, catalogExists, catalogWritten: false, cacheSynced: false, comboOmissions };
|
|
54
|
+
}
|
|
48
55
|
if (!catalogExists) {
|
|
49
56
|
return { ...result, catalogExists, catalogWritten: false, cacheSynced: false, comboOmissions };
|
|
50
57
|
}
|