@morehao/dsh-github-trending 0.1.2 → 0.2.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.
@@ -10,9 +10,12 @@ __export(config_exports, {
10
10
  CORE_PROVIDERS: () => CORE_PROVIDERS,
11
11
  DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
12
12
  PROVIDER_BASE_URLS: () => PROVIDER_BASE_URLS,
13
+ SETTINGS_SECTIONS: () => SETTINGS_SECTIONS,
13
14
  effectiveBaseURL: () => effectiveBaseURL,
14
15
  isValidTimezone: () => isValidTimezone,
16
+ mergeSettingsSection: () => mergeSettingsSection,
15
17
  normalizeSettings: () => normalizeSettings,
18
+ sectionOfField: () => sectionOfField,
16
19
  toCoreConfig: () => toCoreConfig,
17
20
  toYaml: () => toYaml,
18
21
  validateSettings: () => validateSettings
@@ -44,7 +47,7 @@ var DEFAULT_SETTINGS = {
44
47
  autoMerge: true,
45
48
  labels: []
46
49
  },
47
- github: { authMode: "device", tokenRef: "GITHUB_TOKEN" },
50
+ github: { authMode: "gh", tokenRef: "GITHUB_TOKEN" },
48
51
  trending: { since: "daily", languages: ["all", "typescript", "python", "go", "rust"], topN: 25 },
49
52
  output: { dir: "trending" },
50
53
  schedule: { enabled: true, time: "08:30:00", timezone: "Asia/Shanghai" },
@@ -57,6 +60,43 @@ var PROVIDER_BASE_URLS = {
57
60
  anthropic: ""
58
61
  };
59
62
  var CORE_PROVIDERS = ["deepseek", "openai-compatible"];
63
+ var SETTINGS_SECTIONS = ["source", "model", "repo", "auth", "scope", "schedule"];
64
+ function sectionOfField(field) {
65
+ if (field.startsWith("model.")) return "model";
66
+ if (field.startsWith("repo.") || field.startsWith("output.") || field.startsWith("pr.")) return "repo";
67
+ if (field.startsWith("github.")) return "auth";
68
+ if (field.startsWith("trending.")) return "scope";
69
+ if (field.startsWith("schedule.")) return "schedule";
70
+ if (field === "corePath" || field === "coreAutoDownload") return "source";
71
+ return void 0;
72
+ }
73
+ function mergeSettingsSection(stored, incoming, section) {
74
+ const next = JSON.parse(JSON.stringify(stored));
75
+ switch (section) {
76
+ case "source":
77
+ next.corePath = incoming.corePath;
78
+ next.coreAutoDownload = incoming.coreAutoDownload;
79
+ break;
80
+ case "model":
81
+ next.model = incoming.model;
82
+ break;
83
+ case "repo":
84
+ next.repo = incoming.repo;
85
+ next.pr = incoming.pr;
86
+ next.output = incoming.output;
87
+ break;
88
+ case "auth":
89
+ next.github = incoming.github;
90
+ break;
91
+ case "scope":
92
+ next.trending = incoming.trending;
93
+ break;
94
+ case "schedule":
95
+ next.schedule = incoming.schedule;
96
+ break;
97
+ }
98
+ return next;
99
+ }
60
100
  function str(value, fallback) {
61
101
  return typeof value === "string" ? value : fallback;
62
102
  }
@@ -110,7 +150,8 @@ function normalizeSettings(input) {
110
150
  labels: strArray(pr.labels, DEFAULT_SETTINGS.pr.labels)
111
151
  },
112
152
  github: {
113
- authMode: authMode === "pat" || authMode === "gh" ? authMode : "device",
153
+ // 旧版本可能存 'device':它没有对应实现,统一迁移到真正可用的「本机 gh」。
154
+ authMode: authMode === "pat" ? "pat" : "gh",
114
155
  tokenRef: str(github.tokenRef, DEFAULT_SETTINGS.github.tokenRef).trim() || DEFAULT_SETTINGS.github.tokenRef
115
156
  },
116
157
  trending: {
@@ -155,11 +196,17 @@ function validateSettings(settings, providers = CORE_PROVIDERS) {
155
196
  if (model.model.trim() === "") {
156
197
  problems.push({ field: "model.model", code: "CONFIG_INVALID", message: "\u5FC5\u987B\u586B\u6A21\u578B ID\uFF08\u63D2\u4EF6\u4E0D\u8BBE\u9690\u5F0F\u9ED8\u8BA4\u6A21\u578B\uFF09" });
157
198
  }
158
- const baseURL = model.baseURL || PROVIDER_BASE_URLS[model.provider] || "";
159
- if (baseURL === "") {
160
- problems.push({ field: "model.baseURL", code: "CONFIG_INVALID", message: "\u8BE5 provider \u9700\u8981\u663E\u5F0F baseURL" });
161
- } else if (!/^https?:\/\//.test(baseURL)) {
162
- problems.push({ field: "model.baseURL", code: "CONFIG_INVALID", message: "baseURL \u5FC5\u987B\u662F http(s) \u5730\u5740" });
199
+ if (model.mode === "proxy") {
200
+ if (model.baseURL !== "" && !/^https?:\/\//.test(model.baseURL)) {
201
+ problems.push({ field: "model.baseURL", code: "CONFIG_INVALID", message: "baseURL \u5FC5\u987B\u662F http(s) \u5730\u5740" });
202
+ }
203
+ } else {
204
+ const baseURL = model.baseURL || PROVIDER_BASE_URLS[model.provider] || "";
205
+ if (baseURL === "") {
206
+ problems.push({ field: "model.baseURL", code: "CONFIG_INVALID", message: "\u8BE5 provider \u9700\u8981\u663E\u5F0F baseURL" });
207
+ } else if (!/^https?:\/\//.test(baseURL)) {
208
+ problems.push({ field: "model.baseURL", code: "CONFIG_INVALID", message: "baseURL \u5FC5\u987B\u662F http(s) \u5730\u5740" });
209
+ }
163
210
  }
164
211
  if (model.temperature < 0 || model.temperature > 2) {
165
212
  problems.push({ field: "model.temperature", code: "CONFIG_INVALID", message: "temperature \u5FC5\u987B\u5728 0\u20132 \u4E4B\u95F4" });
@@ -336,7 +383,7 @@ var Store = class {
336
383
  return path3.join(this.dir, HISTORY_FILE);
337
384
  }
338
385
  get logsPath() {
339
- return runsDir(this.env);
386
+ return path3.join(this.dir, "runs");
340
387
  }
341
388
  readSettings() {
342
389
  return normalizeSettings(readJson(this.settingsPath, {}));
@@ -458,7 +505,10 @@ function areaOf(code) {
458
505
  if (code === "GITHUB_AUTH") return "auth";
459
506
  return "run";
460
507
  }
461
- function hintOf(code) {
508
+ function hintOf(code, message) {
509
+ if (code === "GIT_OPERATION_FAILED" && typeof message === "string" && /non-fast-forward|\[rejected\]|failed to push|fetch first/i.test(message)) {
510
+ return "\u63A8\u9001\u88AB\u62D2\uFF08non-fast-forward\uFF09\uFF1A\u8FDC\u7AEF\u5DF2\u6709\u540C\u540D\u5206\u652F\u4E14\u5386\u53F2\u5206\u53C9\u3002\u5148\u5408\u5E76/\u5173\u95ED\u5E76\u5220\u9664\u8BE5\u8FDC\u7AEF\u5206\u652F\uFF0C\u6216\u8BA9\u6838\u5FC3\u4EE5 --force-with-lease \u63A8\u9001\u540E\u91CD\u8BD5\u3002";
511
+ }
462
512
  switch (code) {
463
513
  case "CORE_UNAVAILABLE":
464
514
  return "\u6838\u5FC3\u4E8C\u8FDB\u5236\u6CA1\u627E\u5230\uFF1A\u786E\u8BA4\u63D2\u4EF6\u5B89\u88C5\u5B8C\u6574\uFF08\u542B\u5E73\u53F0\u5305\uFF09\uFF0C\u6216\u7528 TRENDING_DIGEST_CORE \u6307\u5411\u4E00\u4E2A\u53EF\u6267\u884C\u7684 trendradar\u3002";
@@ -547,7 +597,8 @@ function classifyStderr(stderr) {
547
597
  const match = explicit.exec(line);
548
598
  if (match !== null && CORE_CODES.has(match[1] ?? "")) {
549
599
  const code = match[1];
550
- return { code, message: (match[2] ?? "").trim() || line, retriable: isRetriable(code), inferred: false };
600
+ const detail = [(match[2] ?? "").trim(), ...lines.slice(index + 1)].filter((part) => part !== "").join("\n").slice(0, 2e3);
601
+ return { code, message: detail || line, retriable: isRetriable(code), inferred: false };
551
602
  }
552
603
  }
553
604
  const message = lines[lines.length - 1] ?? "\u6838\u5FC3\u672A\u8F93\u51FA\u9519\u8BEF\u4FE1\u606F";
@@ -1474,7 +1525,7 @@ async function resolveRef(services, ref, env = process.env) {
1474
1525
  async function resolveGithubToken(services, authMode, tokenRef, deps = {}) {
1475
1526
  const env = deps.env ?? process.env;
1476
1527
  if (authMode === "gh") {
1477
- const execGh = deps.execGh ?? defaultGhToken;
1528
+ const execGh = deps.execGh ?? (() => defaultGhToken(deps.freshGh === true));
1478
1529
  try {
1479
1530
  const token = await execGh();
1480
1531
  if (token !== void 0 && token !== "") return { token, source: "gh" };
@@ -1485,22 +1536,30 @@ async function resolveGithubToken(services, authMode, tokenRef, deps = {}) {
1485
1536
  if (resolved.value !== void 0) return { token: resolved.value, source: resolved.source ?? "credentials" };
1486
1537
  return { source: "none" };
1487
1538
  }
1488
- async function defaultGhToken() {
1539
+ var ghTokenCache;
1540
+ var GH_TOKEN_TTL_MS = 3e4;
1541
+ async function defaultGhToken(fresh = false) {
1542
+ if (!fresh && ghTokenCache !== void 0 && Date.now() - ghTokenCache.at < GH_TOKEN_TTL_MS) {
1543
+ return ghTokenCache.value;
1544
+ }
1489
1545
  const { execCapture: execCapture2 } = await import("./exec-33EQXGUA.js");
1490
- const result = await execCapture2("gh", ["auth", "token"], { timeoutMs: 5e3 });
1491
- if (result.code !== 0) return void 0;
1492
- const token = result.stdout.trim();
1493
- return token === "" ? void 0 : token;
1546
+ const result = await execCapture2("gh", ["auth", "token"], { timeoutMs: 2e3 });
1547
+ const token = result.code === 0 ? result.stdout.trim() : "";
1548
+ const value = token === "" ? void 0 : token;
1549
+ ghTokenCache = { value, at: Date.now() };
1550
+ return value;
1494
1551
  }
1495
1552
 
1496
1553
  // src/host/quickfill.ts
1497
1554
  var quickfill_exports = {};
1498
1555
  __export(quickfill_exports, {
1499
1556
  applyCandidate: () => applyCandidate,
1557
+ applyDefaultSelection: () => applyDefaultSelection,
1500
1558
  coreProviderFor: () => coreProviderFor,
1501
1559
  credentialServices: () => credentialServices,
1502
1560
  listProviderCandidates: () => listProviderCandidates,
1503
1561
  llmServices: () => llmServices,
1562
+ readDefaultSelection: () => readDefaultSelection,
1504
1563
  resolveModelKey: () => resolveModelKey
1505
1564
  });
1506
1565
  function llmServices(get) {
@@ -1531,7 +1590,6 @@ async function listProviderCandidates(options) {
1531
1590
  const declared = new Map(configurable.map((entry) => [entry.provider, entry]));
1532
1591
  const ids = [];
1533
1592
  for (const provider of live) if (!ids.includes(provider.id)) ids.push(provider.id);
1534
- for (const entry of configurable) if (!ids.includes(entry.provider)) ids.push(entry.provider);
1535
1593
  const candidates = [];
1536
1594
  for (const id of ids) {
1537
1595
  const entry = declared.get(id);
@@ -1550,7 +1608,7 @@ async function listProviderCandidates(options) {
1550
1608
  break;
1551
1609
  }
1552
1610
  }
1553
- if (mode !== "direct" && oauth) mode = "proxy";
1611
+ if (mode !== "direct") mode = "proxy";
1554
1612
  const models = await listModelsWithTimeout(llm, id);
1555
1613
  candidates.push({
1556
1614
  id,
@@ -1568,7 +1626,37 @@ async function listProviderCandidates(options) {
1568
1626
  coreProvider: coreProviderFor(id)
1569
1627
  });
1570
1628
  }
1571
- return candidates;
1629
+ return candidates.filter((candidate) => candidate.models.length > 0);
1630
+ }
1631
+ function readDefaultSelection(get) {
1632
+ try {
1633
+ const service = get("agentDefaultModel");
1634
+ const selection = service?.currentSelection?.();
1635
+ if (selection !== void 0 && typeof selection.provider === "string" && selection.provider !== "" && typeof selection.model === "string" && selection.model !== "") {
1636
+ return {
1637
+ provider: selection.provider,
1638
+ model: selection.model,
1639
+ ...typeof selection.reasoningEffort === "string" ? { reasoningEffort: selection.reasoningEffort } : {}
1640
+ };
1641
+ }
1642
+ } catch {
1643
+ }
1644
+ return void 0;
1645
+ }
1646
+ function applyDefaultSelection(settings, selection, candidate) {
1647
+ if (candidate !== void 0) return applyCandidate(settings, candidate, selection.model);
1648
+ const coreProvider = coreProviderFor(selection.provider);
1649
+ return {
1650
+ ...settings,
1651
+ model: {
1652
+ ...settings.model,
1653
+ provider: coreProvider,
1654
+ mode: "proxy",
1655
+ hostProvider: selection.provider,
1656
+ baseURL: "",
1657
+ model: selection.model
1658
+ }
1659
+ };
1572
1660
  }
1573
1661
  function applyCandidate(settings, candidate, modelId) {
1574
1662
  const model = {
@@ -1763,6 +1851,173 @@ function authorized(request, token) {
1763
1851
  return provided.length === expected.length && timingSafeEqual(provided, expected);
1764
1852
  }
1765
1853
 
1854
+ // src/host/schedule.ts
1855
+ var schedule_exports = {};
1856
+ __export(schedule_exports, {
1857
+ Scheduler: () => Scheduler,
1858
+ isDue: () => isDue,
1859
+ nextOccurrence: () => nextOccurrence,
1860
+ parseClock: () => parseClock,
1861
+ zonedDateString: () => zonedDateString,
1862
+ zonedTimeToUtc: () => zonedTimeToUtc
1863
+ });
1864
+ var MINUTE = 6e4;
1865
+ var CATCH_UP_MINUTES = 6 * 60;
1866
+ function partsOf(date, timezone) {
1867
+ const formatter = new Intl.DateTimeFormat("en-US", {
1868
+ timeZone: timezone,
1869
+ hourCycle: "h23",
1870
+ year: "numeric",
1871
+ month: "2-digit",
1872
+ day: "2-digit",
1873
+ hour: "2-digit",
1874
+ minute: "2-digit",
1875
+ second: "2-digit"
1876
+ });
1877
+ const map = /* @__PURE__ */ new Map();
1878
+ for (const part of formatter.formatToParts(date)) {
1879
+ if (part.type !== "literal") map.set(part.type, part.value);
1880
+ }
1881
+ return {
1882
+ year: Number(map.get("year")),
1883
+ month: Number(map.get("month")),
1884
+ day: Number(map.get("day")),
1885
+ hour: Number(map.get("hour")),
1886
+ minute: Number(map.get("minute")),
1887
+ second: Number(map.get("second"))
1888
+ };
1889
+ }
1890
+ function offsetMs(date, timezone) {
1891
+ const parts = partsOf(date, timezone);
1892
+ const asUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
1893
+ return asUtc - Math.floor(date.getTime() / 1e3) * 1e3;
1894
+ }
1895
+ function zonedTimeToUtc(year, month, day, hour, minute, second, timezone) {
1896
+ const guess = Date.UTC(year, month - 1, day, hour, minute, second);
1897
+ const first = new Date(guess - offsetMs(new Date(guess), timezone));
1898
+ return new Date(guess - offsetMs(first, timezone));
1899
+ }
1900
+ function parseClock(time) {
1901
+ const match = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/.exec(time.trim());
1902
+ if (match === null) throw new Error(`\u975E\u6CD5\u65F6\u95F4\uFF1A${time}`);
1903
+ return {
1904
+ hour: Number(match[1]),
1905
+ minute: Number(match[2]),
1906
+ second: Number(match[3] ?? "0")
1907
+ };
1908
+ }
1909
+ function zonedDateString(date, timezone) {
1910
+ const parts = partsOf(date, timezone);
1911
+ return `${String(parts.year).padStart(4, "0")}-${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
1912
+ }
1913
+ function nextOccurrence(now, schedule) {
1914
+ const { hour, minute, second } = parseClock(schedule.time);
1915
+ const today = partsOf(now, schedule.timezone);
1916
+ const candidate = zonedTimeToUtc(today.year, today.month, today.day, hour, minute, second, schedule.timezone);
1917
+ if (candidate.getTime() > now.getTime()) return candidate;
1918
+ const tomorrow = new Date(Date.UTC(today.year, today.month - 1, today.day) + 864e5);
1919
+ return zonedTimeToUtc(
1920
+ tomorrow.getUTCFullYear(),
1921
+ tomorrow.getUTCMonth() + 1,
1922
+ tomorrow.getUTCDate(),
1923
+ hour,
1924
+ minute,
1925
+ second,
1926
+ schedule.timezone
1927
+ );
1928
+ }
1929
+ function isDue(now, schedule) {
1930
+ const { hour, minute } = parseClock(schedule.time);
1931
+ const parts = partsOf(now, schedule.timezone);
1932
+ const nowMinutes = parts.hour * 60 + parts.minute;
1933
+ const targetMinutes = hour * 60 + minute;
1934
+ const late = nowMinutes - targetMinutes;
1935
+ return late >= 0 && late <= CATCH_UP_MINUTES;
1936
+ }
1937
+ var Scheduler = class {
1938
+ deps;
1939
+ timerHandle;
1940
+ stopped = false;
1941
+ lastFiredFor;
1942
+ constructor(deps) {
1943
+ this.deps = deps;
1944
+ }
1945
+ start() {
1946
+ this.stopped = false;
1947
+ const timer = this.deps.getTimer();
1948
+ const tick = () => {
1949
+ void this.tick();
1950
+ };
1951
+ if (timer !== void 0 && typeof timer.interval === "function") {
1952
+ this.timerHandle = timer.interval(tick, MINUTE);
1953
+ } else {
1954
+ const handle = setInterval(tick, MINUTE);
1955
+ handle.unref?.();
1956
+ this.timerHandle = handle;
1957
+ }
1958
+ setTimeout(tick, 5e3).unref?.();
1959
+ }
1960
+ stop() {
1961
+ this.stopped = true;
1962
+ const handle = this.timerHandle;
1963
+ if (typeof handle === "object" && handle !== null && typeof handle.clear === "function") handle.clear();
1964
+ else if (typeof handle === "number" || handle !== void 0) clearInterval(handle);
1965
+ this.timerHandle = void 0;
1966
+ }
1967
+ /** 公开给测试:判断此刻是否该跑,并按需触发一次。 */
1968
+ async tick(now = /* @__PURE__ */ new Date()) {
1969
+ if (this.stopped) return "skipped";
1970
+ const schedule = this.readSchedule();
1971
+ if (!schedule.enabled) return "disabled";
1972
+ if (!isDue(now, schedule)) return "skipped";
1973
+ const today = zonedDateString(now, schedule.timezone);
1974
+ const state = this.deps.store.readState();
1975
+ const lastDate = state.lastRunAt === void 0 ? void 0 : zonedDateString(new Date(state.lastRunAt), schedule.timezone);
1976
+ if (lastDate === today) return "skipped";
1977
+ if (this.lastFiredFor === today) return "skipped";
1978
+ if (this.deps.isRunning()) return "busy";
1979
+ this.lastFiredFor = today;
1980
+ try {
1981
+ await this.deps.run("schedule");
1982
+ return "ran";
1983
+ } catch (error) {
1984
+ this.deps.onError?.(error);
1985
+ return "skipped";
1986
+ }
1987
+ }
1988
+ readSchedule() {
1989
+ const settings = this.deps.store.readSettings();
1990
+ return {
1991
+ enabled: settings.schedule.enabled,
1992
+ time: settings.schedule.time,
1993
+ timezone: settings.schedule.timezone
1994
+ };
1995
+ }
1996
+ /** 下一次运行(禁用时为 undefined)。 */
1997
+ next() {
1998
+ const schedule = this.readSchedule();
1999
+ if (!schedule.enabled) return void 0;
2000
+ try {
2001
+ return nextOccurrence(/* @__PURE__ */ new Date(), schedule);
2002
+ } catch {
2003
+ return void 0;
2004
+ }
2005
+ }
2006
+ describe(state, now = /* @__PURE__ */ new Date()) {
2007
+ const schedule = this.readSchedule();
2008
+ const next = schedule.enabled ? this.next() : void 0;
2009
+ return {
2010
+ enabled: schedule.enabled,
2011
+ time: schedule.time,
2012
+ timezone: schedule.timezone,
2013
+ ...next === void 0 ? {} : { nextRunAt: next.toISOString() },
2014
+ ...state.lastRunAt === void 0 ? {} : { lastRunDate: zonedDateString(new Date(state.lastRunAt), schedule.timezone) },
2015
+ catchUpMinutes: CATCH_UP_MINUTES,
2016
+ source: "timer"
2017
+ };
2018
+ }
2019
+ };
2020
+
1766
2021
  // src/host/runner.ts
1767
2022
  var runner_exports = {};
1768
2023
  __export(runner_exports, {
@@ -1783,6 +2038,13 @@ function repoProblem(repoPath) {
1783
2038
  }
1784
2039
  return void 0;
1785
2040
  }
2041
+ function runDate(settings, now = /* @__PURE__ */ new Date()) {
2042
+ try {
2043
+ return zonedDateString(now, settings.schedule.timezone);
2044
+ } catch {
2045
+ return now.toISOString().slice(0, 10);
2046
+ }
2047
+ }
1786
2048
  var Runner = class {
1787
2049
  active;
1788
2050
  deps;
@@ -1812,12 +2074,13 @@ var Runner = class {
1812
2074
  }
1813
2075
  const repoIssue = repoProblem(settings.repo.path);
1814
2076
  if (repoIssue !== void 0) throw new IpcError(repoIssue.code, repoIssue.message);
2077
+ const dryRun = options.dryRun === true;
2078
+ const date = options.date !== void 0 && options.date !== "" ? options.date : runDate(settings);
1815
2079
  const id = newRunId();
1816
2080
  const controller = new AbortController();
1817
2081
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1818
2082
  this.active = { id, controller, startedAt };
1819
2083
  const logFile = store.newLogPath(id);
1820
- const dryRun = options.dryRun === true;
1821
2084
  const startedMs = Date.now();
1822
2085
  let proxy;
1823
2086
  try {
@@ -1836,6 +2099,7 @@ var Runner = class {
1836
2099
  if (modelKey.mode === "direct" && (modelKey.apiKey === void 0 || modelKey.apiKey === "")) {
1837
2100
  throw new IpcError("MODEL_KEY_MISSING", `\u6CA1\u6709\u89E3\u6790\u5230\u6A21\u578B\u5BC6\u94A5\uFF08\u5F15\u7528 ${settings.model.apiKeyRef}\uFF09`);
1838
2101
  }
2102
+ let apiKey = modelKey.apiKey ?? "";
1839
2103
  let baseURLOverride;
1840
2104
  if (modelKey.mode === "proxy") {
1841
2105
  const llm = this.deps.get("llm");
@@ -1851,6 +2115,7 @@ var Runner = class {
1851
2115
  timeoutMs: 12e4
1852
2116
  });
1853
2117
  baseURLOverride = proxy.url;
2118
+ apiKey = proxy.token;
1854
2119
  store.appendLog(
1855
2120
  logFile,
1856
2121
  `[proxy] \u56DE\u73AF\u4EE3\u7406\u5DF2\u542F\u52A8\uFF1A127.0.0.1:${proxy.port}\uFF08\u4E00\u6B21\u6027 token\uFF0C\u8FD0\u884C\u7ED3\u675F\u5373\u5173\u95ED\uFF09`
@@ -1864,13 +2129,13 @@ var Runner = class {
1864
2129
  core,
1865
2130
  version: handshakeResult.version,
1866
2131
  settings,
1867
- apiKey: modelKey.apiKey ?? "",
2132
+ apiKey,
1868
2133
  ...github.token === void 0 ? {} : { token: github.token },
1869
- ...options.date === void 0 ? {} : { date: options.date },
2134
+ date,
1870
2135
  dryRun,
1871
2136
  ...baseURLOverride === void 0 ? {} : { baseURLOverride },
1872
2137
  signal: controller.signal,
1873
- secrets: [modelKey.apiKey, github.token],
2138
+ secrets: [apiKey, github.token],
1874
2139
  onStderr: (line) => store.appendLog(logFile, line)
1875
2140
  });
1876
2141
  const outcome = result.outcome;
@@ -1878,7 +2143,7 @@ var Runner = class {
1878
2143
  id,
1879
2144
  outcome,
1880
2145
  dryRun,
1881
- ...options.date === void 0 ? {} : { date: options.date },
2146
+ date,
1882
2147
  logFile,
1883
2148
  channel: result.channelKind,
1884
2149
  proxy: proxy !== void 0,
@@ -1893,7 +2158,7 @@ var Runner = class {
1893
2158
  id,
1894
2159
  error: cancelled && !(error instanceof IpcError && error.code === "CORE_CANCELLED") ? new IpcError("CORE_CANCELLED", "\u8FD0\u884C\u5DF2\u53D6\u6D88\uFF1A\u6838\u5FC3\u8FDB\u7A0B\u5DF2\u7EC8\u6B62\uFF0C\u672A\u63D0\u4EA4\u4EFB\u4F55\u72B6\u6001") : error,
1895
2160
  dryRun,
1896
- ...options.date === void 0 ? {} : { date: options.date },
2161
+ date,
1897
2162
  logFile,
1898
2163
  durationMs: Date.now() - startedMs
1899
2164
  });
@@ -1910,14 +2175,17 @@ var Runner = class {
1910
2175
  }
1911
2176
  summarize(input) {
1912
2177
  const { outcome } = input;
1913
- const error = outcome.error === void 0 ? void 0 : {
1914
- code: outcome.error.code,
1915
- message: redact(outcome.error.message),
1916
- hint: hintOf(outcome.error.code),
1917
- area: areaOf(outcome.error.code),
1918
- retriable: outcome.error.retriable,
1919
- inferred: outcome.error.details?.inferred === true
1920
- };
2178
+ const error = outcome.error === void 0 ? void 0 : (() => {
2179
+ const message = redact(outcome.error.message);
2180
+ return {
2181
+ code: outcome.error.code,
2182
+ message,
2183
+ hint: hintOf(outcome.error.code, message),
2184
+ area: areaOf(outcome.error.code),
2185
+ retriable: outcome.error.retriable,
2186
+ inferred: outcome.error.details?.inferred === true
2187
+ };
2188
+ })();
1921
2189
  return {
1922
2190
  id: input.id,
1923
2191
  ok: outcome.ok,
@@ -1951,7 +2219,7 @@ var Runner = class {
1951
2219
  durationMs: input.durationMs ?? 0,
1952
2220
  coreVersion: "",
1953
2221
  files: 0,
1954
- error: { code, message, hint: hintOf(code), area: areaOf(code), retriable: false },
2222
+ error: { code, message, hint: hintOf(code, message), area: areaOf(code), retriable: false },
1955
2223
  warnings: [],
1956
2224
  logFile: input.logFile
1957
2225
  };
@@ -1980,7 +2248,7 @@ var Runner = class {
1980
2248
  const state = store.readState();
1981
2249
  const next = {
1982
2250
  lastRunAt: record.at,
1983
- ...summary.ok && summary.prUrl !== void 0 ? { pendingPr: { url: summary.prUrl, createdAt: record.at } } : {},
2251
+ ...summary.ok && summary.prUrl !== void 0 ? { pendingPr: { url: summary.prUrl, createdAt: record.at, ...summary.date === void 0 ? {} : { date: summary.date } } } : {},
1984
2252
  ...summary.ok && !summary.skipped && summary.contentHash !== void 0 ? {
1985
2253
  lastDigest: {
1986
2254
  date: summary.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
@@ -2000,172 +2268,64 @@ var Runner = class {
2000
2268
  }
2001
2269
  };
2002
2270
 
2003
- // src/host/schedule.ts
2004
- var schedule_exports = {};
2005
- __export(schedule_exports, {
2006
- Scheduler: () => Scheduler,
2007
- isDue: () => isDue,
2008
- nextOccurrence: () => nextOccurrence,
2009
- parseClock: () => parseClock,
2010
- zonedDateString: () => zonedDateString,
2011
- zonedTimeToUtc: () => zonedTimeToUtc
2271
+ // src/host/github.ts
2272
+ var github_exports = {};
2273
+ __export(github_exports, {
2274
+ probeGithubAuth: () => probeGithubAuth
2012
2275
  });
2013
- var MINUTE = 6e4;
2014
- var CATCH_UP_MINUTES = 6 * 60;
2015
- function partsOf(date, timezone) {
2016
- const formatter = new Intl.DateTimeFormat("en-US", {
2017
- timeZone: timezone,
2018
- hourCycle: "h23",
2019
- year: "numeric",
2020
- month: "2-digit",
2021
- day: "2-digit",
2022
- hour: "2-digit",
2023
- minute: "2-digit",
2024
- second: "2-digit"
2276
+ async function probeGithubAuth(options) {
2277
+ const env = options.env ?? process.env;
2278
+ const resolved = await resolveGithubToken(options.credentials, options.authMode, options.tokenRef, {
2279
+ env,
2280
+ freshGh: true
2025
2281
  });
2026
- const map = /* @__PURE__ */ new Map();
2027
- for (const part of formatter.formatToParts(date)) {
2028
- if (part.type !== "literal") map.set(part.type, part.value);
2029
- }
2030
- return {
2031
- year: Number(map.get("year")),
2032
- month: Number(map.get("month")),
2033
- day: Number(map.get("day")),
2034
- hour: Number(map.get("hour")),
2035
- minute: Number(map.get("minute")),
2036
- second: Number(map.get("second"))
2037
- };
2038
- }
2039
- function offsetMs(date, timezone) {
2040
- const parts = partsOf(date, timezone);
2041
- const asUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
2042
- return asUtc - Math.floor(date.getTime() / 1e3) * 1e3;
2043
- }
2044
- function zonedTimeToUtc(year, month, day, hour, minute, second, timezone) {
2045
- const guess = Date.UTC(year, month - 1, day, hour, minute, second);
2046
- const first = new Date(guess - offsetMs(new Date(guess), timezone));
2047
- return new Date(guess - offsetMs(first, timezone));
2048
- }
2049
- function parseClock(time) {
2050
- const match = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/.exec(time.trim());
2051
- if (match === null) throw new Error(`\u975E\u6CD5\u65F6\u95F4\uFF1A${time}`);
2052
- return {
2053
- hour: Number(match[1]),
2054
- minute: Number(match[2]),
2055
- second: Number(match[3] ?? "0")
2056
- };
2057
- }
2058
- function zonedDateString(date, timezone) {
2059
- const parts = partsOf(date, timezone);
2060
- return `${String(parts.year).padStart(4, "0")}-${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
2061
- }
2062
- function nextOccurrence(now, schedule) {
2063
- const { hour, minute, second } = parseClock(schedule.time);
2064
- const today = partsOf(now, schedule.timezone);
2065
- const candidate = zonedTimeToUtc(today.year, today.month, today.day, hour, minute, second, schedule.timezone);
2066
- if (candidate.getTime() > now.getTime()) return candidate;
2067
- const tomorrow = new Date(Date.UTC(today.year, today.month - 1, today.day) + 864e5);
2068
- return zonedTimeToUtc(
2069
- tomorrow.getUTCFullYear(),
2070
- tomorrow.getUTCMonth() + 1,
2071
- tomorrow.getUTCDate(),
2072
- hour,
2073
- minute,
2074
- second,
2075
- schedule.timezone
2076
- );
2077
- }
2078
- function isDue(now, schedule) {
2079
- const { hour, minute } = parseClock(schedule.time);
2080
- const parts = partsOf(now, schedule.timezone);
2081
- const nowMinutes = parts.hour * 60 + parts.minute;
2082
- const targetMinutes = hour * 60 + minute;
2083
- const late = nowMinutes - targetMinutes;
2084
- return late >= 0 && late <= CATCH_UP_MINUTES;
2085
- }
2086
- var Scheduler = class {
2087
- deps;
2088
- timerHandle;
2089
- stopped = false;
2090
- lastFiredFor;
2091
- constructor(deps) {
2092
- this.deps = deps;
2093
- }
2094
- start() {
2095
- this.stopped = false;
2096
- const timer = this.deps.getTimer();
2097
- const tick = () => {
2098
- void this.tick();
2282
+ if (resolved.token === void 0 || resolved.token === "") {
2283
+ return {
2284
+ ok: false,
2285
+ code: "GITHUB_AUTH",
2286
+ source: resolved.source,
2287
+ message: options.authMode === "gh" ? "\u6CA1\u6709\u53D6\u5230\u672C\u673A gh \u7684\u767B\u5F55\u6001\uFF1A\u5148\u5728\u7EC8\u7AEF\u6267\u884C `gh auth login`\u3002" : `\u6CA1\u6709\u53D6\u5230\u51ED\u636E\u300C${options.tokenRef}\u300D\uFF1A\u8BF7\u5728\u73AF\u5883\u53D8\u91CF\u6216\u51ED\u636E\u91CC\u914D\u7F6E\u4E00\u4E2A Personal Access Token\u3002`
2099
2288
  };
2100
- if (timer !== void 0 && typeof timer.interval === "function") {
2101
- this.timerHandle = timer.interval(tick, MINUTE);
2102
- } else {
2103
- const handle = setInterval(tick, MINUTE);
2104
- handle.unref?.();
2105
- this.timerHandle = handle;
2106
- }
2107
- setTimeout(tick, 5e3).unref?.();
2108
- }
2109
- stop() {
2110
- this.stopped = true;
2111
- const handle = this.timerHandle;
2112
- if (typeof handle === "object" && handle !== null && typeof handle.clear === "function") handle.clear();
2113
- else if (typeof handle === "number" || handle !== void 0) clearInterval(handle);
2114
- this.timerHandle = void 0;
2115
2289
  }
2116
- /** 公开给测试:判断此刻是否该跑,并按需触发一次。 */
2117
- async tick(now = /* @__PURE__ */ new Date()) {
2118
- if (this.stopped) return "skipped";
2119
- const schedule = this.readSchedule();
2120
- if (!schedule.enabled) return "disabled";
2121
- if (!isDue(now, schedule)) return "skipped";
2122
- const today = zonedDateString(now, schedule.timezone);
2123
- const state = this.deps.store.readState();
2124
- const lastDate = state.lastRunAt === void 0 ? void 0 : zonedDateString(new Date(state.lastRunAt), schedule.timezone);
2125
- if (lastDate === today) return "skipped";
2126
- if (this.lastFiredFor === today) return "skipped";
2127
- if (this.deps.isRunning()) return "busy";
2128
- this.lastFiredFor = today;
2129
- try {
2130
- await this.deps.run("schedule");
2131
- return "ran";
2132
- } catch (error) {
2133
- this.deps.onError?.(error);
2134
- return "skipped";
2290
+ const doFetch = options.fetchImpl ?? fetch;
2291
+ const controller = new AbortController();
2292
+ const timer = setTimeout(() => controller.abort(), 1e4);
2293
+ try {
2294
+ const response = await doFetch("https://api.github.com/user", {
2295
+ method: "GET",
2296
+ headers: {
2297
+ authorization: `Bearer ${resolved.token}`,
2298
+ accept: "application/vnd.github+json",
2299
+ "user-agent": "dsh-github-trending"
2300
+ },
2301
+ signal: controller.signal
2302
+ });
2303
+ if (!response.ok) {
2304
+ return {
2305
+ ok: false,
2306
+ code: "GITHUB_AUTH",
2307
+ source: resolved.source,
2308
+ message: `GitHub \u8FD4\u56DE HTTP ${response.status}\uFF08token \u65E0\u6548\u6216\u6743\u9650\u4E0D\u8DB3\uFF09`
2309
+ };
2135
2310
  }
2136
- }
2137
- readSchedule() {
2138
- const settings = this.deps.store.readSettings();
2311
+ const body = await response.json();
2139
2312
  return {
2140
- enabled: settings.schedule.enabled,
2141
- time: settings.schedule.time,
2142
- timezone: settings.schedule.timezone
2313
+ ok: true,
2314
+ source: resolved.source,
2315
+ ...typeof body.login === "string" ? { login: body.login } : {}
2143
2316
  };
2144
- }
2145
- /** 下一次运行(禁用时为 undefined)。 */
2146
- next() {
2147
- const schedule = this.readSchedule();
2148
- if (!schedule.enabled) return void 0;
2149
- try {
2150
- return nextOccurrence(/* @__PURE__ */ new Date(), schedule);
2151
- } catch {
2152
- return void 0;
2153
- }
2154
- }
2155
- describe(state, now = /* @__PURE__ */ new Date()) {
2156
- const schedule = this.readSchedule();
2157
- const next = schedule.enabled ? this.next() : void 0;
2317
+ } catch (error) {
2318
+ const aborted = error instanceof Error && error.name === "AbortError";
2158
2319
  return {
2159
- enabled: schedule.enabled,
2160
- time: schedule.time,
2161
- timezone: schedule.timezone,
2162
- ...next === void 0 ? {} : { nextRunAt: next.toISOString() },
2163
- ...state.lastRunAt === void 0 ? {} : { lastRunDate: zonedDateString(new Date(state.lastRunAt), schedule.timezone) },
2164
- catchUpMinutes: CATCH_UP_MINUTES,
2165
- source: "timer"
2320
+ ok: false,
2321
+ code: "GITHUB_AUTH",
2322
+ source: resolved.source,
2323
+ message: aborted ? "\u8BF7\u6C42 GitHub \u8D85\u65F6" : error instanceof Error ? error.message : String(error)
2166
2324
  };
2325
+ } finally {
2326
+ clearTimeout(timer);
2167
2327
  }
2168
- };
2328
+ }
2169
2329
 
2170
2330
  // src/host/import-cli.ts
2171
2331
  var import_cli_exports = {};
@@ -2399,6 +2559,92 @@ function importFirstAvailable(env = process.env, base) {
2399
2559
  };
2400
2560
  }
2401
2561
 
2562
+ // src/host/pending-pr.ts
2563
+ var pending_pr_exports = {};
2564
+ __export(pending_pr_exports, {
2565
+ checkPendingPr: () => checkPendingPr,
2566
+ clearPendingPrCache: () => clearPendingPrCache,
2567
+ parsePullUrl: () => parsePullUrl
2568
+ });
2569
+ function parsePullUrl(url) {
2570
+ const match = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+)(?:[/?#].*)?$/i.exec(url.trim());
2571
+ if (match === null) return void 0;
2572
+ const owner = match[1];
2573
+ const repo = match[2];
2574
+ const number = Number(match[3]);
2575
+ if (owner === void 0 || repo === void 0 || !Number.isInteger(number) || number <= 0) return void 0;
2576
+ return { owner, repo, number };
2577
+ }
2578
+ var CACHE_TTL_MS2 = 6e4;
2579
+ var cache = /* @__PURE__ */ new Map();
2580
+ function clearPendingPrCache() {
2581
+ cache.clear();
2582
+ }
2583
+ async function checkPendingPr(options) {
2584
+ const { url } = options;
2585
+ const parsed = parsePullUrl(url);
2586
+ if (parsed === void 0) {
2587
+ return { state: "unknown", url, reason: "PR \u94FE\u63A5\u65E0\u6CD5\u89E3\u6790\uFF0C\u8DF3\u8FC7\u5BF9\u8D26" };
2588
+ }
2589
+ const now = options.now ?? Date.now();
2590
+ if (options.cache !== false) {
2591
+ const hit = cache.get(url);
2592
+ if (hit !== void 0 && now - hit.at < CACHE_TTL_MS2) return hit.status;
2593
+ }
2594
+ if (options.token === void 0 || options.token === "") {
2595
+ return { state: "unknown", url, number: parsed.number, reason: "\u6CA1\u6709\u53EF\u7528\u7684 GitHub token\uFF0C\u65E0\u6CD5\u6838\u5BF9 PR \u72B6\u6001" };
2596
+ }
2597
+ const doFetch = options.fetchImpl ?? fetch;
2598
+ const controller = new AbortController();
2599
+ const timer = setTimeout(() => controller.abort(), 8e3);
2600
+ try {
2601
+ const response = await doFetch(
2602
+ `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`,
2603
+ {
2604
+ method: "GET",
2605
+ headers: {
2606
+ authorization: `Bearer ${options.token}`,
2607
+ accept: "application/vnd.github+json",
2608
+ "user-agent": "dsh-github-trending"
2609
+ },
2610
+ signal: controller.signal
2611
+ }
2612
+ );
2613
+ if (response.status === 404) {
2614
+ const status2 = { state: "unknown", url, number: parsed.number, reason: "PR \u4E0D\u5B58\u5728\u6216\u65E0\u6743\u8BBF\u95EE" };
2615
+ cache.set(url, { at: now, status: status2 });
2616
+ return status2;
2617
+ }
2618
+ if (!response.ok) {
2619
+ const status2 = { state: "unknown", url, number: parsed.number, reason: `GitHub \u8FD4\u56DE HTTP ${response.status}` };
2620
+ cache.set(url, { at: now, status: status2 });
2621
+ return status2;
2622
+ }
2623
+ const body = await response.json();
2624
+ const merged = body.merged === true || typeof body.merged_at === "string" && body.merged_at !== "";
2625
+ const state = merged ? "merged" : body.state === "open" ? "open" : body.state === "closed" ? "closed" : "unknown";
2626
+ const status = {
2627
+ state,
2628
+ url,
2629
+ number: parsed.number,
2630
+ ...typeof body.title === "string" ? { title: body.title } : {}
2631
+ };
2632
+ cache.set(url, { at: now, status });
2633
+ return status;
2634
+ } catch (error) {
2635
+ const status = {
2636
+ state: "unknown",
2637
+ url,
2638
+ number: parsed.number,
2639
+ reason: error instanceof Error ? error.message : String(error)
2640
+ };
2641
+ cache.set(url, { at: now, status });
2642
+ return status;
2643
+ } finally {
2644
+ clearTimeout(timer);
2645
+ }
2646
+ }
2647
+
2402
2648
  // src/host/preflight.ts
2403
2649
  var preflight_exports = {};
2404
2650
  __export(preflight_exports, {
@@ -2526,6 +2772,46 @@ async function preflightModel(options) {
2526
2772
  }
2527
2773
  }
2528
2774
 
2775
+ // src/host/repo.ts
2776
+ var repo_exports = {};
2777
+ __export(repo_exports, {
2778
+ readDefaultBranch: () => readDefaultBranch
2779
+ });
2780
+ import { statSync as statSync5 } from "fs";
2781
+ import path10 from "path";
2782
+ function isDirectory(dir) {
2783
+ try {
2784
+ return statSync5(dir).isDirectory();
2785
+ } catch {
2786
+ return false;
2787
+ }
2788
+ }
2789
+ async function git(dir, args, env) {
2790
+ try {
2791
+ const result = await execCapture("git", ["-C", dir, ...args], { env, timeoutMs: 5e3 });
2792
+ return result.code === 0 ? result.stdout.trim() : void 0;
2793
+ } catch {
2794
+ return void 0;
2795
+ }
2796
+ }
2797
+ async function readDefaultBranch(repoPath, options = {}) {
2798
+ const dir = repoPath.trim();
2799
+ if (dir === "") return { source: "none", message: "\u4ED3\u5E93\u8DEF\u5F84\u4E3A\u7A7A" };
2800
+ if (!path10.isAbsolute(dir)) return { source: "none", message: "\u4ED3\u5E93\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
2801
+ if (!isDirectory(dir)) return { source: "none", message: "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE" };
2802
+ const env = options.env ?? process.env;
2803
+ const origin = await git(dir, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], env);
2804
+ if (origin !== void 0) {
2805
+ const branch = origin.replace(/^origin\//, "");
2806
+ if (branch !== "") return { branch, source: "origin-head" };
2807
+ }
2808
+ const head = await git(dir, ["rev-parse", "--abbrev-ref", "HEAD"], env);
2809
+ if (head !== void 0) {
2810
+ if (head !== "" && head !== "HEAD") return { branch: head, source: "head" };
2811
+ }
2812
+ return { source: "none", message: "\u672A\u8BFB\u53D6\u5230\u9ED8\u8BA4\u5206\u652F\uFF08\u53EF\u80FD\u662F\u7A7A\u4ED3\u5E93\u6216\u6CA1\u6709 origin\uFF09" };
2813
+ }
2814
+
2529
2815
  // src/host/routes.ts
2530
2816
  var routes_exports = {};
2531
2817
  __export(routes_exports, {
@@ -2583,7 +2869,8 @@ function createRoutes(deps) {
2583
2869
  const code = error instanceof IpcError ? error.code : "INTERNAL";
2584
2870
  const message = redact(error instanceof Error ? error.message : String(error));
2585
2871
  deps.logger?.("route.error", { pathname, method, code, message });
2586
- sendJson2(response, code === "CONFIG_INVALID" ? 400 : 500, { ok: false, code, message });
2872
+ const status = code === "CONFIG_INVALID" ? 400 : 500;
2873
+ sendJson2(response, status, { ok: false, code, message });
2587
2874
  }
2588
2875
  };
2589
2876
  }
@@ -2603,10 +2890,17 @@ async function dispatch(method, pathname, url, request, deps) {
2603
2890
  }
2604
2891
  if (method === "POST" && pathname === "/config") {
2605
2892
  const body = await readBody2(request);
2606
- const next = normalizeSettings(body.settings ?? body);
2893
+ const incoming = normalizeSettings(body.settings ?? body);
2894
+ const section = typeof body.section === "string" && SETTINGS_SECTIONS.includes(body.section) ? body.section : void 0;
2895
+ let next = section === void 0 ? incoming : mergeSettingsSection(settings, incoming, section);
2896
+ if (next.repo.path.trim() !== "" && (section === void 0 || section === "repo")) {
2897
+ const branch = await readDefaultBranch(next.repo.path, { env });
2898
+ if (branch.branch !== void 0) next.repo.baseBranch = branch.branch;
2899
+ }
2607
2900
  const core = await inspectCore({ ...next, corePath: settings.corePath }, { env });
2608
2901
  const providers = core.supportedProviders.length > 0 ? core.supportedProviders : void 0;
2609
- const problems = validateSettings(next, providers);
2902
+ const allProblems = validateSettings(next, providers);
2903
+ const problems = section === void 0 ? allProblems : allProblems.filter((problem) => sectionOfField(problem.field) === section);
2610
2904
  if (problems.length > 0) {
2611
2905
  return { saved: false, problems, settings: next };
2612
2906
  }
@@ -2636,25 +2930,52 @@ async function dispatch(method, pathname, url, request, deps) {
2636
2930
  if (method === "POST" && pathname === "/run/cancel") {
2637
2931
  return { cancelled: deps.runner.cancel() };
2638
2932
  }
2933
+ if (method === "GET" && pathname === "/repo/branch") {
2934
+ const repoPath = url.searchParams.get("path") ?? settings.repo.path;
2935
+ return { repo: await readDefaultBranch(repoPath, { env }) };
2936
+ }
2937
+ if (method === "POST" && pathname === "/github/test") {
2938
+ const body = await readBody2(request);
2939
+ const candidate = body.settings === void 0 ? settings : normalizeSettings(body.settings);
2940
+ const probe = await probeGithubAuth({
2941
+ credentials: credentialServices(deps.get),
2942
+ authMode: candidate.github.authMode,
2943
+ tokenRef: candidate.github.tokenRef,
2944
+ env
2945
+ });
2946
+ return { github: probe };
2947
+ }
2639
2948
  if (method === "GET" && pathname === "/quickfill") {
2640
2949
  const candidates = await listProviderCandidates({
2641
2950
  llm: llmServices(deps.get),
2642
2951
  credentials: credentialServices(deps.get),
2643
2952
  env
2644
2953
  });
2645
- return { providers: candidates };
2954
+ return { providers: candidates, defaultSelection: readDefaultSelection(deps.get) ?? null };
2646
2955
  }
2647
2956
  if (method === "POST" && pathname === "/quickfill") {
2648
2957
  const body = await readBody2(request);
2649
- const providerId = typeof body.providerId === "string" ? body.providerId : "";
2650
- const modelId = typeof body.modelId === "string" ? body.modelId : "";
2651
2958
  const candidates = await listProviderCandidates({
2652
2959
  llm: llmServices(deps.get),
2653
2960
  credentials: credentialServices(deps.get),
2654
2961
  env
2655
2962
  });
2963
+ if (body.useDefault === true) {
2964
+ const selection = readDefaultSelection(deps.get);
2965
+ if (selection === void 0) throw new IpcError("CONFIG_INVALID", "\u5BBF\u4E3B\u6CA1\u6709\u53EF\u6CBF\u7528\u7684\u9ED8\u8BA4\u6A21\u578B");
2966
+ const candidate2 = candidates.find((item) => item.id === selection.provider);
2967
+ const next2 = applyDefaultSelection(settings, selection, candidate2);
2968
+ deps.store.writeSettings(next2);
2969
+ return {
2970
+ saved: true,
2971
+ settings: next2,
2972
+ applied: { provider: selection.provider, model: selection.model, mode: next2.model.mode }
2973
+ };
2974
+ }
2975
+ const providerId = typeof body.providerId === "string" ? body.providerId : "";
2976
+ const modelId = typeof body.modelId === "string" ? body.modelId : "";
2656
2977
  const candidate = candidates.find((item) => item.id === providerId);
2657
- if (candidate === void 0) throw new IpcError("CONFIG_INVALID", `\u5BBF\u4E3B\u6CA1\u6709\u8FD9\u4E2A provider\uFF1A${providerId}`);
2978
+ if (candidate === void 0) throw new IpcError("CONFIG_INVALID", `\u5BBF\u4E3B\u6CA1\u6709\u8FD9\u4E2A\u5DF2\u914D\u7F6E\u7684 provider\uFF1A${providerId}`);
2658
2979
  if (modelId === "") throw new IpcError("CONFIG_INVALID", "\u5FC5\u987B\u9009\u62E9\u4E00\u4E2A\u6A21\u578B");
2659
2980
  const next = applyCandidate(settings, candidate, modelId);
2660
2981
  deps.store.writeSettings(next);
@@ -2683,6 +3004,22 @@ async function statePayload(deps, settings) {
2683
3004
  const coexistence = await detectCoexistence(core.path, { env });
2684
3005
  const modelCredential = await describeRef(credentials, settings.model.apiKeyRef, env);
2685
3006
  const github = await resolveGithubToken(credentials, settings.github.authMode, settings.github.tokenRef, { env });
3007
+ let pendingPr = state.pendingPr ?? null;
3008
+ let pendingPrState;
3009
+ if (pendingPr !== null) {
3010
+ const status = await checkPendingPr({
3011
+ url: pendingPr.url,
3012
+ token: github.token,
3013
+ ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
3014
+ });
3015
+ pendingPrState = status.state;
3016
+ if (status.state === "merged" || status.state === "closed") {
3017
+ const next = { ...state };
3018
+ delete next.pendingPr;
3019
+ deps.store.writeState(next);
3020
+ pendingPr = null;
3021
+ }
3022
+ }
2686
3023
  return {
2687
3024
  plugin: { name: "@morehao/dsh-github-trending", version: deps.version },
2688
3025
  settings,
@@ -2694,7 +3031,8 @@ async function statePayload(deps, settings) {
2694
3031
  lastRunAt: state.lastRunAt ?? null,
2695
3032
  lastDigest: state.lastDigest ?? null,
2696
3033
  lastResult: state.lastResult ?? null,
2697
- pendingPr: state.pendingPr ?? null,
3034
+ pendingPr,
3035
+ ...pendingPrState === void 0 ? {} : { pendingPrState },
2698
3036
  history
2699
3037
  },
2700
3038
  model: {
@@ -2792,7 +3130,7 @@ function createTrendingRunTool(runner) {
2792
3130
  description: "\u6267\u884C\u4E00\u6B21 GitHub \u8D8B\u52BF\u65E5\u62A5\uFF1A\u5B9A\u4F4D\u5E76\u63E1\u624B\u672C\u5730\u6838\u5FC3\uFF0Cspawn \u4E00\u6B21 run\uFF0C\u8FD4\u56DE\u7ED3\u6784\u5316\u6458\u8981\uFF08\u6587\u4EF6\u6570\u3001commit\u3001PR\u3001\u9519\u8BEF\u7801\uFF09\u3002dryRun=true \u53EA\u6293\u53D6\u4E0E\u6E32\u67D3\u3001\u4E0D\u5199\u6587\u4EF6\u4E5F\u4E0D\u63D0\u4EA4\u3002\u540C\u65E5\u540C\u5185\u5BB9\u91CD\u8DD1\u4F1A\u8FD4\u56DE skipped=true\uFF08\u96F6\u65B0\u589E commit\uFF09\u3002",
2793
3131
  parameters: {
2794
3132
  dryRun: { type: "boolean", description: "\u53EA\u8BD5\u8DD1\uFF1A\u4E0D\u5199\u6587\u4EF6\u3001\u4E0D\u63D0\u4EA4\u3001\u4E0D\u5F00 PR" },
2795
- date: { type: "string", description: "\u6307\u5B9A\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1B\u7701\u7565\u5219\u7528\u4ECA\u5929" }
3133
+ date: { type: "string", description: "\u6307\u5B9A\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1B\u7701\u7565\u5219\u7528\u8C03\u5EA6\u65F6\u533A\u7684\u4ECA\u5929" }
2796
3134
  },
2797
3135
  output: {
2798
3136
  schema: RUN_OUTPUT_SCHEMA,
@@ -2894,12 +3232,15 @@ export {
2894
3232
  credentials_exports,
2895
3233
  quickfill_exports,
2896
3234
  proxy_exports,
2897
- Runner,
2898
- runner_exports,
2899
3235
  Scheduler,
2900
3236
  schedule_exports,
3237
+ Runner,
3238
+ runner_exports,
3239
+ github_exports,
2901
3240
  import_cli_exports,
3241
+ pending_pr_exports,
2902
3242
  preflight_exports,
3243
+ repo_exports,
2903
3244
  ROUTE_PREFIX,
2904
3245
  createRoutes,
2905
3246
  routes_exports,