devez-vibe 1.9.34 → 1.9.36

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/bin/dvz.exe CHANGED
Binary file
@@ -330,10 +330,40 @@ function catalogEntry(model, defaultResolvedModel) {
330
330
  };
331
331
  }
332
332
 
333
+ function compareVersions(left, right) {
334
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
335
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
336
+ if (difference) return difference;
337
+ }
338
+ return 0;
339
+ }
340
+
341
+ /** Each family's newest model in CLAUDE_MODEL_ORDER first, then older ones by family and version. */
342
+ function sortClaudeModels(models) {
343
+ const ranked = models.map((model) => {
344
+ const [family = "", version = ""] = compactClaudeModelName(model).toLowerCase().split(" ");
345
+ return { model, family, version: version.match(/\d+/g)?.map(Number) ?? [] };
346
+ });
347
+ const newest = new Map();
348
+ for (const { family, version } of ranked) {
349
+ if (!newest.has(family) || compareVersions(version, newest.get(family)) > 0) newest.set(family, version);
350
+ }
351
+ const tier = (entry) => (compareVersions(entry.version, newest.get(entry.family)) === 0 ? 0 : 1);
352
+ const familyOrder = ({ family }) => {
353
+ const index = CLAUDE_MODEL_ORDER.indexOf(family);
354
+ return index < 0 ? CLAUDE_MODEL_ORDER.length : index;
355
+ };
356
+ return ranked
357
+ .sort((left, right) => tier(left) - tier(right)
358
+ || familyOrder(left) - familyOrder(right)
359
+ || compareVersions(right.version, left.version))
360
+ .map(({ model }) => model);
361
+ }
362
+
333
363
  function claudeCatalogEntries(models, defaultResolvedModel) {
334
- const catalogModels = models.filter((model) => model.value && model.value !== "default"
364
+ const catalogModels = sortClaudeModels(models.filter((model) => model.value && model.value !== "default"
335
365
  && ![model.value, model.resolvedModel]
336
- .some((name) => stripClaudeModel(String(name || "")).startsWith(RETIRED_OPUS_MODEL)));
366
+ .some((name) => stripClaudeModel(String(name || "")).startsWith(RETIRED_OPUS_MODEL))));
337
367
  const entries = catalogModels.map((model) => catalogEntry(model, defaultResolvedModel));
338
368
  const existingIndex = entries.findIndex((entry) =>
339
369
  stripClaudeModel(entry.model) === PREVIOUS_OPUS_MODEL
@@ -401,14 +431,6 @@ async function loadModelCatalog(params) {
401
431
  const defaultResolvedModel = String(
402
432
  models.find((model) => model.value === "default")?.resolvedModel || "",
403
433
  );
404
- models.sort((left, right) => {
405
- const leftFamily = String(left.value || "").match(/(fable|opus|sonnet|haiku)/i)?.[1]?.toLowerCase();
406
- const rightFamily = String(right.value || "").match(/(fable|opus|sonnet|haiku)/i)?.[1]?.toLowerCase();
407
- const leftOrder = CLAUDE_MODEL_ORDER.indexOf(leftFamily);
408
- const rightOrder = CLAUDE_MODEL_ORDER.indexOf(rightFamily);
409
- return (leftOrder < 0 ? CLAUDE_MODEL_ORDER.length : leftOrder)
410
- - (rightOrder < 0 ? CLAUDE_MODEL_ORDER.length : rightOrder);
411
- });
412
434
  return {
413
435
  data: claudeCatalogEntries(models, defaultResolvedModel),
414
436
  };
@@ -1463,11 +1485,41 @@ async function createSession(params, resumeId) {
1463
1485
  session.model = visibleModel(capabilities.value);
1464
1486
  }
1465
1487
  session.effort = supportedEffort(capabilities, params.effort);
1488
+ watchMcpStartup(session);
1466
1489
  const account = initialization.account || await safeAccount(agentQuery);
1467
1490
  const usage = await safeUsage(agentQuery);
1468
1491
  return { session, initialization, account, usage };
1469
1492
  }
1470
1493
 
1494
+ // Claude connects MCP servers in the background and reports a failure only
1495
+ // through mcpServerStatus(). Once nothing is pending, each failed or signed-out
1496
+ // server goes to the host as Codex's startup notification, so both providers
1497
+ // warn the same way. The first look waits a second so the host already knows
1498
+ // this thread when the notice arrives.
1499
+ async function watchMcpStartup(session, attempts = 30, delay = 1000) {
1500
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1501
+ await new Promise((resolve) => setTimeout(resolve, delay));
1502
+ if (sessions.get(session.id) !== session) return;
1503
+ let statuses;
1504
+ try { statuses = await session.query.mcpServerStatus(); } catch { return; }
1505
+ if (!Array.isArray(statuses)) return;
1506
+ if (statuses.some((server) => server?.status === "pending")) continue;
1507
+ for (const server of statuses) {
1508
+ if (server?.status !== "failed" && server?.status !== "needs-auth") continue;
1509
+ const source = server.source || server.scope;
1510
+ notify("mcpServer/startupStatus/updated", {
1511
+ threadId: session.id,
1512
+ name: String(server.name || "MCP"),
1513
+ status: "failed",
1514
+ ...(server.status === "needs-auth" ? { failureReason: "authenticationRequired" } : {}),
1515
+ ...(server.error ? { error: String(server.error) } : {}),
1516
+ ...(source ? { source: String(source) } : {}),
1517
+ });
1518
+ }
1519
+ return;
1520
+ }
1521
+ }
1522
+
1471
1523
  async function safeAccount(agentQuery) {
1472
1524
  try { return await agentQuery.accountInfo(); } catch { return null; }
1473
1525
  }
@@ -4230,6 +4282,39 @@ async function runSelfTest() {
4230
4282
  if (reconnected.join(",") !== "connected,disabled") {
4231
4283
  throw new Error(`Claude MCP reconnect self-test failed: ${reconnected.join(",")}`);
4232
4284
  }
4285
+ // Startup failures wait out pending servers, then go out once each.
4286
+ const startupPolls = [
4287
+ [{ name: "slow", status: "pending" }],
4288
+ [
4289
+ { name: "ok", status: "connected" },
4290
+ { name: "broken", status: "failed", error: "HTTP 401: token_expired" },
4291
+ { name: "claude.ai Notion", status: "needs-auth", scope: "claudeai" },
4292
+ ],
4293
+ ];
4294
+ const startupSession = {
4295
+ id: "mcp-startup-self-test",
4296
+ query: { mcpServerStatus: async () => startupPolls.shift() || [] },
4297
+ };
4298
+ sessions.set(startupSession.id, startupSession);
4299
+ const startupNotices = [];
4300
+ const startupWrite = process.stdout.write;
4301
+ process.stdout.write = (chunk) => { startupNotices.push(JSON.parse(String(chunk))); return true; };
4302
+ try {
4303
+ await watchMcpStartup(startupSession, 3, 0);
4304
+ } finally {
4305
+ process.stdout.write = startupWrite;
4306
+ sessions.delete(startupSession.id);
4307
+ }
4308
+ const startupNames = startupNotices.map((notice) => notice.params?.name).join(",");
4309
+ if (startupNames !== "broken,claude.ai Notion"
4310
+ || startupNotices.some((notice) => notice.method !== "mcpServer/startupStatus/updated"
4311
+ || notice.params?.threadId !== startupSession.id
4312
+ || notice.params?.status !== "failed")
4313
+ || startupNotices[0].params.error !== "HTTP 401: token_expired"
4314
+ || startupNotices[1].params.failureReason !== "authenticationRequired"
4315
+ || startupNotices[1].params.source !== "claudeai") {
4316
+ throw new Error(`Claude MCP startup self-test failed: ${JSON.stringify(startupNotices)}`);
4317
+ }
4233
4318
  const user = (uuid, text) => ({
4234
4319
  type: "user",
4235
4320
  uuid,
@@ -4575,6 +4660,19 @@ async function runSelfTest() {
4575
4660
  ) !== "max") {
4576
4661
  throw new Error(`Claude pinned model self-test failed: ${JSON.stringify(catalog)}`);
4577
4662
  }
4663
+ const orderedNames = claudeCatalogEntries([
4664
+ { value: "haiku", displayName: "Haiku 4.5" },
4665
+ { value: "claude-sonnet-4-6", displayName: "Sonnet 4.6" },
4666
+ { value: "fable", displayName: "Fable 5" },
4667
+ { value: "opus", resolvedModel: "claude-opus-5-5", displayName: "Opus" },
4668
+ { value: "sonnet", displayName: "Sonnet 5" },
4669
+ { value: "claude-opus-6", displayName: "Opus 6" },
4670
+ { value: "claude-fable-5-1", displayName: "Fable 5.1" },
4671
+ { value: "nova", displayName: "Nova 1" },
4672
+ ].map((model) => ({ resolvedModel: model.value, ...model })), "").filter((entry) => !entry.hidden).map((entry) => entry.displayName).join(",");
4673
+ if (orderedNames !== "Fable 5.1,Opus 6,Sonnet 5,Haiku 4.5,Nova 1,Fable 5,Opus 5.5,Sonnet 4.6") {
4674
+ throw new Error(`Claude model order self-test failed: ${orderedNames}`);
4675
+ }
4578
4676
  const inheritedFork = forkSessionSelection(
4579
4677
  { model: "claude:sonnet", effort: "high" },
4580
4678
  { model: "claude:opus", effort: "max" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.9.34",
3
+ "version": "1.9.36",
4
4
  "type": "module",
5
5
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
6
6
  "keywords": [