larkway 0.3.50 → 0.3.51

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 CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  You @ the bot in a Feishu thread. It runs on your machine — reading your real codebase, executing commands, opening MRs — and posts the result back. You define what the agent knows and what it can do. Larkway just carries the messages.
10
10
 
11
- **Current release: v0.3.50**
11
+ **Current release: v0.3.51**
12
12
 
13
13
  ---
14
14
 
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  你在飞书话题里 @ bot,它在你的机器上运行——读真实代码库、执行命令、开 MR——把结果贴回飞书。你定义 agent 知道什么、能做什么。Larkway 只负责传递消息。
10
10
 
11
- **当前版本:v0.3.50**
11
+ **当前版本:v0.3.51**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -132784,6 +132784,68 @@ async function isCodexReady() {
132784
132784
  if (!await detectCodexLogin()) return false;
132785
132785
  return (await detectCodexRuntimeWritable()).ok;
132786
132786
  }
132787
+ function parseCodexConfigDefaults(raw) {
132788
+ const topLevel = raw.split(/^\[/m)[0] ?? "";
132789
+ const model = /^\s*model\s*=\s*"([^"]+)"/m.exec(topLevel)?.[1];
132790
+ const effort = /^\s*model_reasoning_effort\s*=\s*"([^"]+)"/m.exec(topLevel)?.[1];
132791
+ return {
132792
+ ...model ? { model } : {},
132793
+ ...effort ? { effort } : {}
132794
+ };
132795
+ }
132796
+ function parseCodexModelsCache(raw) {
132797
+ let parsed;
132798
+ try {
132799
+ parsed = JSON.parse(raw);
132800
+ } catch {
132801
+ return [];
132802
+ }
132803
+ const models = parsed?.models;
132804
+ if (!Array.isArray(models)) return [];
132805
+ const out = [];
132806
+ for (const m of models) {
132807
+ const slug = m?.slug;
132808
+ if (typeof slug !== "string" || slug === "") continue;
132809
+ const desc = m?.description;
132810
+ const d = typeof desc === "string" ? Array.from(desc).slice(0, 60).join("") : "";
132811
+ out.push({ v: slug, d });
132812
+ if (out.length >= 8) break;
132813
+ }
132814
+ return out;
132815
+ }
132816
+ async function readBackendModelFacts() {
132817
+ const home = process.env.HOME ?? "";
132818
+ let codexDefault;
132819
+ try {
132820
+ codexDefault = parseCodexConfigDefaults(
132821
+ await readFile13(path24.join(home, ".codex", "config.toml"), "utf8")
132822
+ );
132823
+ if (!codexDefault.model && !codexDefault.effort) codexDefault = void 0;
132824
+ } catch {
132825
+ }
132826
+ let codexVisible;
132827
+ try {
132828
+ const list = parseCodexModelsCache(
132829
+ await readFile13(path24.join(home, ".codex", "models_cache.json"), "utf8")
132830
+ );
132831
+ if (list.length > 0) codexVisible = list;
132832
+ } catch {
132833
+ }
132834
+ let claudeDefault;
132835
+ try {
132836
+ const settings = JSON.parse(
132837
+ await readFile13(path24.join(home, ".claude", "settings.json"), "utf8")
132838
+ );
132839
+ if (typeof settings.model === "string" && settings.model !== "") {
132840
+ claudeDefault = { model: settings.model };
132841
+ }
132842
+ } catch {
132843
+ }
132844
+ return {
132845
+ codex: { globalDefault: codexDefault, visibleModels: codexVisible },
132846
+ claude: { globalDefault: claudeDefault }
132847
+ };
132848
+ }
132787
132849
  var getBackends = async (_req) => {
132788
132850
  const codexReady = await isCodexReady();
132789
132851
  const ready = {
@@ -132791,9 +132853,14 @@ var getBackends = async (_req) => {
132791
132853
  // claude is always assumed ready (checked separately by doctor)
132792
132854
  codex: codexReady
132793
132855
  };
132856
+ const modelFacts = await readBackendModelFacts();
132794
132857
  const backends = BACKEND_ORDER.map((id) => ({
132795
132858
  ...BACKEND_META[id] ?? { id, name: id, short: id, vendor: "\u7B2C\u4E09\u65B9\u5E95\u5EA7", mono: id.slice(0, 2).toUpperCase() },
132796
- ready: ready[id] ?? false
132859
+ ready: ready[id] ?? false,
132860
+ ...id === "codex" ? {
132861
+ globalDefault: modelFacts.codex.globalDefault,
132862
+ visibleModels: modelFacts.codex.visibleModels
132863
+ } : id === "claude" ? { globalDefault: modelFacts.claude.globalDefault } : {}
132797
132864
  }));
132798
132865
  return { status: 200, json: { backends } };
132799
132866
  };
@@ -701,13 +701,28 @@ function lkBackendChipHTML(backendId, opts = {}) {
701
701
  */
702
702
  let _backendReady = {};
703
703
 
704
- /** 拉一次 /api/backends 并更新 _backendReady,供 select 渲染前调用。 */
704
+ /**
705
+ * 各底座的模型事实(同样来自 GET /api/backends):
706
+ * globalDefault — 底座全局配置的实际生效值 {model, effort},「底座默认」
707
+ * 项用它把盲盒渲染成「当前 = <model> / <effort>」;
708
+ * visibleModels — 本机可见模型 [{v,d}](codex 来自 ~/.codex/models_cache.json),
709
+ * 建议列表优先用它,硬编码列表只作兜底。
710
+ * @type {Record<string, {globalDefault?:{model?:string,effort?:string}, visibleModels?:Array<{v:string,d:string}>}>}
711
+ */
712
+ let _backendModelFacts = {};
713
+
714
+ /** 拉一次 /api/backends 并更新 _backendReady/_backendModelFacts,供 select 渲染前调用。 */
705
715
  async function loadBackends() {
706
716
  try {
707
717
  const res = await api("GET", "/api/backends");
708
718
  if (res.ok && Array.isArray(res.json?.backends)) {
709
719
  for (const b of res.json.backends) {
710
- if (typeof b.id === "string") _backendReady[b.id] = !!b.ready;
720
+ if (typeof b.id !== "string") continue;
721
+ _backendReady[b.id] = !!b.ready;
722
+ _backendModelFacts[b.id] = {
723
+ globalDefault: b.globalDefault && typeof b.globalDefault === "object" ? b.globalDefault : undefined,
724
+ visibleModels: Array.isArray(b.visibleModels) ? b.visibleModels : undefined,
725
+ };
711
726
  }
712
727
  }
713
728
  } catch { /* 失败静默;ready 默认 true */ }
@@ -814,11 +829,20 @@ function lkHexA(hex, alpha) {
814
829
  */
815
830
  function modelSuggestionsForBackend(backendId) {
816
831
  if (backendId === "codex") {
817
- return [
818
- { v: "gpt-5.5", d: "日常首选 · 能力与速度均衡" },
819
- { v: "gpt-5.4-mini", d: "便宜快 · 轻量任务" },
820
- { v: "gpt-5.3-codex", d: "代码特化 · 重构与评审" },
821
- ];
832
+ // 中文定位描述覆盖已知官方模型;本机 cache 里其余模型用它自带的英文描述。
833
+ const curated = {
834
+ "gpt-5.5": "日常首选 · 能力与速度均衡",
835
+ "gpt-5.4-mini": "便宜快 · 轻量任务",
836
+ "gpt-5.3-codex": "代码特化 · 重构与评审",
837
+ };
838
+ // 优先用本机真实可见模型(~/.codex/models_cache.json,经 /api/backends);
839
+ // 拿不到(cache 缺失/接口失败)才落回硬编码。内部代理的私有模型名不进
840
+ // 硬编码 —— 它要么出现在这台机器的 cache 里,要么走自定义输入逃生口。
841
+ const live = _backendModelFacts.codex?.visibleModels;
842
+ if (Array.isArray(live) && live.length > 0) {
843
+ return live.map((m) => ({ v: m.v, d: curated[m.v] ?? m.d ?? "" }));
844
+ }
845
+ return Object.entries(curated).map(([v, d]) => ({ v, d }));
822
846
  }
823
847
  return [
824
848
  { v: "claude-sonnet-5", d: "Sonnet 5 · 快/省" },
@@ -850,7 +874,17 @@ function effortLevelsForBackend(backendId) {
850
874
 
851
875
  /** model 值 → 定位描述;空值/自定义值给对应措辞。 */
852
876
  function modelDescFor(backendId, val) {
853
- if (!val) return "不覆盖 —— 跟随底座全局设置";
877
+ if (!val) {
878
+ // 「底座默认」不做盲盒:能读到底座全局配置的实际生效值就直接亮出来
879
+ // (2026-07-15 教训:服务器全局默认改了模型,看板却只显示"跟随全局
880
+ // 设置",跟建议列表首项一叠加,读起来像配置被改回去了)。
881
+ const gd = _backendModelFacts[backendId]?.globalDefault;
882
+ if (gd && (gd.model || gd.effort)) {
883
+ const parts = [gd.model, gd.effort].filter(Boolean).join(" / ");
884
+ return `不覆盖 —— 当前底座全局 = ${parts}`;
885
+ }
886
+ return "不覆盖 —— 跟随底座全局设置";
887
+ }
854
888
  const hit = modelSuggestionsForBackend(backendId).find((m) => m.v === val);
855
889
  return hit ? hit.d : "自定义模型名";
856
890
  }
@@ -874,7 +908,7 @@ function modelMenuHTML(backendId, currentVal) {
874
908
  (v === (currentVal || "") ? ICONS.check : "") +
875
909
  `</div>`;
876
910
  return (
877
- item("", "不覆盖 —— 跟随底座全局设置") +
911
+ item("", modelDescFor(backendId, "")) +
878
912
  modelSuggestionsForBackend(backendId).map((m) => item(m.v, m.d)).join("") +
879
913
  `<div class="bk2-mi-custom" role="option" data-model-custom>${ICONS.edit}输入自定义模型名…</div>`
880
914
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.50",
3
+ "version": "0.3.51",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",