@bridge4dev/runner 0.50.0 → 0.52.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/dist/adapters/codex.js +93 -0
- package/dist/adapters/types.d.ts +9 -2
- package/dist/index.js +6 -0
- package/dist/supervisor.d.ts +11 -1
- package/dist/supervisor.js +59 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/adapters/codex.js
CHANGED
|
@@ -284,6 +284,12 @@ class CodexSession {
|
|
|
284
284
|
sessionId: this.spec.sessionId,
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
|
+
// Before the thread, not after: a model this CLI no longer offers is
|
|
288
|
+
// refused by `thread/start` itself, and the pin is stored — so the same
|
|
289
|
+
// session would fail again on every relaunch (#374).
|
|
290
|
+
await this.dropModelTheCliNoLongerHas();
|
|
291
|
+
if (this.stopped)
|
|
292
|
+
return;
|
|
287
293
|
const thread = await this.openThread();
|
|
288
294
|
if (this.stopped)
|
|
289
295
|
return;
|
|
@@ -1915,6 +1921,93 @@ class CodexSession {
|
|
|
1915
1921
|
};
|
|
1916
1922
|
this.emit({ type: 'capabilities', capabilities });
|
|
1917
1923
|
}
|
|
1924
|
+
/**
|
|
1925
|
+
* A pinned model the installed CLI does not have is dropped before it can
|
|
1926
|
+
* fail the session (#374).
|
|
1927
|
+
*
|
|
1928
|
+
* The pin is chosen from a catalogue that was measured the last time an agent
|
|
1929
|
+
* ran on this machine, and the CLI moves underneath it: codex 0.153.4 gained
|
|
1930
|
+
* `gpt-6-astra` and lost `gpt-5.3-codex-spark`, so a machine that had not run
|
|
1931
|
+
* a session since the update was offering — and storing — a model that no
|
|
1932
|
+
* longer exists. `turn/start` does not warn about that, it refuses, and since
|
|
1933
|
+
* the pin is persisted the refusal repeats on every relaunch.
|
|
1934
|
+
*
|
|
1935
|
+
* Asked of `model/list` rather than recognised in an error message: matching
|
|
1936
|
+
* on text is what the retry rules were explicitly moved away from in 0.44.1,
|
|
1937
|
+
* because the wording is the vendor's to change.
|
|
1938
|
+
*
|
|
1939
|
+
* A probe that itself fails changes nothing. Diagnosing the start must not be
|
|
1940
|
+
* able to prevent it — an unreachable `model/list` on a CLI that would have
|
|
1941
|
+
* accepted the model would otherwise cost the user their choice.
|
|
1942
|
+
*/
|
|
1943
|
+
async dropModelTheCliNoLongerHas() {
|
|
1944
|
+
const pinned = this.model;
|
|
1945
|
+
if (!pinned)
|
|
1946
|
+
return;
|
|
1947
|
+
let known;
|
|
1948
|
+
try {
|
|
1949
|
+
known = await this.probeModelIds();
|
|
1950
|
+
}
|
|
1951
|
+
catch (error) {
|
|
1952
|
+
log.warn('codex: could not check the pinned model — leaving it alone', {
|
|
1953
|
+
sessionId: this.spec.sessionId,
|
|
1954
|
+
error: describe(error),
|
|
1955
|
+
});
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
// Only a COMPLETE list can convict a model. An empty answer means this build
|
|
1959
|
+
// does not really implement `model/list`; a truncated one means the answer
|
|
1960
|
+
// continues on a page we did not ask for. Both must read as «no verdict»,
|
|
1961
|
+
// or the check invents an absence and takes the user's choice away.
|
|
1962
|
+
if (!known.complete || known.ids.size === 0)
|
|
1963
|
+
return;
|
|
1964
|
+
if (known.ids.has(pinned))
|
|
1965
|
+
return;
|
|
1966
|
+
delete this.model;
|
|
1967
|
+
// Said out loud, because the session is about to run on a different model
|
|
1968
|
+
// than the one that was picked — silently substituting it would be worse
|
|
1969
|
+
// than the failure this replaces.
|
|
1970
|
+
this.notice('warn', `This server's codex no longer offers ${pinned} — starting on the agent's own default instead.`);
|
|
1971
|
+
// Cleared everywhere, not just here: the API stores the pin and hands it
|
|
1972
|
+
// back in the next relaunch descriptor, so a pin dropped only in memory
|
|
1973
|
+
// comes back on the next restart (the same trap as QA-100 MAJOR-5).
|
|
1974
|
+
//
|
|
1975
|
+
// What the column ends up holding is «nothing pinned» only until the thread
|
|
1976
|
+
// opens: the capabilities frame right after it reports `currentModel`, and
|
|
1977
|
+
// with no pin that is the thread's own model. Either way the invariant the
|
|
1978
|
+
// clearing exists for holds — what is stored is a model this CLI has.
|
|
1979
|
+
this.emit({ type: 'settings', model: null });
|
|
1980
|
+
if (this.effort) {
|
|
1981
|
+
// The level was chosen for a model that is not there. Keeping it would
|
|
1982
|
+
// apply «Ultra» to whatever the CLI defaults to, which may not have it.
|
|
1983
|
+
delete this.effort;
|
|
1984
|
+
this.emit({ type: 'settings', effort: null });
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Every model id this CLI knows, for the pin check only — including the ones
|
|
1989
|
+
* `listModels()` filters out of the PICKER.
|
|
1990
|
+
*
|
|
1991
|
+
* `hidden` means «do not offer this», not «this does not exist»: codex ships
|
|
1992
|
+
* internal entries like `codex-auto-review` that way, and a pin is not an
|
|
1993
|
+
* offer. Judging a stored pin by the picker's list would delete a model the
|
|
1994
|
+
* CLI still accepts.
|
|
1995
|
+
*
|
|
1996
|
+
* `complete` is false when the answer is paged, and a paged answer proves
|
|
1997
|
+
* nothing about what is missing.
|
|
1998
|
+
*/
|
|
1999
|
+
async probeModelIds() {
|
|
2000
|
+
const result = asRecord(await this.client.request('model/list', { limit: 50 }, 15_000));
|
|
2001
|
+
const data = Array.isArray(result['data']) ? result['data'] : [];
|
|
2002
|
+
const ids = new Set();
|
|
2003
|
+
for (const entry of data) {
|
|
2004
|
+
const row = asRecord(entry);
|
|
2005
|
+
const id = str(row['id']) ?? str(row['model']);
|
|
2006
|
+
if (id)
|
|
2007
|
+
ids.add(id);
|
|
2008
|
+
}
|
|
2009
|
+
return { ids, complete: !str(result['nextCursor']) };
|
|
2010
|
+
}
|
|
1918
2011
|
async listModels() {
|
|
1919
2012
|
const result = asRecord(await this.client.request('model/list', { limit: 50 }, 15_000));
|
|
1920
2013
|
const data = Array.isArray(result['data']) ? result['data'] : [];
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -406,9 +406,16 @@ export type AgentEvent = {
|
|
|
406
406
|
} | {
|
|
407
407
|
type: 'capabilities';
|
|
408
408
|
capabilities: AgentCapabilities;
|
|
409
|
-
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* `model: null` — the pin is GONE, not merely unchanged: the installed CLI
|
|
412
|
+
* stopped offering it (#374). Spelled like `effort: null` and read the same
|
|
413
|
+
* way by the API, which clears the stored column instead of handing the dead
|
|
414
|
+
* pin back in the next descriptor.
|
|
415
|
+
*/
|
|
416
|
+
| {
|
|
410
417
|
type: 'settings';
|
|
411
|
-
model?: string;
|
|
418
|
+
model?: string | null;
|
|
412
419
|
mode?: AgentMode;
|
|
413
420
|
effort?: string | null;
|
|
414
421
|
} | {
|
package/dist/index.js
CHANGED
|
@@ -465,6 +465,12 @@ function runnerCapabilities(apiUrlOverride) {
|
|
|
465
465
|
? ['verify_start', 'verify_status', 'verify_cancel', 'preview_checkout', 'preview_stop']
|
|
466
466
|
: []),
|
|
467
467
|
...(agentInstallEnabled ? ['agent_install'] : []),
|
|
468
|
+
// «Перемерить версии агентов сейчас» — единственный путь, которым машину
|
|
469
|
+
// можно СПРОСИТЬ про версии; всё остальное она присылает сама и по своему
|
|
470
|
+
// расписанию. Объявлено отдельно от `agent_install` и БЕЗ его условия:
|
|
471
|
+
// это чтение, а не установка, и машину, чей владелец запретил ставить из
|
|
472
|
+
// дашборда, спросить о том, что на ней стоит, по-прежнему можно.
|
|
473
|
+
'agent_versions_refresh',
|
|
468
474
|
],
|
|
469
475
|
};
|
|
470
476
|
}
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -244,7 +244,17 @@ export declare class Supervisor {
|
|
|
244
244
|
agent: string;
|
|
245
245
|
from: string | null;
|
|
246
246
|
to: string;
|
|
247
|
-
}
|
|
247
|
+
},
|
|
248
|
+
/**
|
|
249
|
+
* Force a fresh probe on a path the reason alone does not describe.
|
|
250
|
+
*
|
|
251
|
+
* «Перемерить сейчас» sends `measured` — it IS a plain measurement — but it
|
|
252
|
+
* is also the one caller that must not be answered out of the hour-long
|
|
253
|
+
* cache, because a person pressed a button precisely to get past it.
|
|
254
|
+
*/
|
|
255
|
+
options?: {
|
|
256
|
+
force?: boolean;
|
|
257
|
+
}): Promise<AgentVersionsMeasurement | null>;
|
|
248
258
|
private queueVersionChange;
|
|
249
259
|
/**
|
|
250
260
|
* Р13: the agent of a starting session, moved forward in the background.
|
package/dist/supervisor.js
CHANGED
|
@@ -259,14 +259,22 @@ export class Supervisor {
|
|
|
259
259
|
* missing audit lines are not the problem worth solving.
|
|
260
260
|
*/
|
|
261
261
|
static MAX_PENDING_VERSION_CHANGES = 8;
|
|
262
|
-
async publishAgentVersions(reason, changed
|
|
262
|
+
async publishAgentVersions(reason, changed,
|
|
263
|
+
/**
|
|
264
|
+
* Force a fresh probe on a path the reason alone does not describe.
|
|
265
|
+
*
|
|
266
|
+
* «Перемерить сейчас» sends `measured` — it IS a plain measurement — but it
|
|
267
|
+
* is also the one caller that must not be answered out of the hour-long
|
|
268
|
+
* cache, because a person pressed a button precisely to get past it.
|
|
269
|
+
*/
|
|
270
|
+
options) {
|
|
263
271
|
try {
|
|
264
272
|
const measure = this.opts.measureAgentVersions ?? measureAgentVersions;
|
|
265
273
|
// After an install — ours or the auto one — the cached number is a lie,
|
|
266
274
|
// and a stale `at` with it would be dropped by the API's ordering guard
|
|
267
275
|
// together with the audit line the install owes. Forcing it here means a
|
|
268
276
|
// caller cannot forget to invalidate the cache first.
|
|
269
|
-
const measurement = await measure({ force: reason !== 'measured' });
|
|
277
|
+
const measurement = await measure({ force: options?.force ?? reason !== 'measured' });
|
|
270
278
|
// A plain tick carries an earlier install's news rather than replacing it.
|
|
271
279
|
// The API keys the audit line by the measurement's `at`, so the same
|
|
272
280
|
// change arriving under a later `at` is one line, not two.
|
|
@@ -294,11 +302,15 @@ export class Supervisor {
|
|
|
294
302
|
void this.publishAgentVersions('measured');
|
|
295
303
|
}
|
|
296
304
|
}
|
|
305
|
+
// Handed back so a caller can answer with EXACTLY what was published,
|
|
306
|
+
// rather than measuring a second time and hoping the two agree.
|
|
307
|
+
return measurement;
|
|
297
308
|
}
|
|
298
309
|
catch (error) {
|
|
299
310
|
if (changed && reason !== 'measured')
|
|
300
311
|
this.queueVersionChange({ reason, changed });
|
|
301
312
|
log.warn('supervisor: could not report agent versions', { error: String(error) });
|
|
313
|
+
return null;
|
|
302
314
|
}
|
|
303
315
|
}
|
|
304
316
|
queueVersionChange(entry) {
|
|
@@ -2444,7 +2456,13 @@ export class Supervisor {
|
|
|
2444
2456
|
case 'settings':
|
|
2445
2457
|
if (event.mode)
|
|
2446
2458
|
running.mode = event.mode;
|
|
2447
|
-
|
|
2459
|
+
// null = the agent dropped the pinned MODEL, because the installed CLI
|
|
2460
|
+
// stopped offering it (#374). Dropped from the local record too: this
|
|
2461
|
+
// object is what a relaunch inside the same runner process starts from,
|
|
2462
|
+
// so keeping it would re-pin the model the adapter just refused.
|
|
2463
|
+
if (event.model === null)
|
|
2464
|
+
delete running.model;
|
|
2465
|
+
else if (event.model)
|
|
2448
2466
|
running.model = event.model;
|
|
2449
2467
|
// null = the agent dropped the pinned level (model without it) — the
|
|
2450
2468
|
// API must clear its column, so the value is forwarded as-is.
|
|
@@ -2453,7 +2471,7 @@ export class Supervisor {
|
|
|
2453
2471
|
else if (event.effort)
|
|
2454
2472
|
running.effort = event.effort;
|
|
2455
2473
|
this.sendEvent(running, 'settings', {
|
|
2456
|
-
model: event.model,
|
|
2474
|
+
...(event.model === undefined ? {} : { model: event.model }),
|
|
2457
2475
|
mode: event.mode,
|
|
2458
2476
|
...(event.effort === undefined ? {} : { effort: event.effort }),
|
|
2459
2477
|
});
|
|
@@ -5119,6 +5137,43 @@ export class Supervisor {
|
|
|
5119
5137
|
void this.publishAgentVersions('manual', moved);
|
|
5120
5138
|
return;
|
|
5121
5139
|
}
|
|
5140
|
+
/**
|
|
5141
|
+
* «Перемерить версии агентов прямо сейчас» — за кнопкой «Check again».
|
|
5142
|
+
*
|
|
5143
|
+
* Единственный путь, которым машину можно СПРОСИТЬ про версии. Всё
|
|
5144
|
+
* остальное присылает она сама и по своему расписанию: кадр уходит на
|
|
5145
|
+
* каждом переподключении (из кэша), раз в час по таймеру, и свежий —
|
|
5146
|
+
* только после установки, которую сделал сам DevBridge.
|
|
5147
|
+
*
|
|
5148
|
+
* Отсюда и дыра, которую эта команда закрывает: человек обновил агента
|
|
5149
|
+
* руками через `claude update` на машине, раннеру об этом никто не
|
|
5150
|
+
* сказал, и карточка держала старое число до часа. «Новая сессия
|
|
5151
|
+
* чинила» его побочным эффектом — автообновление шло ставить, честно
|
|
5152
|
+
* мерило перед установкой и сбрасывало кэш, — а на машине с
|
|
5153
|
+
* ВЫКЛЮЧЕННЫМ автообновлением не происходило и этого.
|
|
5154
|
+
*
|
|
5155
|
+
* Меряем один раз и отдаём измеренное в ответе, а не только кадром:
|
|
5156
|
+
* кадр и ответ идут по одному сокету, но обрабатываются на той стороне
|
|
5157
|
+
* независимо, и запрос, который вернулся раньше, чем доехала запись,
|
|
5158
|
+
* оставил бы на экране ровно то устаревшее число, ради которого кнопку
|
|
5159
|
+
* и нажали. Кадр при этом всё равно уходит — у него свой путь с
|
|
5160
|
+
* аудитом, и повторная запись с тем же `at` отбрасывается сторожем
|
|
5161
|
+
* порядка на стороне API.
|
|
5162
|
+
*/
|
|
5163
|
+
case 'agent_versions_refresh': {
|
|
5164
|
+
// Один замер на нажатие, и это структура, а не совпадение: публикация
|
|
5165
|
+
// отдаёт то, что измерила, и ответ собирается из НЕГО. Мерить второй
|
|
5166
|
+
// раз «для ответа» значило бы держаться за кэш, который вызывающий не
|
|
5167
|
+
// видит, — а кэш здесь и есть то, мимо чего нажимают кнопку.
|
|
5168
|
+
const snapshot = await this.publishAgentVersions('measured', undefined, { force: true });
|
|
5169
|
+
if (!snapshot) {
|
|
5170
|
+
return void reply({ ok: false, error: 'Could not measure the agents' });
|
|
5171
|
+
}
|
|
5172
|
+
return void reply({
|
|
5173
|
+
ok: true,
|
|
5174
|
+
result: { at: snapshot.at, reason: 'measured', agents: snapshot.agents },
|
|
5175
|
+
});
|
|
5176
|
+
}
|
|
5122
5177
|
/**
|
|
5123
5178
|
* «Would this file reach the agent, and how big is it» (ticket #192).
|
|
5124
5179
|
*
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.52.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED