@morehao/dsh-github-trending 0.1.4 → 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.
- package/CHANGELOG.md +18 -0
- package/README.md +12 -7
- package/client.js +616 -412
- package/contract/CONTRACT.sha256 +2 -2
- package/contract/UPSTREAM.json +5 -5
- package/contract/schema/ipc.v1.json +1 -0
- package/lib/{chunk-RXYA5WFW.js → chunk-HIJIQJCZ.js} +543 -202
- package/lib/index.js +1 -1
- package/lib/internal.d.ts +152 -10
- package/lib/internal.js +7 -1
- package/package.json +2 -2
|
@@ -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: "
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1491
|
-
|
|
1492
|
-
const
|
|
1493
|
-
|
|
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"
|
|
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
|
|
2132
|
+
apiKey,
|
|
1868
2133
|
...github.token === void 0 ? {} : { token: github.token },
|
|
1869
|
-
|
|
2134
|
+
date,
|
|
1870
2135
|
dryRun,
|
|
1871
2136
|
...baseURLOverride === void 0 ? {} : { baseURLOverride },
|
|
1872
2137
|
signal: controller.signal,
|
|
1873
|
-
secrets: [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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/
|
|
2004
|
-
var
|
|
2005
|
-
__export(
|
|
2006
|
-
|
|
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
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
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
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
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
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
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
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2313
|
+
ok: true,
|
|
2314
|
+
source: resolved.source,
|
|
2315
|
+
...typeof body.login === "string" ? { login: body.login } : {}
|
|
2143
2316
|
};
|
|
2144
|
-
}
|
|
2145
|
-
|
|
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
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|