@morehao/dsh-github-trending 0.1.4 → 0.3.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 +52 -0
- package/README.md +32 -14
- package/client.js +2545 -540
- package/lib/{chunk-RXYA5WFW.js → chunk-X4PBXIZE.js} +905 -268
- package/lib/index.js +29 -11
- package/lib/internal.d.ts +284 -91
- package/lib/internal.js +9 -1
- package/package.json +7 -6
- package/contract/CONTRACT.sha256 +0 -10
- package/contract/UPSTREAM.json +0 -37
- package/contract/fixtures/digest-degraded.json +0 -39
- package/contract/fixtures/digest-empty-lang.json +0 -39
- package/contract/fixtures/digest-normal.json +0 -91
- package/contract/schema/api.v1.yaml +0 -70
- package/contract/schema/digest.v1.json +0 -24
- package/contract/schema/ipc.v1.json +0 -103
- package/contract/schema/model.v1.json +0 -46
|
@@ -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" });
|
|
@@ -291,14 +338,21 @@ var HISTORY_FILE = "history.json";
|
|
|
291
338
|
// src/state.ts
|
|
292
339
|
var state_exports = {};
|
|
293
340
|
__export(state_exports, {
|
|
341
|
+
CONTENT_HASH_ALGO_V2: () => CONTENT_HASH_ALGO_V2,
|
|
294
342
|
HISTORY_LIMIT: () => HISTORY_LIMIT,
|
|
343
|
+
PREVIEW_LIMIT: () => PREVIEW_LIMIT,
|
|
344
|
+
PREVIEW_MAX_BYTES: () => PREVIEW_MAX_BYTES,
|
|
295
345
|
Store: () => Store,
|
|
296
346
|
newRunId: () => newRunId,
|
|
297
347
|
writeJsonAtomic: () => writeJsonAtomic
|
|
298
348
|
});
|
|
299
|
-
import { mkdirSync, readFileSync, renameSync, writeFileSync, appendFileSync, existsSync } from "fs";
|
|
349
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, appendFileSync, existsSync } from "fs";
|
|
300
350
|
import path3 from "path";
|
|
301
351
|
var HISTORY_LIMIT = 30;
|
|
352
|
+
var PREVIEW_LIMIT = 30;
|
|
353
|
+
var PREVIEW_MAX_BYTES = 512 * 1024;
|
|
354
|
+
var CONTENT_HASH_ALGO_V2 = "jcs-sha256-v1";
|
|
355
|
+
var V2_CORE_MIN = [0, 3, 0];
|
|
302
356
|
function readJson(file, fallback) {
|
|
303
357
|
try {
|
|
304
358
|
const text = readFileSync(file, "utf8");
|
|
@@ -312,6 +366,32 @@ function readJson(file, fallback) {
|
|
|
312
366
|
function ensureDir(dir) {
|
|
313
367
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
314
368
|
}
|
|
369
|
+
function coreVersionIsV2(coreVersion) {
|
|
370
|
+
if (typeof coreVersion !== "string") return false;
|
|
371
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(coreVersion.trim());
|
|
372
|
+
if (match === null) return false;
|
|
373
|
+
const major = Number(match[1]);
|
|
374
|
+
const minor = Number(match[2]);
|
|
375
|
+
const patch = Number(match[3]);
|
|
376
|
+
const [minMajor, minMinor, minPatch] = V2_CORE_MIN;
|
|
377
|
+
if (major !== minMajor) return major > minMajor;
|
|
378
|
+
if (minor !== minMinor) return minor > minMinor;
|
|
379
|
+
return patch >= minPatch;
|
|
380
|
+
}
|
|
381
|
+
function digestIsV2(digest) {
|
|
382
|
+
return digest !== void 0 && digest.contentHashAlgo === CONTENT_HASH_ALGO_V2 && coreVersionIsV2(digest.coreVersion);
|
|
383
|
+
}
|
|
384
|
+
function normalizeState(state) {
|
|
385
|
+
const next = { ...state };
|
|
386
|
+
if (next.lastDigest !== void 0 && !digestIsV2(next.lastDigest)) {
|
|
387
|
+
delete next.lastDigest;
|
|
388
|
+
delete next.pendingPr;
|
|
389
|
+
}
|
|
390
|
+
if (next.lastResult !== void 0 && next.lastResult.legacy !== true && !coreVersionIsV2(next.lastResult.coreVersion)) {
|
|
391
|
+
next.lastResult = { ...next.lastResult, legacy: true };
|
|
392
|
+
}
|
|
393
|
+
return next;
|
|
394
|
+
}
|
|
315
395
|
function writeJsonAtomic(file, value) {
|
|
316
396
|
ensureDir(path3.dirname(file));
|
|
317
397
|
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -336,7 +416,10 @@ var Store = class {
|
|
|
336
416
|
return path3.join(this.dir, HISTORY_FILE);
|
|
337
417
|
}
|
|
338
418
|
get logsPath() {
|
|
339
|
-
return
|
|
419
|
+
return path3.join(this.dir, "runs");
|
|
420
|
+
}
|
|
421
|
+
get previewsPath() {
|
|
422
|
+
return path3.join(this.dir, "previews");
|
|
340
423
|
}
|
|
341
424
|
readSettings() {
|
|
342
425
|
return normalizeSettings(readJson(this.settingsPath, {}));
|
|
@@ -345,18 +428,49 @@ var Store = class {
|
|
|
345
428
|
writeJsonAtomic(this.settingsPath, settings);
|
|
346
429
|
}
|
|
347
430
|
readState() {
|
|
348
|
-
return readJson(this.statePath, {});
|
|
431
|
+
return normalizeState(readJson(this.statePath, {}));
|
|
349
432
|
}
|
|
350
433
|
writeState(state) {
|
|
351
434
|
writeJsonAtomic(this.statePath, state);
|
|
352
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* 把 v1 时代遗留的 state / history 一次性清洗落盘:清掉不可信的 lastDigest 与
|
|
438
|
+
* 与之绑定的 pendingPr,并给旧历史记录打 legacy 标。插件启动时调用一次。
|
|
439
|
+
*/
|
|
440
|
+
migrateLegacyState() {
|
|
441
|
+
const rawState = readJson(this.statePath, {});
|
|
442
|
+
const nextState = normalizeState(rawState);
|
|
443
|
+
const stateChanged = JSON.stringify(nextState) !== JSON.stringify(rawState);
|
|
444
|
+
if (stateChanged) this.writeState(nextState);
|
|
445
|
+
const rawHistory = readJson(this.historyPath, { records: [] });
|
|
446
|
+
const records = Array.isArray(rawHistory.records) ? rawHistory.records : [];
|
|
447
|
+
let historyMarked = 0;
|
|
448
|
+
const nextRecords = records.map((record) => {
|
|
449
|
+
if (record.legacy === true || coreVersionIsV2(record.coreVersion)) return record;
|
|
450
|
+
historyMarked += 1;
|
|
451
|
+
return { ...record, legacy: true };
|
|
452
|
+
});
|
|
453
|
+
if (historyMarked > 0) writeJsonAtomic(this.historyPath, { records: nextRecords });
|
|
454
|
+
return {
|
|
455
|
+
stateChanged,
|
|
456
|
+
clearedDigest: rawState.lastDigest !== void 0 && nextState.lastDigest === void 0,
|
|
457
|
+
clearedPendingPr: rawState.pendingPr !== void 0 && nextState.pendingPr === void 0,
|
|
458
|
+
historyMarked
|
|
459
|
+
};
|
|
460
|
+
}
|
|
353
461
|
readHistory() {
|
|
462
|
+
return this.readHistoryRaw().map(
|
|
463
|
+
(record) => record.legacy === true || coreVersionIsV2(record.coreVersion) ? record : { ...record, legacy: true }
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
/** 原始历史(不做 legacy 标注):写入路径用它,避免把「读时标注」持久化。 */
|
|
467
|
+
readHistoryRaw() {
|
|
354
468
|
const file = readJson(this.historyPath, { records: [] });
|
|
355
469
|
return Array.isArray(file.records) ? file.records : [];
|
|
356
470
|
}
|
|
357
471
|
/** 记录一次运行:历史裁剪到 30 条,state 同步更新。 */
|
|
358
472
|
recordRun(record, next = {}) {
|
|
359
|
-
const records = [record, ...this.
|
|
473
|
+
const records = [record, ...this.readHistoryRaw()].slice(0, HISTORY_LIMIT);
|
|
360
474
|
writeJsonAtomic(this.historyPath, { records });
|
|
361
475
|
const state = this.readState();
|
|
362
476
|
this.writeState({ ...state, ...next, lastResult: record });
|
|
@@ -381,6 +495,61 @@ var Store = class {
|
|
|
381
495
|
return "";
|
|
382
496
|
}
|
|
383
497
|
}
|
|
498
|
+
/** 预览副本路径;写入前才建目录,读接口不会凭空创建目录。 */
|
|
499
|
+
previewPath(id) {
|
|
500
|
+
return path3.join(this.previewsPath, `${id}.md`);
|
|
501
|
+
}
|
|
502
|
+
/** 写预览副本:同目录临时文件 + rename、0600;写完裁到 30 份。 */
|
|
503
|
+
writePreview(id, text) {
|
|
504
|
+
ensureDir(this.previewsPath);
|
|
505
|
+
const file = this.previewPath(id);
|
|
506
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
507
|
+
writeFileSync(temp, text, { mode: 384 });
|
|
508
|
+
renameSync(temp, file);
|
|
509
|
+
this.prunePreviews();
|
|
510
|
+
}
|
|
511
|
+
/** 读预览副本;不存在/不可读返回 undefined,超过上限时截断。 */
|
|
512
|
+
readPreview(id, maxBytes = PREVIEW_MAX_BYTES) {
|
|
513
|
+
const file = this.previewPath(id);
|
|
514
|
+
try {
|
|
515
|
+
const stat2 = statSync(file);
|
|
516
|
+
if (!stat2.isFile()) return void 0;
|
|
517
|
+
const truncated = stat2.size > maxBytes;
|
|
518
|
+
const buffer = readFileSync(file);
|
|
519
|
+
return {
|
|
520
|
+
text: (truncated ? buffer.subarray(0, maxBytes) : buffer).toString("utf8"),
|
|
521
|
+
truncated
|
|
522
|
+
};
|
|
523
|
+
} catch {
|
|
524
|
+
return void 0;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/** 只保留最近 `keep` 份预览副本;按 mtime 倒序,任何错误都忽略。 */
|
|
528
|
+
prunePreviews(keep = PREVIEW_LIMIT) {
|
|
529
|
+
let names;
|
|
530
|
+
try {
|
|
531
|
+
names = readdirSync(this.previewsPath);
|
|
532
|
+
} catch {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const entries = [];
|
|
536
|
+
for (const name of names) {
|
|
537
|
+
if (!name.endsWith(".md")) continue;
|
|
538
|
+
const file = path3.join(this.previewsPath, name);
|
|
539
|
+
try {
|
|
540
|
+
const stat2 = statSync(file);
|
|
541
|
+
if (stat2.isFile()) entries.push({ file, mtimeMs: stat2.mtimeMs });
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
546
|
+
for (const entry of entries.slice(keep)) {
|
|
547
|
+
try {
|
|
548
|
+
unlinkSync(entry.file);
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
384
553
|
};
|
|
385
554
|
function newRunId(now = /* @__PURE__ */ new Date()) {
|
|
386
555
|
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "-");
|
|
@@ -396,6 +565,7 @@ __export(errors_exports, {
|
|
|
396
565
|
classifyStderr: () => classifyStderr,
|
|
397
566
|
hintOf: () => hintOf,
|
|
398
567
|
isRetriable: () => isRetriable,
|
|
568
|
+
reasonOf: () => reasonOf,
|
|
399
569
|
redact: () => redact
|
|
400
570
|
});
|
|
401
571
|
var PLUGIN_CODES = [
|
|
@@ -458,7 +628,38 @@ function areaOf(code) {
|
|
|
458
628
|
if (code === "GITHUB_AUTH") return "auth";
|
|
459
629
|
return "run";
|
|
460
630
|
}
|
|
461
|
-
function
|
|
631
|
+
function reasonOf(code, message, details) {
|
|
632
|
+
const structured = typeof details?.reason === "string" && details.reason !== "" ? details.reason : void 0;
|
|
633
|
+
if (structured !== void 0) return structured;
|
|
634
|
+
if (code !== "GIT_OPERATION_FAILED" || typeof message !== "string") return void 0;
|
|
635
|
+
if (/stale info/i.test(message)) return "stale-lease";
|
|
636
|
+
if (/non-fast-forward|fetch first/i.test(message)) return "non-fast-forward";
|
|
637
|
+
if (/permission denied|authentication failed|could not read Username|invalid username or password/i.test(message)) return "auth";
|
|
638
|
+
if (/protected branch|GH006/i.test(message)) return "protected";
|
|
639
|
+
if (/hook declined|pre-receive hook/i.test(message)) return "hook";
|
|
640
|
+
if (/pathspec/i.test(message)) return "pathspec";
|
|
641
|
+
return "unknown";
|
|
642
|
+
}
|
|
643
|
+
function hintOf(code, message, reason) {
|
|
644
|
+
const gitReason = reason ?? reasonOf(code, message);
|
|
645
|
+
if (code === "GIT_OPERATION_FAILED" && gitReason !== void 0) {
|
|
646
|
+
switch (gitReason) {
|
|
647
|
+
case "stale-lease":
|
|
648
|
+
return "\u63A8\u9001\u88AB\u62D2\uFF08stale info\uFF09\uFF1A\u672C\u5730\u5BF9\u8FDC\u7AEF\u5206\u652F\u7684\u8BB0\u5F55\u8FC7\u671F\uFF08\u5206\u652F\u88AB\u5220\u6216\u88AB\u6539\uFF09\uFF0C\u4E0D\u662F\u6709\u4EBA\u62A2\u5148\u63A8\u9001\u3002\u5728\u4ED3\u5E93\u91CC\u6267\u884C git fetch --prune origin \u540E\u91CD\u8BD5\uFF1B\u5347\u7EA7\u6838\u5FC3\u540E\u4E0D\u4F1A\u518D\u51FA\u73B0\u3002";
|
|
649
|
+
case "non-fast-forward":
|
|
650
|
+
return "\u63A8\u9001\u88AB\u62D2\uFF08non-fast-forward\uFF09\uFF1A\u8FDC\u7AEF\u5206\u652F\u5DF2\u524D\u8FDB\u3002\u5148 git fetch \u5E76 rebase \u5230\u8FDC\u7AEF\u5206\u652F\uFF0C\u6216\u5220\u9664\u8BE5\u8FDC\u7AEF\u5206\u652F\u540E\u91CD\u8BD5\u3002";
|
|
651
|
+
case "auth":
|
|
652
|
+
return "\u63A8\u9001\u8BA4\u8BC1\u5931\u8D25\uFF1A\u66F4\u65B0 GitHub token / gh \u767B\u5F55\u540E\u91CD\u8BD5\uFF1B\u672C\u5730 commit \u5DF2\u4FDD\u7559\u3002";
|
|
653
|
+
case "protected":
|
|
654
|
+
return "\u76EE\u6807\u5206\u652F\u53D7\u4FDD\u62A4\uFF1A\u6539\u4E3A\u901A\u8FC7 PR \u63D0\u4EA4\uFF0C\u6216\u8C03\u6574\u5206\u652F\u4FDD\u62A4\u89C4\u5219\u3002";
|
|
655
|
+
case "hook":
|
|
656
|
+
return "\u4ED3\u5E93 hook \u62D2\u7EDD\u4E86\u64CD\u4F5C\uFF1A\u6309 hook \u8F93\u51FA\u4FEE\u6B63\u540E\u91CD\u8BD5\uFF1B\u672C\u5730 commit \u5DF2\u4FDD\u7559\u3002";
|
|
657
|
+
case "pathspec":
|
|
658
|
+
return "\u6682\u5B58\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A\u68C0\u67E5\u8BBE\u7F6E\u91CC\u7684\u8F93\u51FA\u76EE\u5F55\uFF08output.dir\uFF09\u662F\u5426\u4E0E\u4ED3\u5E93\u5B9E\u9645\u76EE\u5F55\u4E00\u81F4\u3002";
|
|
659
|
+
default:
|
|
660
|
+
return "\u63A8\u9001\u5931\u8D25\uFF1A\u672C\u5730 commit \u5DF2\u4FDD\u7559\uFF0C\u67E5\u770B\u8FD0\u884C\u65E5\u5FD7\u91CC\u7684 git \u8F93\u51FA\u540E\u91CD\u8BD5\u3002";
|
|
661
|
+
}
|
|
662
|
+
}
|
|
462
663
|
switch (code) {
|
|
463
664
|
case "CORE_UNAVAILABLE":
|
|
464
665
|
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 +748,8 @@ function classifyStderr(stderr) {
|
|
|
547
748
|
const match = explicit.exec(line);
|
|
548
749
|
if (match !== null && CORE_CODES.has(match[1] ?? "")) {
|
|
549
750
|
const code = match[1];
|
|
550
|
-
|
|
751
|
+
const detail = [(match[2] ?? "").trim(), ...lines.slice(index + 1)].filter((part) => part !== "").join("\n").slice(0, 2e3);
|
|
752
|
+
return { code, message: detail || line, retriable: isRetriable(code), inferred: false };
|
|
551
753
|
}
|
|
552
754
|
}
|
|
553
755
|
const message = lines[lines.length - 1] ?? "\u6838\u5FC3\u672A\u8F93\u51FA\u9519\u8BEF\u4FE1\u606F";
|
|
@@ -585,18 +787,17 @@ var ipc_exports = {};
|
|
|
585
787
|
__export(ipc_exports, {
|
|
586
788
|
IPC_SCHEMA_VERSION: () => IPC_SCHEMA_VERSION,
|
|
587
789
|
IpcError: () => IpcError,
|
|
790
|
+
SUPPORTED_CONTRACT_MAJOR: () => SUPPORTED_CONTRACT_MAJOR,
|
|
588
791
|
SUPPORTED_DIGEST_SCHEMA: () => SUPPORTED_DIGEST_SCHEMA,
|
|
589
792
|
isVersionInfo: () => isVersionInfo,
|
|
590
793
|
parseJsonObject: () => parseJsonObject,
|
|
591
794
|
parseRunResult: () => parseRunResult,
|
|
592
795
|
parseVersionInfo: () => parseVersionInfo
|
|
593
796
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
var
|
|
597
|
-
|
|
598
|
-
// src/ipc.ts
|
|
599
|
-
var SUPPORTED_DIGEST_SCHEMA = 1;
|
|
797
|
+
import { validators } from "@morehao/trendradar-contract";
|
|
798
|
+
var IPC_SCHEMA_VERSION = 2;
|
|
799
|
+
var SUPPORTED_DIGEST_SCHEMA = 2;
|
|
800
|
+
var SUPPORTED_CONTRACT_MAJOR = 0;
|
|
600
801
|
var IpcError = class extends Error {
|
|
601
802
|
code;
|
|
602
803
|
constructor(code, message) {
|
|
@@ -632,19 +833,6 @@ function parseVersionInfo(text) {
|
|
|
632
833
|
}
|
|
633
834
|
return value;
|
|
634
835
|
}
|
|
635
|
-
function normalizeFiles(value) {
|
|
636
|
-
if (!Array.isArray(value)) return [];
|
|
637
|
-
const files = [];
|
|
638
|
-
for (const item of value) {
|
|
639
|
-
if (!isRecord(item) || typeof item.path !== "string") continue;
|
|
640
|
-
files.push({
|
|
641
|
-
path: item.path,
|
|
642
|
-
bytes: typeof item.bytes === "number" ? item.bytes : 0,
|
|
643
|
-
sha256: typeof item.sha256 === "string" ? item.sha256 : ""
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
return files;
|
|
647
|
-
}
|
|
648
836
|
function normalizeError(value) {
|
|
649
837
|
if (!isRecord(value)) return void 0;
|
|
650
838
|
if (typeof value.code !== "string" || typeof value.message !== "string") return void 0;
|
|
@@ -657,37 +845,24 @@ function normalizeError(value) {
|
|
|
657
845
|
}
|
|
658
846
|
function parseRunResult(text, options) {
|
|
659
847
|
const value = parseJsonObject(text);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const result = value;
|
|
663
|
-
return {
|
|
664
|
-
ok: result.ok,
|
|
665
|
-
skipped: result.skipped === true,
|
|
666
|
-
dryRun: options.dryRun,
|
|
667
|
-
files: normalizeFiles(result.files),
|
|
668
|
-
contentHash: typeof value.contentHash === "string" ? value.contentHash : void 0,
|
|
669
|
-
branch: typeof value.branch === "string" ? value.branch : void 0,
|
|
670
|
-
commit: typeof value.commit === "string" ? value.commit : void 0,
|
|
671
|
-
prUrl: typeof value.prUrl === "string" ? value.prUrl : void 0,
|
|
672
|
-
error: normalizeError(result.error),
|
|
673
|
-
warnings: Array.isArray(result.warnings) ? result.warnings.filter((item) => typeof item === "string") : [],
|
|
674
|
-
...timing(options),
|
|
675
|
-
coreVersion: options.coreVersion
|
|
676
|
-
};
|
|
848
|
+
if (value.command !== "run" || value.ipcSchema !== IPC_SCHEMA_VERSION || !validators.runResult(value)) {
|
|
849
|
+
throw new IpcError("IPC_INVALID", "\u6838\u5FC3 run --json \u4E0D\u662F\u5951\u7EA6 RunResult\uFF08ipcSchema=2 / command=run\uFF09");
|
|
677
850
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const digest = isRecord(value.digest) ? value.digest : void 0;
|
|
851
|
+
const result = value;
|
|
852
|
+
const error = normalizeError(result.error);
|
|
853
|
+
const skipReason = value.skipReason;
|
|
682
854
|
return {
|
|
683
|
-
ok:
|
|
684
|
-
skipped:
|
|
855
|
+
ok: result.ok,
|
|
856
|
+
skipped: result.skipped === true,
|
|
685
857
|
dryRun: options.dryRun,
|
|
686
|
-
files:
|
|
687
|
-
contentHash: typeof
|
|
688
|
-
branch: typeof
|
|
689
|
-
|
|
690
|
-
|
|
858
|
+
files: Array.isArray(result.files) ? result.files : [],
|
|
859
|
+
contentHash: typeof result.contentHash === "string" ? result.contentHash : void 0,
|
|
860
|
+
branch: typeof result.branch === "string" ? result.branch : void 0,
|
|
861
|
+
commit: typeof result.commit === "string" ? result.commit : void 0,
|
|
862
|
+
prUrl: typeof result.prUrl === "string" ? result.prUrl : void 0,
|
|
863
|
+
skipReason: typeof skipReason === "string" && skipReason !== "" ? skipReason : void 0,
|
|
864
|
+
error,
|
|
865
|
+
warnings: Array.isArray(result.warnings) ? result.warnings.filter((item) => typeof item === "string") : [],
|
|
691
866
|
...timing(options),
|
|
692
867
|
coreVersion: options.coreVersion
|
|
693
868
|
};
|
|
@@ -731,10 +906,11 @@ async function handshake(core, options = {}) {
|
|
|
731
906
|
const detail = error instanceof Error ? error.message : String(error);
|
|
732
907
|
throw new IpcError("CORE_INCOMPATIBLE", `\u6838\u5FC3 version --json \u8F93\u51FA\u65E0\u6CD5\u8BC6\u522B\uFF1A${detail}`);
|
|
733
908
|
}
|
|
734
|
-
|
|
909
|
+
const contractMajor = typeof version.contractVersion === "string" ? Number(version.contractVersion.split(".")[0]) : void 0;
|
|
910
|
+
if (version.ipcSchema !== IPC_SCHEMA_VERSION || version.digestSchema !== SUPPORTED_DIGEST_SCHEMA || contractMajor !== SUPPORTED_CONTRACT_MAJOR) {
|
|
735
911
|
throw new IpcError(
|
|
736
912
|
"CORE_INCOMPATIBLE",
|
|
737
|
-
`\u6838\u5FC3\u534F\u8BAE\u4E0D\u5339\u914D\uFF1A\u6838\u5FC3 ipcSchema=${version.ipcSchema} digestSchema=${version.digestSchema}\uFF0C\u63D2\u4EF6\u652F\u6301 ipcSchema=${IPC_SCHEMA_VERSION} digestSchema=${SUPPORTED_DIGEST_SCHEMA}\u3002\u8BF7\u628A\u63D2\u4EF6\u4E0E\u6838\u5FC3\u5347\u7EA7\u5230\u540C\u4E00\u7248\u672C\uFF08\u63D2\u4EF6\u5E26\u5E73\u53F0\u5305\uFF0C\u5347\u7EA7\u63D2\u4EF6\u5373\u53EF\uFF09\u3002`
|
|
913
|
+
`\u6838\u5FC3\u534F\u8BAE\u4E0D\u5339\u914D\uFF1A\u6838\u5FC3 ipcSchema=${version.ipcSchema} digestSchema=${version.digestSchema} contractVersion=${version.contractVersion ?? "(\u7F3A\u5931)"}\uFF0C\u63D2\u4EF6\u652F\u6301 ipcSchema=${IPC_SCHEMA_VERSION} digestSchema=${SUPPORTED_DIGEST_SCHEMA} contractVersion \u4E3B\u7248\u672C=${SUPPORTED_CONTRACT_MAJOR}\u3002\u8BF7\u628A\u63D2\u4EF6\u4E0E\u6838\u5FC3\u5347\u7EA7\u5230\u540C\u4E00\u7248\u672C\uFF08\u63D2\u4EF6\u5E26\u5E73\u53F0\u5305\uFF0C\u5347\u7EA7\u63D2\u4EF6\u5373\u53EF\uFF09\u3002`
|
|
738
914
|
);
|
|
739
915
|
}
|
|
740
916
|
const supportedProviders = Array.isArray(version.supportedProviders) && version.supportedProviders.length > 0 ? version.supportedProviders : [...DEFAULT_PROVIDERS];
|
|
@@ -752,7 +928,7 @@ __export(release_exports, {
|
|
|
752
928
|
verifyCached: () => verifyCached
|
|
753
929
|
});
|
|
754
930
|
import { createHash } from "crypto";
|
|
755
|
-
import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync, statSync, unlinkSync } from "fs";
|
|
931
|
+
import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
756
932
|
import { createRequire } from "module";
|
|
757
933
|
import { pipeline } from "stream/promises";
|
|
758
934
|
import { Readable } from "stream";
|
|
@@ -819,7 +995,7 @@ function markerPath(binaryPath) {
|
|
|
819
995
|
function verifyCached(binaryPath) {
|
|
820
996
|
let info;
|
|
821
997
|
try {
|
|
822
|
-
info =
|
|
998
|
+
info = statSync2(binaryPath);
|
|
823
999
|
} catch {
|
|
824
1000
|
return { ok: false, reason: "missing" };
|
|
825
1001
|
}
|
|
@@ -933,7 +1109,7 @@ async function ensureCore(options = {}) {
|
|
|
933
1109
|
);
|
|
934
1110
|
}
|
|
935
1111
|
try {
|
|
936
|
-
|
|
1112
|
+
unlinkSync2(descriptor.binaryPath);
|
|
937
1113
|
} catch {
|
|
938
1114
|
}
|
|
939
1115
|
}
|
|
@@ -954,11 +1130,11 @@ __export(locate_exports, {
|
|
|
954
1130
|
locateCore: () => locateCore,
|
|
955
1131
|
overridePath: () => overridePath
|
|
956
1132
|
});
|
|
957
|
-
import { accessSync, constants, existsSync as existsSync3, statSync as
|
|
1133
|
+
import { accessSync, constants, existsSync as existsSync3, statSync as statSync3 } from "fs";
|
|
958
1134
|
import path5 from "path";
|
|
959
1135
|
function executable(file) {
|
|
960
1136
|
try {
|
|
961
|
-
if (!
|
|
1137
|
+
if (!statSync3(file).isFile()) return false;
|
|
962
1138
|
accessSync(file, constants.X_OK);
|
|
963
1139
|
return true;
|
|
964
1140
|
} catch {
|
|
@@ -1174,7 +1350,12 @@ async function runCore(options) {
|
|
|
1174
1350
|
`;
|
|
1175
1351
|
const secrets = [options.apiKey, options.token, ...options.secrets ?? []];
|
|
1176
1352
|
const child = spawn2(options.core.path, buildCoreArgs(channel.path, { dryRun: options.dryRun, date: options.date }), {
|
|
1177
|
-
env: sanitizedEnv(process.env, {
|
|
1353
|
+
env: sanitizedEnv(process.env, {
|
|
1354
|
+
GITHUB_TOKEN: options.token,
|
|
1355
|
+
GH_TOKEN: options.token,
|
|
1356
|
+
// 环境变量通道:旧核心会忽略它,不会因为多一个未知参数而失败。
|
|
1357
|
+
TRENDRADAR_TRIGGER: options.trigger ?? "manual"
|
|
1358
|
+
}),
|
|
1178
1359
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1179
1360
|
windowsHide: true
|
|
1180
1361
|
});
|
|
@@ -1305,12 +1486,12 @@ __export(core_exports, {
|
|
|
1305
1486
|
findOnPath: () => findOnPath,
|
|
1306
1487
|
inspectCore: () => inspectCore
|
|
1307
1488
|
});
|
|
1308
|
-
import { accessSync as accessSync2, constants as constants3, statSync as
|
|
1489
|
+
import { accessSync as accessSync2, constants as constants3, statSync as statSync4 } from "fs";
|
|
1309
1490
|
import path7 from "path";
|
|
1310
1491
|
var CACHE_TTL_MS = 3e4;
|
|
1311
1492
|
function executable2(file) {
|
|
1312
1493
|
try {
|
|
1313
|
-
if (!
|
|
1494
|
+
if (!statSync4(file).isFile()) return false;
|
|
1314
1495
|
accessSync2(file, constants3.X_OK);
|
|
1315
1496
|
return true;
|
|
1316
1497
|
} catch {
|
|
@@ -1406,7 +1587,7 @@ async function detectCoexistence(ownPath, options = {}) {
|
|
|
1406
1587
|
};
|
|
1407
1588
|
} else {
|
|
1408
1589
|
const info = JSON.parse(result.stdout);
|
|
1409
|
-
const compatible = info.ipcSchema ===
|
|
1590
|
+
const compatible = info.ipcSchema === IPC_SCHEMA_VERSION && info.digestSchema === SUPPORTED_DIGEST_SCHEMA;
|
|
1410
1591
|
status = {
|
|
1411
1592
|
found: true,
|
|
1412
1593
|
path: found,
|
|
@@ -1474,7 +1655,7 @@ async function resolveRef(services, ref, env = process.env) {
|
|
|
1474
1655
|
async function resolveGithubToken(services, authMode, tokenRef, deps = {}) {
|
|
1475
1656
|
const env = deps.env ?? process.env;
|
|
1476
1657
|
if (authMode === "gh") {
|
|
1477
|
-
const execGh = deps.execGh ?? defaultGhToken;
|
|
1658
|
+
const execGh = deps.execGh ?? (() => defaultGhToken(deps.freshGh === true));
|
|
1478
1659
|
try {
|
|
1479
1660
|
const token = await execGh();
|
|
1480
1661
|
if (token !== void 0 && token !== "") return { token, source: "gh" };
|
|
@@ -1485,22 +1666,30 @@ async function resolveGithubToken(services, authMode, tokenRef, deps = {}) {
|
|
|
1485
1666
|
if (resolved.value !== void 0) return { token: resolved.value, source: resolved.source ?? "credentials" };
|
|
1486
1667
|
return { source: "none" };
|
|
1487
1668
|
}
|
|
1488
|
-
|
|
1669
|
+
var ghTokenCache;
|
|
1670
|
+
var GH_TOKEN_TTL_MS = 3e4;
|
|
1671
|
+
async function defaultGhToken(fresh = false) {
|
|
1672
|
+
if (!fresh && ghTokenCache !== void 0 && Date.now() - ghTokenCache.at < GH_TOKEN_TTL_MS) {
|
|
1673
|
+
return ghTokenCache.value;
|
|
1674
|
+
}
|
|
1489
1675
|
const { execCapture: execCapture2 } = await import("./exec-33EQXGUA.js");
|
|
1490
|
-
const result = await execCapture2("gh", ["auth", "token"], { timeoutMs:
|
|
1491
|
-
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1676
|
+
const result = await execCapture2("gh", ["auth", "token"], { timeoutMs: 2e3 });
|
|
1677
|
+
const token = result.code === 0 ? result.stdout.trim() : "";
|
|
1678
|
+
const value = token === "" ? void 0 : token;
|
|
1679
|
+
ghTokenCache = { value, at: Date.now() };
|
|
1680
|
+
return value;
|
|
1494
1681
|
}
|
|
1495
1682
|
|
|
1496
1683
|
// src/host/quickfill.ts
|
|
1497
1684
|
var quickfill_exports = {};
|
|
1498
1685
|
__export(quickfill_exports, {
|
|
1499
1686
|
applyCandidate: () => applyCandidate,
|
|
1687
|
+
applyDefaultSelection: () => applyDefaultSelection,
|
|
1500
1688
|
coreProviderFor: () => coreProviderFor,
|
|
1501
1689
|
credentialServices: () => credentialServices,
|
|
1502
1690
|
listProviderCandidates: () => listProviderCandidates,
|
|
1503
1691
|
llmServices: () => llmServices,
|
|
1692
|
+
readDefaultSelection: () => readDefaultSelection,
|
|
1504
1693
|
resolveModelKey: () => resolveModelKey
|
|
1505
1694
|
});
|
|
1506
1695
|
function llmServices(get) {
|
|
@@ -1531,7 +1720,6 @@ async function listProviderCandidates(options) {
|
|
|
1531
1720
|
const declared = new Map(configurable.map((entry) => [entry.provider, entry]));
|
|
1532
1721
|
const ids = [];
|
|
1533
1722
|
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
1723
|
const candidates = [];
|
|
1536
1724
|
for (const id of ids) {
|
|
1537
1725
|
const entry = declared.get(id);
|
|
@@ -1550,7 +1738,7 @@ async function listProviderCandidates(options) {
|
|
|
1550
1738
|
break;
|
|
1551
1739
|
}
|
|
1552
1740
|
}
|
|
1553
|
-
if (mode !== "direct"
|
|
1741
|
+
if (mode !== "direct") mode = "proxy";
|
|
1554
1742
|
const models = await listModelsWithTimeout(llm, id);
|
|
1555
1743
|
candidates.push({
|
|
1556
1744
|
id,
|
|
@@ -1568,7 +1756,37 @@ async function listProviderCandidates(options) {
|
|
|
1568
1756
|
coreProvider: coreProviderFor(id)
|
|
1569
1757
|
});
|
|
1570
1758
|
}
|
|
1571
|
-
return candidates;
|
|
1759
|
+
return candidates.filter((candidate) => candidate.models.length > 0);
|
|
1760
|
+
}
|
|
1761
|
+
function readDefaultSelection(get) {
|
|
1762
|
+
try {
|
|
1763
|
+
const service = get("agentDefaultModel");
|
|
1764
|
+
const selection = service?.currentSelection?.();
|
|
1765
|
+
if (selection !== void 0 && typeof selection.provider === "string" && selection.provider !== "" && typeof selection.model === "string" && selection.model !== "") {
|
|
1766
|
+
return {
|
|
1767
|
+
provider: selection.provider,
|
|
1768
|
+
model: selection.model,
|
|
1769
|
+
...typeof selection.reasoningEffort === "string" ? { reasoningEffort: selection.reasoningEffort } : {}
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
} catch {
|
|
1773
|
+
}
|
|
1774
|
+
return void 0;
|
|
1775
|
+
}
|
|
1776
|
+
function applyDefaultSelection(settings, selection, candidate) {
|
|
1777
|
+
if (candidate !== void 0) return applyCandidate(settings, candidate, selection.model);
|
|
1778
|
+
const coreProvider = coreProviderFor(selection.provider);
|
|
1779
|
+
return {
|
|
1780
|
+
...settings,
|
|
1781
|
+
model: {
|
|
1782
|
+
...settings.model,
|
|
1783
|
+
provider: coreProvider,
|
|
1784
|
+
mode: "proxy",
|
|
1785
|
+
hostProvider: selection.provider,
|
|
1786
|
+
baseURL: "",
|
|
1787
|
+
model: selection.model
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1572
1790
|
}
|
|
1573
1791
|
function applyCandidate(settings, candidate, modelId) {
|
|
1574
1792
|
const model = {
|
|
@@ -1763,18 +1981,196 @@ function authorized(request, token) {
|
|
|
1763
1981
|
return provided.length === expected.length && timingSafeEqual(provided, expected);
|
|
1764
1982
|
}
|
|
1765
1983
|
|
|
1984
|
+
// src/host/schedule.ts
|
|
1985
|
+
var schedule_exports = {};
|
|
1986
|
+
__export(schedule_exports, {
|
|
1987
|
+
Scheduler: () => Scheduler,
|
|
1988
|
+
isDue: () => isDue,
|
|
1989
|
+
nextOccurrence: () => nextOccurrence,
|
|
1990
|
+
parseClock: () => parseClock,
|
|
1991
|
+
zonedDateString: () => zonedDateString,
|
|
1992
|
+
zonedTimeToUtc: () => zonedTimeToUtc
|
|
1993
|
+
});
|
|
1994
|
+
var MINUTE = 6e4;
|
|
1995
|
+
var CATCH_UP_MINUTES = 6 * 60;
|
|
1996
|
+
var MAX_ATTEMPTS_PER_DAY = 2;
|
|
1997
|
+
var RETRY_GAP_MS = 5 * MINUTE;
|
|
1998
|
+
function partsOf(date, timezone) {
|
|
1999
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
2000
|
+
timeZone: timezone,
|
|
2001
|
+
hourCycle: "h23",
|
|
2002
|
+
year: "numeric",
|
|
2003
|
+
month: "2-digit",
|
|
2004
|
+
day: "2-digit",
|
|
2005
|
+
hour: "2-digit",
|
|
2006
|
+
minute: "2-digit",
|
|
2007
|
+
second: "2-digit"
|
|
2008
|
+
});
|
|
2009
|
+
const map = /* @__PURE__ */ new Map();
|
|
2010
|
+
for (const part of formatter.formatToParts(date)) {
|
|
2011
|
+
if (part.type !== "literal") map.set(part.type, part.value);
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
year: Number(map.get("year")),
|
|
2015
|
+
month: Number(map.get("month")),
|
|
2016
|
+
day: Number(map.get("day")),
|
|
2017
|
+
hour: Number(map.get("hour")),
|
|
2018
|
+
minute: Number(map.get("minute")),
|
|
2019
|
+
second: Number(map.get("second"))
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
function offsetMs(date, timezone) {
|
|
2023
|
+
const parts = partsOf(date, timezone);
|
|
2024
|
+
const asUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
|
|
2025
|
+
return asUtc - Math.floor(date.getTime() / 1e3) * 1e3;
|
|
2026
|
+
}
|
|
2027
|
+
function zonedTimeToUtc(year, month, day, hour, minute, second, timezone) {
|
|
2028
|
+
const guess = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
2029
|
+
const first = new Date(guess - offsetMs(new Date(guess), timezone));
|
|
2030
|
+
return new Date(guess - offsetMs(first, timezone));
|
|
2031
|
+
}
|
|
2032
|
+
function parseClock(time) {
|
|
2033
|
+
const match = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/.exec(time.trim());
|
|
2034
|
+
if (match === null) throw new Error(`\u975E\u6CD5\u65F6\u95F4\uFF1A${time}`);
|
|
2035
|
+
return {
|
|
2036
|
+
hour: Number(match[1]),
|
|
2037
|
+
minute: Number(match[2]),
|
|
2038
|
+
second: Number(match[3] ?? "0")
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
function zonedDateString(date, timezone) {
|
|
2042
|
+
const parts = partsOf(date, timezone);
|
|
2043
|
+
return `${String(parts.year).padStart(4, "0")}-${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
|
|
2044
|
+
}
|
|
2045
|
+
function nextOccurrence(now, schedule) {
|
|
2046
|
+
const { hour, minute, second } = parseClock(schedule.time);
|
|
2047
|
+
const today = partsOf(now, schedule.timezone);
|
|
2048
|
+
const candidate = zonedTimeToUtc(today.year, today.month, today.day, hour, minute, second, schedule.timezone);
|
|
2049
|
+
if (candidate.getTime() > now.getTime()) return candidate;
|
|
2050
|
+
const tomorrow = new Date(Date.UTC(today.year, today.month - 1, today.day) + 864e5);
|
|
2051
|
+
return zonedTimeToUtc(
|
|
2052
|
+
tomorrow.getUTCFullYear(),
|
|
2053
|
+
tomorrow.getUTCMonth() + 1,
|
|
2054
|
+
tomorrow.getUTCDate(),
|
|
2055
|
+
hour,
|
|
2056
|
+
minute,
|
|
2057
|
+
second,
|
|
2058
|
+
schedule.timezone
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
function isDue(now, schedule) {
|
|
2062
|
+
const { hour, minute } = parseClock(schedule.time);
|
|
2063
|
+
const parts = partsOf(now, schedule.timezone);
|
|
2064
|
+
const nowMinutes = parts.hour * 60 + parts.minute;
|
|
2065
|
+
const targetMinutes = hour * 60 + minute;
|
|
2066
|
+
const late = nowMinutes - targetMinutes;
|
|
2067
|
+
return late >= 0 && late <= CATCH_UP_MINUTES;
|
|
2068
|
+
}
|
|
2069
|
+
var Scheduler = class {
|
|
2070
|
+
deps;
|
|
2071
|
+
timerHandle;
|
|
2072
|
+
stopped = false;
|
|
2073
|
+
/** 当天已尝试次数与时间;只在内存里,进程重启后重新计数。 */
|
|
2074
|
+
attempts = /* @__PURE__ */ new Map();
|
|
2075
|
+
constructor(deps) {
|
|
2076
|
+
this.deps = deps;
|
|
2077
|
+
}
|
|
2078
|
+
start() {
|
|
2079
|
+
this.stopped = false;
|
|
2080
|
+
const timer = this.deps.getTimer();
|
|
2081
|
+
const tick = () => {
|
|
2082
|
+
void this.tick();
|
|
2083
|
+
};
|
|
2084
|
+
if (timer !== void 0 && typeof timer.interval === "function") {
|
|
2085
|
+
this.timerHandle = timer.interval(tick, MINUTE);
|
|
2086
|
+
} else {
|
|
2087
|
+
const handle = setInterval(tick, MINUTE);
|
|
2088
|
+
handle.unref?.();
|
|
2089
|
+
this.timerHandle = handle;
|
|
2090
|
+
}
|
|
2091
|
+
setTimeout(tick, 5e3).unref?.();
|
|
2092
|
+
}
|
|
2093
|
+
stop() {
|
|
2094
|
+
this.stopped = true;
|
|
2095
|
+
const handle = this.timerHandle;
|
|
2096
|
+
if (typeof handle === "object" && handle !== null && typeof handle.clear === "function") handle.clear();
|
|
2097
|
+
else if (typeof handle === "number" || handle !== void 0) clearInterval(handle);
|
|
2098
|
+
this.timerHandle = void 0;
|
|
2099
|
+
}
|
|
2100
|
+
/** 公开给测试:判断此刻是否该跑,并按需触发一次。 */
|
|
2101
|
+
async tick(now = /* @__PURE__ */ new Date()) {
|
|
2102
|
+
if (this.stopped) return "skipped";
|
|
2103
|
+
const schedule = this.readSchedule();
|
|
2104
|
+
if (!schedule.enabled) return "disabled";
|
|
2105
|
+
if (!isDue(now, schedule)) return "skipped";
|
|
2106
|
+
const today = zonedDateString(now, schedule.timezone);
|
|
2107
|
+
const state = this.deps.store.readState();
|
|
2108
|
+
const lastSuccess = state.lastSuccessAt === void 0 ? void 0 : zonedDateString(new Date(state.lastSuccessAt), schedule.timezone);
|
|
2109
|
+
if (lastSuccess === today) return "skipped";
|
|
2110
|
+
const attempt = this.attempts.get(today);
|
|
2111
|
+
if (attempt !== void 0) {
|
|
2112
|
+
if (attempt.count >= MAX_ATTEMPTS_PER_DAY) return "skipped";
|
|
2113
|
+
if (now.getTime() - attempt.lastAt < RETRY_GAP_MS) return "skipped";
|
|
2114
|
+
}
|
|
2115
|
+
if (this.deps.isRunning()) return "busy";
|
|
2116
|
+
for (const key of this.attempts.keys()) {
|
|
2117
|
+
if (key !== today) this.attempts.delete(key);
|
|
2118
|
+
}
|
|
2119
|
+
this.attempts.set(today, { count: (attempt?.count ?? 0) + 1, lastAt: now.getTime() });
|
|
2120
|
+
try {
|
|
2121
|
+
await this.deps.run("schedule");
|
|
2122
|
+
return "ran";
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
this.deps.onError?.(error);
|
|
2125
|
+
return "skipped";
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
readSchedule() {
|
|
2129
|
+
const settings = this.deps.store.readSettings();
|
|
2130
|
+
return {
|
|
2131
|
+
enabled: settings.schedule.enabled,
|
|
2132
|
+
time: settings.schedule.time,
|
|
2133
|
+
timezone: settings.schedule.timezone
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
/** 下一次运行(禁用时为 undefined)。 */
|
|
2137
|
+
next() {
|
|
2138
|
+
const schedule = this.readSchedule();
|
|
2139
|
+
if (!schedule.enabled) return void 0;
|
|
2140
|
+
try {
|
|
2141
|
+
return nextOccurrence(/* @__PURE__ */ new Date(), schedule);
|
|
2142
|
+
} catch {
|
|
2143
|
+
return void 0;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
describe(state, now = /* @__PURE__ */ new Date()) {
|
|
2147
|
+
const schedule = this.readSchedule();
|
|
2148
|
+
const next = schedule.enabled ? this.next() : void 0;
|
|
2149
|
+
return {
|
|
2150
|
+
enabled: schedule.enabled,
|
|
2151
|
+
time: schedule.time,
|
|
2152
|
+
timezone: schedule.timezone,
|
|
2153
|
+
...next === void 0 ? {} : { nextRunAt: next.toISOString() },
|
|
2154
|
+
...state.lastRunAt === void 0 ? {} : { lastRunDate: zonedDateString(new Date(state.lastRunAt), schedule.timezone) },
|
|
2155
|
+
catchUpMinutes: CATCH_UP_MINUTES,
|
|
2156
|
+
source: "timer"
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
|
|
1766
2161
|
// src/host/runner.ts
|
|
1767
2162
|
var runner_exports = {};
|
|
1768
2163
|
__export(runner_exports, {
|
|
1769
2164
|
Runner: () => Runner
|
|
1770
2165
|
});
|
|
1771
|
-
import {
|
|
2166
|
+
import { createHash as createHash2 } from "crypto";
|
|
2167
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync, statSync as statSync5 } from "fs";
|
|
1772
2168
|
import path8 from "path";
|
|
1773
2169
|
function repoProblem(repoPath) {
|
|
1774
2170
|
if (repoPath.trim() === "") return { code: "CONFIG_INVALID", message: "\u8FD8\u6CA1\u6709\u9009\u62E9\u5F52\u6863\u4ED3\u5E93" };
|
|
1775
2171
|
if (!existsSync4(repoPath)) return { code: "REPO_NOT_GIT", message: `\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${repoPath}` };
|
|
1776
2172
|
try {
|
|
1777
|
-
if (!
|
|
2173
|
+
if (!statSync5(repoPath).isDirectory()) return { code: "REPO_NOT_GIT", message: `\u4E0D\u662F\u76EE\u5F55\uFF1A${repoPath}` };
|
|
1778
2174
|
} catch {
|
|
1779
2175
|
return { code: "REPO_NOT_GIT", message: `\u65E0\u6CD5\u8BBF\u95EE\uFF1A${repoPath}` };
|
|
1780
2176
|
}
|
|
@@ -1783,6 +2179,22 @@ function repoProblem(repoPath) {
|
|
|
1783
2179
|
}
|
|
1784
2180
|
return void 0;
|
|
1785
2181
|
}
|
|
2182
|
+
function runDate(settings, now = /* @__PURE__ */ new Date()) {
|
|
2183
|
+
try {
|
|
2184
|
+
return zonedDateString(now, settings.schedule.timezone);
|
|
2185
|
+
} catch {
|
|
2186
|
+
return now.toISOString().slice(0, 10);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
function isInside(root, target) {
|
|
2190
|
+
const rel = path8.relative(root, target);
|
|
2191
|
+
return rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
|
|
2192
|
+
}
|
|
2193
|
+
function normalizeSha256(value) {
|
|
2194
|
+
if (typeof value !== "string") return void 0;
|
|
2195
|
+
const hex = value.trim().replace(/^sha256:/i, "").toLowerCase();
|
|
2196
|
+
return /^[0-9a-f]{64}$/.test(hex) ? hex : void 0;
|
|
2197
|
+
}
|
|
1786
2198
|
var Runner = class {
|
|
1787
2199
|
active;
|
|
1788
2200
|
deps;
|
|
@@ -1812,12 +2224,13 @@ var Runner = class {
|
|
|
1812
2224
|
}
|
|
1813
2225
|
const repoIssue = repoProblem(settings.repo.path);
|
|
1814
2226
|
if (repoIssue !== void 0) throw new IpcError(repoIssue.code, repoIssue.message);
|
|
2227
|
+
const dryRun = options.dryRun === true;
|
|
2228
|
+
const date = options.date !== void 0 && options.date !== "" ? options.date : runDate(settings);
|
|
1815
2229
|
const id = newRunId();
|
|
1816
2230
|
const controller = new AbortController();
|
|
1817
2231
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1818
2232
|
this.active = { id, controller, startedAt };
|
|
1819
2233
|
const logFile = store.newLogPath(id);
|
|
1820
|
-
const dryRun = options.dryRun === true;
|
|
1821
2234
|
const startedMs = Date.now();
|
|
1822
2235
|
let proxy;
|
|
1823
2236
|
try {
|
|
@@ -1836,6 +2249,7 @@ var Runner = class {
|
|
|
1836
2249
|
if (modelKey.mode === "direct" && (modelKey.apiKey === void 0 || modelKey.apiKey === "")) {
|
|
1837
2250
|
throw new IpcError("MODEL_KEY_MISSING", `\u6CA1\u6709\u89E3\u6790\u5230\u6A21\u578B\u5BC6\u94A5\uFF08\u5F15\u7528 ${settings.model.apiKeyRef}\uFF09`);
|
|
1838
2251
|
}
|
|
2252
|
+
let apiKey = modelKey.apiKey ?? "";
|
|
1839
2253
|
let baseURLOverride;
|
|
1840
2254
|
if (modelKey.mode === "proxy") {
|
|
1841
2255
|
const llm = this.deps.get("llm");
|
|
@@ -1851,6 +2265,7 @@ var Runner = class {
|
|
|
1851
2265
|
timeoutMs: 12e4
|
|
1852
2266
|
});
|
|
1853
2267
|
baseURLOverride = proxy.url;
|
|
2268
|
+
apiKey = proxy.token;
|
|
1854
2269
|
store.appendLog(
|
|
1855
2270
|
logFile,
|
|
1856
2271
|
`[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 +2279,14 @@ var Runner = class {
|
|
|
1864
2279
|
core,
|
|
1865
2280
|
version: handshakeResult.version,
|
|
1866
2281
|
settings,
|
|
1867
|
-
apiKey
|
|
2282
|
+
apiKey,
|
|
1868
2283
|
...github.token === void 0 ? {} : { token: github.token },
|
|
1869
|
-
|
|
2284
|
+
date,
|
|
1870
2285
|
dryRun,
|
|
2286
|
+
trigger: options.trigger === "schedule" ? "schedule" : "manual",
|
|
1871
2287
|
...baseURLOverride === void 0 ? {} : { baseURLOverride },
|
|
1872
2288
|
signal: controller.signal,
|
|
1873
|
-
secrets: [
|
|
2289
|
+
secrets: [apiKey, github.token],
|
|
1874
2290
|
onStderr: (line) => store.appendLog(logFile, line)
|
|
1875
2291
|
});
|
|
1876
2292
|
const outcome = result.outcome;
|
|
@@ -1878,13 +2294,21 @@ var Runner = class {
|
|
|
1878
2294
|
id,
|
|
1879
2295
|
outcome,
|
|
1880
2296
|
dryRun,
|
|
1881
|
-
|
|
2297
|
+
date,
|
|
1882
2298
|
logFile,
|
|
1883
2299
|
channel: result.channelKind,
|
|
1884
2300
|
proxy: proxy !== void 0,
|
|
1885
2301
|
coreSource: core.source,
|
|
1886
2302
|
...core.tag === void 0 ? {} : { coreTag: core.tag }
|
|
1887
2303
|
});
|
|
2304
|
+
const preview = this.capturePreview({
|
|
2305
|
+
id,
|
|
2306
|
+
date,
|
|
2307
|
+
settings,
|
|
2308
|
+
outcome,
|
|
2309
|
+
secrets: [apiKey, github.token]
|
|
2310
|
+
});
|
|
2311
|
+
if (preview !== void 0) summary.preview = preview;
|
|
1888
2312
|
this.record(summary, settings);
|
|
1889
2313
|
return summary;
|
|
1890
2314
|
} catch (error) {
|
|
@@ -1893,7 +2317,7 @@ var Runner = class {
|
|
|
1893
2317
|
id,
|
|
1894
2318
|
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
2319
|
dryRun,
|
|
1896
|
-
|
|
2320
|
+
date,
|
|
1897
2321
|
logFile,
|
|
1898
2322
|
durationMs: Date.now() - startedMs
|
|
1899
2323
|
});
|
|
@@ -1910,14 +2334,19 @@ var Runner = class {
|
|
|
1910
2334
|
}
|
|
1911
2335
|
summarize(input) {
|
|
1912
2336
|
const { outcome } = input;
|
|
1913
|
-
const error = outcome.error === void 0 ? void 0 : {
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2337
|
+
const error = outcome.error === void 0 ? void 0 : (() => {
|
|
2338
|
+
const message = redact(outcome.error.message);
|
|
2339
|
+
const reason = reasonOf(outcome.error.code, message, outcome.error.details);
|
|
2340
|
+
return {
|
|
2341
|
+
code: outcome.error.code,
|
|
2342
|
+
message,
|
|
2343
|
+
...reason === void 0 ? {} : { reason },
|
|
2344
|
+
hint: hintOf(outcome.error.code, message, reason),
|
|
2345
|
+
area: areaOf(outcome.error.code),
|
|
2346
|
+
retriable: outcome.error.retriable,
|
|
2347
|
+
inferred: outcome.error.details?.inferred === true
|
|
2348
|
+
};
|
|
2349
|
+
})();
|
|
1921
2350
|
return {
|
|
1922
2351
|
id: input.id,
|
|
1923
2352
|
ok: outcome.ok,
|
|
@@ -1929,7 +2358,9 @@ var Runner = class {
|
|
|
1929
2358
|
files: outcome.files.length,
|
|
1930
2359
|
...outcome.contentHash === void 0 ? {} : { contentHash: outcome.contentHash },
|
|
1931
2360
|
...outcome.branch === void 0 ? {} : { branch: outcome.branch },
|
|
2361
|
+
...outcome.commit === void 0 ? {} : { commit: outcome.commit },
|
|
1932
2362
|
...outcome.prUrl === void 0 ? {} : { prUrl: outcome.prUrl },
|
|
2363
|
+
...outcome.skipReason === void 0 ? {} : { skipReason: outcome.skipReason },
|
|
1933
2364
|
...error === void 0 ? {} : { error },
|
|
1934
2365
|
warnings: outcome.warnings,
|
|
1935
2366
|
logFile: input.logFile,
|
|
@@ -1951,11 +2382,73 @@ var Runner = class {
|
|
|
1951
2382
|
durationMs: input.durationMs ?? 0,
|
|
1952
2383
|
coreVersion: "",
|
|
1953
2384
|
files: 0,
|
|
1954
|
-
error: { code, message, hint: hintOf(code), area: areaOf(code), retriable: false },
|
|
2385
|
+
error: { code, message, hint: hintOf(code, message), area: areaOf(code), retriable: false },
|
|
1955
2386
|
warnings: [],
|
|
1956
2387
|
logFile: input.logFile
|
|
1957
2388
|
};
|
|
1958
2389
|
}
|
|
2390
|
+
/**
|
|
2391
|
+
* 从本次核心返回的 artifact 清单里选当天的 `.md`,只读复制到 previews/。
|
|
2392
|
+
*
|
|
2393
|
+
* 这是插件**唯一**读取用户仓库的场景,而且只读核心自己刚写出的那一份:
|
|
2394
|
+
* - 路径必须在 `repo.path` 内(词法 + realpath 各判一次,挡 `..`/绝对路径/符号链接);
|
|
2395
|
+
* - 上限 512 KB、拒绝 NUL、sha256 必须与契约声明一致;
|
|
2396
|
+
* - 任何一步失败都只记日志,返回 undefined;运行结果不受影响。
|
|
2397
|
+
*/
|
|
2398
|
+
capturePreview(input) {
|
|
2399
|
+
const { outcome, settings, date, id } = input;
|
|
2400
|
+
if (!outcome.ok || outcome.dryRun || outcome.skipped || date === void 0) return void 0;
|
|
2401
|
+
const skip = (reason, extra = {}) => {
|
|
2402
|
+
this.deps.logger?.("preview.skip", { id, reason, ...extra });
|
|
2403
|
+
return void 0;
|
|
2404
|
+
};
|
|
2405
|
+
const markdown = outcome.files.filter((file) => {
|
|
2406
|
+
const ext = path8.extname(file.path).toLowerCase();
|
|
2407
|
+
return ext === ".md" && path8.basename(file.path).toLowerCase() !== "readme.md";
|
|
2408
|
+
});
|
|
2409
|
+
if (markdown.length === 0) return skip("no-markdown");
|
|
2410
|
+
const dated = markdown.filter((file) => path8.basename(file.path) === `${date}.md`);
|
|
2411
|
+
const chosen = dated.length > 0 ? dated[0] : markdown.length === 1 ? markdown[0] : void 0;
|
|
2412
|
+
if (chosen === void 0) return skip("ambiguous", { candidates: markdown.length });
|
|
2413
|
+
const repoPath = settings.repo.path;
|
|
2414
|
+
const resolved = path8.resolve(repoPath, chosen.path);
|
|
2415
|
+
if (!isInside(repoPath, resolved)) return skip("outside-repo", { path: chosen.path });
|
|
2416
|
+
let fileReal;
|
|
2417
|
+
try {
|
|
2418
|
+
const repoReal = realpathSync(repoPath);
|
|
2419
|
+
fileReal = realpathSync(resolved);
|
|
2420
|
+
if (!isInside(repoReal, fileReal)) return skip("symlink-outside-repo", { path: chosen.path });
|
|
2421
|
+
} catch {
|
|
2422
|
+
return skip("missing-file", { path: chosen.path });
|
|
2423
|
+
}
|
|
2424
|
+
let buffer;
|
|
2425
|
+
try {
|
|
2426
|
+
const stat2 = statSync5(fileReal);
|
|
2427
|
+
if (!stat2.isFile()) return skip("not-a-file", { path: chosen.path });
|
|
2428
|
+
if (stat2.size > PREVIEW_MAX_BYTES) return skip("too-large", { bytes: stat2.size });
|
|
2429
|
+
buffer = readFileSync3(fileReal);
|
|
2430
|
+
} catch {
|
|
2431
|
+
return skip("read-failed", { path: chosen.path });
|
|
2432
|
+
}
|
|
2433
|
+
if (buffer.includes(0)) return skip("contains-nul", { path: chosen.path });
|
|
2434
|
+
const declared = normalizeSha256(chosen.sha256);
|
|
2435
|
+
const actual = createHash2("sha256").update(buffer).digest("hex");
|
|
2436
|
+
if (declared === void 0 || declared !== actual) {
|
|
2437
|
+
return skip("sha-mismatch", { path: chosen.path, declared: declared ?? null, actual });
|
|
2438
|
+
}
|
|
2439
|
+
const text = redact(buffer.toString("utf8"), input.secrets);
|
|
2440
|
+
const stored = Buffer.from(text, "utf8");
|
|
2441
|
+
try {
|
|
2442
|
+
this.deps.store.writePreview(id, text);
|
|
2443
|
+
} catch {
|
|
2444
|
+
return skip("write-failed", { path: chosen.path });
|
|
2445
|
+
}
|
|
2446
|
+
return {
|
|
2447
|
+
file: path8.relative(repoPath, resolved).split(path8.sep).join("/"),
|
|
2448
|
+
bytes: stored.length,
|
|
2449
|
+
sha256: createHash2("sha256").update(stored).digest("hex")
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
1959
2452
|
/** 写历史与状态;同一天的 skipped 运行不覆盖 lastDigest。 */
|
|
1960
2453
|
record(summary, settings) {
|
|
1961
2454
|
const store = this.deps.store;
|
|
@@ -1965,12 +2458,20 @@ var Runner = class {
|
|
|
1965
2458
|
kind: summary.dryRun ? "dry-run" : "run",
|
|
1966
2459
|
ok: summary.ok,
|
|
1967
2460
|
skipped: summary.skipped,
|
|
1968
|
-
...summary.error === void 0 ? {} : {
|
|
2461
|
+
...summary.error === void 0 ? {} : {
|
|
2462
|
+
errorCode: summary.error.code,
|
|
2463
|
+
errorMessage: summary.error.message,
|
|
2464
|
+
...summary.error.reason === void 0 ? {} : { errorReason: summary.error.reason }
|
|
2465
|
+
},
|
|
2466
|
+
...summary.skipReason === void 0 ? {} : { skipReason: summary.skipReason },
|
|
1969
2467
|
durationMs: summary.durationMs,
|
|
1970
2468
|
files: summary.files,
|
|
2469
|
+
...summary.date === void 0 ? {} : { date: summary.date },
|
|
1971
2470
|
...summary.contentHash === void 0 ? {} : { contentHash: summary.contentHash },
|
|
1972
2471
|
...summary.branch === void 0 ? {} : { branch: summary.branch },
|
|
2472
|
+
...summary.commit === void 0 ? {} : { commit: summary.commit },
|
|
1973
2473
|
...summary.prUrl === void 0 ? {} : { prUrl: summary.prUrl },
|
|
2474
|
+
...summary.preview === void 0 ? {} : { preview: summary.preview },
|
|
1974
2475
|
coreVersion: summary.coreVersion,
|
|
1975
2476
|
model: { provider: settings.model.provider, model: settings.model.model },
|
|
1976
2477
|
languages: settings.trending.languages,
|
|
@@ -1980,11 +2481,13 @@ var Runner = class {
|
|
|
1980
2481
|
const state = store.readState();
|
|
1981
2482
|
const next = {
|
|
1982
2483
|
lastRunAt: record.at,
|
|
1983
|
-
...summary.ok
|
|
2484
|
+
...summary.ok ? { lastSuccessAt: record.at } : {},
|
|
2485
|
+
...summary.ok && summary.prUrl !== void 0 ? { pendingPr: { url: summary.prUrl, createdAt: record.at, ...summary.date === void 0 ? {} : { date: summary.date } } } : {},
|
|
1984
2486
|
...summary.ok && !summary.skipped && summary.contentHash !== void 0 ? {
|
|
1985
2487
|
lastDigest: {
|
|
1986
2488
|
date: summary.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
1987
2489
|
contentHash: summary.contentHash,
|
|
2490
|
+
contentHashAlgo: CONTENT_HASH_ALGO_V2,
|
|
1988
2491
|
coreVersion: summary.coreVersion
|
|
1989
2492
|
},
|
|
1990
2493
|
...state.pendingPr !== void 0 && summary.prUrl === void 0 ? { pendingPr: state.pendingPr } : {}
|
|
@@ -2000,172 +2503,106 @@ var Runner = class {
|
|
|
2000
2503
|
}
|
|
2001
2504
|
};
|
|
2002
2505
|
|
|
2003
|
-
// src/host/
|
|
2004
|
-
var
|
|
2005
|
-
__export(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
nextOccurrence: () => nextOccurrence,
|
|
2009
|
-
parseClock: () => parseClock,
|
|
2010
|
-
zonedDateString: () => zonedDateString,
|
|
2011
|
-
zonedTimeToUtc: () => zonedTimeToUtc
|
|
2506
|
+
// src/host/plugin-info.ts
|
|
2507
|
+
var plugin_info_exports = {};
|
|
2508
|
+
__export(plugin_info_exports, {
|
|
2509
|
+
normalizeRepositoryUrl: () => normalizeRepositoryUrl,
|
|
2510
|
+
readPluginInfo: () => readPluginInfo
|
|
2012
2511
|
});
|
|
2013
|
-
|
|
2014
|
-
var
|
|
2015
|
-
function
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
if (
|
|
2512
|
+
import { createRequire as createRequire2 } from "module";
|
|
2513
|
+
var DEFAULT_NAME = "@morehao/dsh-github-trending";
|
|
2514
|
+
function normalizeRepositoryUrl(value) {
|
|
2515
|
+
if (typeof value !== "string") return void 0;
|
|
2516
|
+
let url = value.trim();
|
|
2517
|
+
if (url === "") return void 0;
|
|
2518
|
+
url = url.replace(/^git\+/i, "");
|
|
2519
|
+
const ssh = /^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/i.exec(url);
|
|
2520
|
+
if (ssh !== null) url = `https://github.com/${ssh[1]}/${ssh[2]}`;
|
|
2521
|
+
url = url.replace(/\.git$/i, "");
|
|
2522
|
+
try {
|
|
2523
|
+
const parsed = new URL(url);
|
|
2524
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
|
|
2525
|
+
if (parsed.hostname.toLowerCase() !== "github.com") return void 0;
|
|
2526
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
2527
|
+
if (pathname === "") return void 0;
|
|
2528
|
+
return `https://github.com${pathname}`;
|
|
2529
|
+
} catch {
|
|
2530
|
+
return void 0;
|
|
2029
2531
|
}
|
|
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
2532
|
}
|
|
2058
|
-
function
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
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();
|
|
2533
|
+
function readPluginInfo() {
|
|
2534
|
+
try {
|
|
2535
|
+
const require2 = createRequire2(import.meta.url);
|
|
2536
|
+
const pkg = require2("../package.json");
|
|
2537
|
+
const repository = typeof pkg.repository === "string" ? normalizeRepositoryUrl(pkg.repository) : typeof pkg.repository === "object" && pkg.repository !== null ? normalizeRepositoryUrl(pkg.repository.url) : void 0;
|
|
2538
|
+
return {
|
|
2539
|
+
name: typeof pkg.name === "string" && pkg.name !== "" ? pkg.name : DEFAULT_NAME,
|
|
2540
|
+
version: typeof pkg.version === "string" ? pkg.version : "0.0.0",
|
|
2541
|
+
...repository === void 0 ? {} : { repository }
|
|
2099
2542
|
};
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
-
}
|
|
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";
|
|
2135
|
-
}
|
|
2543
|
+
} catch {
|
|
2544
|
+
return { name: DEFAULT_NAME, version: "0.0.0" };
|
|
2136
2545
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// src/host/github.ts
|
|
2549
|
+
var github_exports = {};
|
|
2550
|
+
__export(github_exports, {
|
|
2551
|
+
probeGithubAuth: () => probeGithubAuth
|
|
2552
|
+
});
|
|
2553
|
+
async function probeGithubAuth(options) {
|
|
2554
|
+
const env = options.env ?? process.env;
|
|
2555
|
+
const resolved = await resolveGithubToken(options.credentials, options.authMode, options.tokenRef, {
|
|
2556
|
+
env,
|
|
2557
|
+
freshGh: true
|
|
2558
|
+
});
|
|
2559
|
+
if (resolved.token === void 0 || resolved.token === "") {
|
|
2139
2560
|
return {
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2561
|
+
ok: false,
|
|
2562
|
+
code: "GITHUB_AUTH",
|
|
2563
|
+
source: resolved.source,
|
|
2564
|
+
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`
|
|
2143
2565
|
};
|
|
2144
2566
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2567
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
2568
|
+
const controller = new AbortController();
|
|
2569
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
2570
|
+
try {
|
|
2571
|
+
const response = await doFetch("https://api.github.com/user", {
|
|
2572
|
+
method: "GET",
|
|
2573
|
+
headers: {
|
|
2574
|
+
authorization: `Bearer ${resolved.token}`,
|
|
2575
|
+
accept: "application/vnd.github+json",
|
|
2576
|
+
"user-agent": "dsh-github-trending"
|
|
2577
|
+
},
|
|
2578
|
+
signal: controller.signal
|
|
2579
|
+
});
|
|
2580
|
+
if (!response.ok) {
|
|
2581
|
+
return {
|
|
2582
|
+
ok: false,
|
|
2583
|
+
code: "GITHUB_AUTH",
|
|
2584
|
+
source: resolved.source,
|
|
2585
|
+
message: `GitHub \u8FD4\u56DE HTTP ${response.status}\uFF08token \u65E0\u6548\u6216\u6743\u9650\u4E0D\u8DB3\uFF09`
|
|
2586
|
+
};
|
|
2153
2587
|
}
|
|
2154
|
-
|
|
2155
|
-
describe(state, now = /* @__PURE__ */ new Date()) {
|
|
2156
|
-
const schedule = this.readSchedule();
|
|
2157
|
-
const next = schedule.enabled ? this.next() : void 0;
|
|
2588
|
+
const body = await response.json();
|
|
2158
2589
|
return {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
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"
|
|
2590
|
+
ok: true,
|
|
2591
|
+
source: resolved.source,
|
|
2592
|
+
...typeof body.login === "string" ? { login: body.login } : {}
|
|
2166
2593
|
};
|
|
2594
|
+
} catch (error) {
|
|
2595
|
+
const aborted = error instanceof Error && error.name === "AbortError";
|
|
2596
|
+
return {
|
|
2597
|
+
ok: false,
|
|
2598
|
+
code: "GITHUB_AUTH",
|
|
2599
|
+
source: resolved.source,
|
|
2600
|
+
message: aborted ? "\u8BF7\u6C42 GitHub \u8D85\u65F6" : error instanceof Error ? error.message : String(error)
|
|
2601
|
+
};
|
|
2602
|
+
} finally {
|
|
2603
|
+
clearTimeout(timer);
|
|
2167
2604
|
}
|
|
2168
|
-
}
|
|
2605
|
+
}
|
|
2169
2606
|
|
|
2170
2607
|
// src/host/import-cli.ts
|
|
2171
2608
|
var import_cli_exports = {};
|
|
@@ -2175,7 +2612,7 @@ __export(import_cli_exports, {
|
|
|
2175
2612
|
importFromCli: () => importFromCli,
|
|
2176
2613
|
parseSimpleYaml: () => parseSimpleYaml
|
|
2177
2614
|
});
|
|
2178
|
-
import { readFileSync as
|
|
2615
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2179
2616
|
import os3 from "os";
|
|
2180
2617
|
import path9 from "path";
|
|
2181
2618
|
function cliConfigCandidates(env = process.env, home = os3.homedir()) {
|
|
@@ -2267,7 +2704,7 @@ function isSecretKey(key) {
|
|
|
2267
2704
|
function importFromCli(filePath, base = DEFAULT_SETTINGS) {
|
|
2268
2705
|
let text;
|
|
2269
2706
|
try {
|
|
2270
|
-
text =
|
|
2707
|
+
text = readFileSync4(filePath, "utf8");
|
|
2271
2708
|
} catch (error) {
|
|
2272
2709
|
return {
|
|
2273
2710
|
ok: false,
|
|
@@ -2399,6 +2836,92 @@ function importFirstAvailable(env = process.env, base) {
|
|
|
2399
2836
|
};
|
|
2400
2837
|
}
|
|
2401
2838
|
|
|
2839
|
+
// src/host/pending-pr.ts
|
|
2840
|
+
var pending_pr_exports = {};
|
|
2841
|
+
__export(pending_pr_exports, {
|
|
2842
|
+
checkPendingPr: () => checkPendingPr,
|
|
2843
|
+
clearPendingPrCache: () => clearPendingPrCache,
|
|
2844
|
+
parsePullUrl: () => parsePullUrl
|
|
2845
|
+
});
|
|
2846
|
+
function parsePullUrl(url) {
|
|
2847
|
+
const match = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+)(?:[/?#].*)?$/i.exec(url.trim());
|
|
2848
|
+
if (match === null) return void 0;
|
|
2849
|
+
const owner = match[1];
|
|
2850
|
+
const repo = match[2];
|
|
2851
|
+
const number = Number(match[3]);
|
|
2852
|
+
if (owner === void 0 || repo === void 0 || !Number.isInteger(number) || number <= 0) return void 0;
|
|
2853
|
+
return { owner, repo, number };
|
|
2854
|
+
}
|
|
2855
|
+
var CACHE_TTL_MS2 = 6e4;
|
|
2856
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2857
|
+
function clearPendingPrCache() {
|
|
2858
|
+
cache.clear();
|
|
2859
|
+
}
|
|
2860
|
+
async function checkPendingPr(options) {
|
|
2861
|
+
const { url } = options;
|
|
2862
|
+
const parsed = parsePullUrl(url);
|
|
2863
|
+
if (parsed === void 0) {
|
|
2864
|
+
return { state: "unknown", url, reason: "PR \u94FE\u63A5\u65E0\u6CD5\u89E3\u6790\uFF0C\u8DF3\u8FC7\u5BF9\u8D26" };
|
|
2865
|
+
}
|
|
2866
|
+
const now = options.now ?? Date.now();
|
|
2867
|
+
if (options.cache !== false) {
|
|
2868
|
+
const hit = cache.get(url);
|
|
2869
|
+
if (hit !== void 0 && now - hit.at < CACHE_TTL_MS2) return hit.status;
|
|
2870
|
+
}
|
|
2871
|
+
if (options.token === void 0 || options.token === "") {
|
|
2872
|
+
return { state: "unknown", url, number: parsed.number, reason: "\u6CA1\u6709\u53EF\u7528\u7684 GitHub token\uFF0C\u65E0\u6CD5\u6838\u5BF9 PR \u72B6\u6001" };
|
|
2873
|
+
}
|
|
2874
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
2875
|
+
const controller = new AbortController();
|
|
2876
|
+
const timer = setTimeout(() => controller.abort(), 8e3);
|
|
2877
|
+
try {
|
|
2878
|
+
const response = await doFetch(
|
|
2879
|
+
`https://api.github.com/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`,
|
|
2880
|
+
{
|
|
2881
|
+
method: "GET",
|
|
2882
|
+
headers: {
|
|
2883
|
+
authorization: `Bearer ${options.token}`,
|
|
2884
|
+
accept: "application/vnd.github+json",
|
|
2885
|
+
"user-agent": "dsh-github-trending"
|
|
2886
|
+
},
|
|
2887
|
+
signal: controller.signal
|
|
2888
|
+
}
|
|
2889
|
+
);
|
|
2890
|
+
if (response.status === 404) {
|
|
2891
|
+
const status2 = { state: "unknown", url, number: parsed.number, reason: "PR \u4E0D\u5B58\u5728\u6216\u65E0\u6743\u8BBF\u95EE" };
|
|
2892
|
+
cache.set(url, { at: now, status: status2 });
|
|
2893
|
+
return status2;
|
|
2894
|
+
}
|
|
2895
|
+
if (!response.ok) {
|
|
2896
|
+
const status2 = { state: "unknown", url, number: parsed.number, reason: `GitHub \u8FD4\u56DE HTTP ${response.status}` };
|
|
2897
|
+
cache.set(url, { at: now, status: status2 });
|
|
2898
|
+
return status2;
|
|
2899
|
+
}
|
|
2900
|
+
const body = await response.json();
|
|
2901
|
+
const merged = body.merged === true || typeof body.merged_at === "string" && body.merged_at !== "";
|
|
2902
|
+
const state = merged ? "merged" : body.state === "open" ? "open" : body.state === "closed" ? "closed" : "unknown";
|
|
2903
|
+
const status = {
|
|
2904
|
+
state,
|
|
2905
|
+
url,
|
|
2906
|
+
number: parsed.number,
|
|
2907
|
+
...typeof body.title === "string" ? { title: body.title } : {}
|
|
2908
|
+
};
|
|
2909
|
+
cache.set(url, { at: now, status });
|
|
2910
|
+
return status;
|
|
2911
|
+
} catch (error) {
|
|
2912
|
+
const status = {
|
|
2913
|
+
state: "unknown",
|
|
2914
|
+
url,
|
|
2915
|
+
number: parsed.number,
|
|
2916
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2917
|
+
};
|
|
2918
|
+
cache.set(url, { at: now, status });
|
|
2919
|
+
return status;
|
|
2920
|
+
} finally {
|
|
2921
|
+
clearTimeout(timer);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2402
2925
|
// src/host/preflight.ts
|
|
2403
2926
|
var preflight_exports = {};
|
|
2404
2927
|
__export(preflight_exports, {
|
|
@@ -2526,6 +3049,46 @@ async function preflightModel(options) {
|
|
|
2526
3049
|
}
|
|
2527
3050
|
}
|
|
2528
3051
|
|
|
3052
|
+
// src/host/repo.ts
|
|
3053
|
+
var repo_exports = {};
|
|
3054
|
+
__export(repo_exports, {
|
|
3055
|
+
readDefaultBranch: () => readDefaultBranch
|
|
3056
|
+
});
|
|
3057
|
+
import { statSync as statSync6 } from "fs";
|
|
3058
|
+
import path10 from "path";
|
|
3059
|
+
function isDirectory(dir) {
|
|
3060
|
+
try {
|
|
3061
|
+
return statSync6(dir).isDirectory();
|
|
3062
|
+
} catch {
|
|
3063
|
+
return false;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
async function git(dir, args, env) {
|
|
3067
|
+
try {
|
|
3068
|
+
const result = await execCapture("git", ["-C", dir, ...args], { env, timeoutMs: 5e3 });
|
|
3069
|
+
return result.code === 0 ? result.stdout.trim() : void 0;
|
|
3070
|
+
} catch {
|
|
3071
|
+
return void 0;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
async function readDefaultBranch(repoPath, options = {}) {
|
|
3075
|
+
const dir = repoPath.trim();
|
|
3076
|
+
if (dir === "") return { source: "none", message: "\u4ED3\u5E93\u8DEF\u5F84\u4E3A\u7A7A" };
|
|
3077
|
+
if (!path10.isAbsolute(dir)) return { source: "none", message: "\u4ED3\u5E93\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
|
|
3078
|
+
if (!isDirectory(dir)) return { source: "none", message: "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE" };
|
|
3079
|
+
const env = options.env ?? process.env;
|
|
3080
|
+
const origin = await git(dir, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], env);
|
|
3081
|
+
if (origin !== void 0) {
|
|
3082
|
+
const branch = origin.replace(/^origin\//, "");
|
|
3083
|
+
if (branch !== "") return { branch, source: "origin-head" };
|
|
3084
|
+
}
|
|
3085
|
+
const head = await git(dir, ["rev-parse", "--abbrev-ref", "HEAD"], env);
|
|
3086
|
+
if (head !== void 0) {
|
|
3087
|
+
if (head !== "" && head !== "HEAD") return { branch: head, source: "head" };
|
|
3088
|
+
}
|
|
3089
|
+
return { source: "none", message: "\u672A\u8BFB\u53D6\u5230\u9ED8\u8BA4\u5206\u652F\uFF08\u53EF\u80FD\u662F\u7A7A\u4ED3\u5E93\u6216\u6CA1\u6709 origin\uFF09" };
|
|
3090
|
+
}
|
|
3091
|
+
|
|
2529
3092
|
// src/host/routes.ts
|
|
2530
3093
|
var routes_exports = {};
|
|
2531
3094
|
__export(routes_exports, {
|
|
@@ -2583,7 +3146,8 @@ function createRoutes(deps) {
|
|
|
2583
3146
|
const code = error instanceof IpcError ? error.code : "INTERNAL";
|
|
2584
3147
|
const message = redact(error instanceof Error ? error.message : String(error));
|
|
2585
3148
|
deps.logger?.("route.error", { pathname, method, code, message });
|
|
2586
|
-
|
|
3149
|
+
const status = code === "CONFIG_INVALID" ? 400 : code === "NOT_FOUND" ? 404 : 500;
|
|
3150
|
+
sendJson2(response, status, { ok: false, code, message });
|
|
2587
3151
|
}
|
|
2588
3152
|
};
|
|
2589
3153
|
}
|
|
@@ -2603,10 +3167,17 @@ async function dispatch(method, pathname, url, request, deps) {
|
|
|
2603
3167
|
}
|
|
2604
3168
|
if (method === "POST" && pathname === "/config") {
|
|
2605
3169
|
const body = await readBody2(request);
|
|
2606
|
-
const
|
|
3170
|
+
const incoming = normalizeSettings(body.settings ?? body);
|
|
3171
|
+
const section = typeof body.section === "string" && SETTINGS_SECTIONS.includes(body.section) ? body.section : void 0;
|
|
3172
|
+
let next = section === void 0 ? incoming : mergeSettingsSection(settings, incoming, section);
|
|
3173
|
+
if (next.repo.path.trim() !== "" && (section === void 0 || section === "repo")) {
|
|
3174
|
+
const branch = await readDefaultBranch(next.repo.path, { env });
|
|
3175
|
+
if (branch.branch !== void 0) next.repo.baseBranch = branch.branch;
|
|
3176
|
+
}
|
|
2607
3177
|
const core = await inspectCore({ ...next, corePath: settings.corePath }, { env });
|
|
2608
3178
|
const providers = core.supportedProviders.length > 0 ? core.supportedProviders : void 0;
|
|
2609
|
-
const
|
|
3179
|
+
const allProblems = validateSettings(next, providers);
|
|
3180
|
+
const problems = section === void 0 ? allProblems : allProblems.filter((problem) => sectionOfField(problem.field) === section);
|
|
2610
3181
|
if (problems.length > 0) {
|
|
2611
3182
|
return { saved: false, problems, settings: next };
|
|
2612
3183
|
}
|
|
@@ -2636,25 +3207,52 @@ async function dispatch(method, pathname, url, request, deps) {
|
|
|
2636
3207
|
if (method === "POST" && pathname === "/run/cancel") {
|
|
2637
3208
|
return { cancelled: deps.runner.cancel() };
|
|
2638
3209
|
}
|
|
3210
|
+
if (method === "GET" && pathname === "/repo/branch") {
|
|
3211
|
+
const repoPath = url.searchParams.get("path") ?? settings.repo.path;
|
|
3212
|
+
return { repo: await readDefaultBranch(repoPath, { env }) };
|
|
3213
|
+
}
|
|
3214
|
+
if (method === "POST" && pathname === "/github/test") {
|
|
3215
|
+
const body = await readBody2(request);
|
|
3216
|
+
const candidate = body.settings === void 0 ? settings : normalizeSettings(body.settings);
|
|
3217
|
+
const probe = await probeGithubAuth({
|
|
3218
|
+
credentials: credentialServices(deps.get),
|
|
3219
|
+
authMode: candidate.github.authMode,
|
|
3220
|
+
tokenRef: candidate.github.tokenRef,
|
|
3221
|
+
env
|
|
3222
|
+
});
|
|
3223
|
+
return { github: probe };
|
|
3224
|
+
}
|
|
2639
3225
|
if (method === "GET" && pathname === "/quickfill") {
|
|
2640
3226
|
const candidates = await listProviderCandidates({
|
|
2641
3227
|
llm: llmServices(deps.get),
|
|
2642
3228
|
credentials: credentialServices(deps.get),
|
|
2643
3229
|
env
|
|
2644
3230
|
});
|
|
2645
|
-
return { providers: candidates };
|
|
3231
|
+
return { providers: candidates, defaultSelection: readDefaultSelection(deps.get) ?? null };
|
|
2646
3232
|
}
|
|
2647
3233
|
if (method === "POST" && pathname === "/quickfill") {
|
|
2648
3234
|
const body = await readBody2(request);
|
|
2649
|
-
const providerId = typeof body.providerId === "string" ? body.providerId : "";
|
|
2650
|
-
const modelId = typeof body.modelId === "string" ? body.modelId : "";
|
|
2651
3235
|
const candidates = await listProviderCandidates({
|
|
2652
3236
|
llm: llmServices(deps.get),
|
|
2653
3237
|
credentials: credentialServices(deps.get),
|
|
2654
3238
|
env
|
|
2655
3239
|
});
|
|
3240
|
+
if (body.useDefault === true) {
|
|
3241
|
+
const selection = readDefaultSelection(deps.get);
|
|
3242
|
+
if (selection === void 0) throw new IpcError("CONFIG_INVALID", "\u5BBF\u4E3B\u6CA1\u6709\u53EF\u6CBF\u7528\u7684\u9ED8\u8BA4\u6A21\u578B");
|
|
3243
|
+
const candidate2 = candidates.find((item) => item.id === selection.provider);
|
|
3244
|
+
const next2 = applyDefaultSelection(settings, selection, candidate2);
|
|
3245
|
+
deps.store.writeSettings(next2);
|
|
3246
|
+
return {
|
|
3247
|
+
saved: true,
|
|
3248
|
+
settings: next2,
|
|
3249
|
+
applied: { provider: selection.provider, model: selection.model, mode: next2.model.mode }
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
const providerId = typeof body.providerId === "string" ? body.providerId : "";
|
|
3253
|
+
const modelId = typeof body.modelId === "string" ? body.modelId : "";
|
|
2656
3254
|
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}`);
|
|
3255
|
+
if (candidate === void 0) throw new IpcError("CONFIG_INVALID", `\u5BBF\u4E3B\u6CA1\u6709\u8FD9\u4E2A\u5DF2\u914D\u7F6E\u7684 provider\uFF1A${providerId}`);
|
|
2658
3256
|
if (modelId === "") throw new IpcError("CONFIG_INVALID", "\u5FC5\u987B\u9009\u62E9\u4E00\u4E2A\u6A21\u578B");
|
|
2659
3257
|
const next = applyCandidate(settings, candidate, modelId);
|
|
2660
3258
|
deps.store.writeSettings(next);
|
|
@@ -2665,6 +3263,19 @@ async function dispatch(method, pathname, url, request, deps) {
|
|
|
2665
3263
|
if (result.ok && result.settings !== void 0) deps.store.writeSettings(result.settings);
|
|
2666
3264
|
return { import: { ...result, settings: result.settings === void 0 ? void 0 : result.settings } };
|
|
2667
3265
|
}
|
|
3266
|
+
if (method === "GET" && pathname === "/preview") {
|
|
3267
|
+
const id = url.searchParams.get("id") ?? "";
|
|
3268
|
+
if (!/^[A-Za-z0-9-]+$/.test(id)) throw new IpcError("CONFIG_INVALID", "\u975E\u6CD5\u7684\u8FD0\u884C id");
|
|
3269
|
+
const state = deps.store.readState();
|
|
3270
|
+
const history = deps.store.readHistory();
|
|
3271
|
+
const record = history.find((item) => item.id === id) ?? (state.lastResult?.id === id ? state.lastResult : void 0);
|
|
3272
|
+
if (record === void 0 || record.preview === void 0) {
|
|
3273
|
+
throw new IpcError("NOT_FOUND", "\u6CA1\u6709\u8FD9\u6B21\u8FD0\u884C\u7684\u9884\u89C8\u526F\u672C");
|
|
3274
|
+
}
|
|
3275
|
+
const preview = deps.store.readPreview(id);
|
|
3276
|
+
if (preview === void 0) throw new IpcError("NOT_FOUND", "\u9884\u89C8\u526F\u672C\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u6E05\u7406");
|
|
3277
|
+
return { id, preview };
|
|
3278
|
+
}
|
|
2668
3279
|
if (method === "GET" && pathname === "/log") {
|
|
2669
3280
|
const id = url.searchParams.get("id") ?? "";
|
|
2670
3281
|
if (!/^[A-Za-z0-9-]+$/.test(id)) throw new IpcError("CONFIG_INVALID", "\u975E\u6CD5\u7684\u8FD0\u884C id");
|
|
@@ -2678,13 +3289,33 @@ async function statePayload(deps, settings) {
|
|
|
2678
3289
|
const env = deps.env ?? process.env;
|
|
2679
3290
|
const credentials = credentialServices(deps.get);
|
|
2680
3291
|
const state = deps.store.readState();
|
|
2681
|
-
const history = deps.store.readHistory()
|
|
3292
|
+
const history = deps.store.readHistory();
|
|
2682
3293
|
const core = await inspectCore(settings, { env });
|
|
2683
3294
|
const coexistence = await detectCoexistence(core.path, { env });
|
|
2684
3295
|
const modelCredential = await describeRef(credentials, settings.model.apiKeyRef, env);
|
|
2685
3296
|
const github = await resolveGithubToken(credentials, settings.github.authMode, settings.github.tokenRef, { env });
|
|
3297
|
+
let pendingPr = state.pendingPr ?? null;
|
|
3298
|
+
let pendingPrState;
|
|
3299
|
+
if (pendingPr !== null) {
|
|
3300
|
+
const status = await checkPendingPr({
|
|
3301
|
+
url: pendingPr.url,
|
|
3302
|
+
token: github.token,
|
|
3303
|
+
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl }
|
|
3304
|
+
});
|
|
3305
|
+
pendingPrState = status.state;
|
|
3306
|
+
if (status.state === "merged" || status.state === "closed") {
|
|
3307
|
+
const next = { ...state };
|
|
3308
|
+
delete next.pendingPr;
|
|
3309
|
+
deps.store.writeState(next);
|
|
3310
|
+
pendingPr = null;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
2686
3313
|
return {
|
|
2687
|
-
plugin: {
|
|
3314
|
+
plugin: {
|
|
3315
|
+
name: deps.pluginName ?? "@morehao/dsh-github-trending",
|
|
3316
|
+
version: deps.version,
|
|
3317
|
+
...deps.repository === void 0 ? {} : { repository: deps.repository }
|
|
3318
|
+
},
|
|
2688
3319
|
settings,
|
|
2689
3320
|
core,
|
|
2690
3321
|
coexistence,
|
|
@@ -2694,7 +3325,8 @@ async function statePayload(deps, settings) {
|
|
|
2694
3325
|
lastRunAt: state.lastRunAt ?? null,
|
|
2695
3326
|
lastDigest: state.lastDigest ?? null,
|
|
2696
3327
|
lastResult: state.lastResult ?? null,
|
|
2697
|
-
pendingPr
|
|
3328
|
+
pendingPr,
|
|
3329
|
+
...pendingPrState === void 0 ? {} : { pendingPrState },
|
|
2698
3330
|
history
|
|
2699
3331
|
},
|
|
2700
3332
|
model: {
|
|
@@ -2792,7 +3424,7 @@ function createTrendingRunTool(runner) {
|
|
|
2792
3424
|
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
3425
|
parameters: {
|
|
2794
3426
|
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" }
|
|
3427
|
+
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
3428
|
},
|
|
2797
3429
|
output: {
|
|
2798
3430
|
schema: RUN_OUTPUT_SCHEMA,
|
|
@@ -2894,12 +3526,17 @@ export {
|
|
|
2894
3526
|
credentials_exports,
|
|
2895
3527
|
quickfill_exports,
|
|
2896
3528
|
proxy_exports,
|
|
2897
|
-
Runner,
|
|
2898
|
-
runner_exports,
|
|
2899
3529
|
Scheduler,
|
|
2900
3530
|
schedule_exports,
|
|
3531
|
+
Runner,
|
|
3532
|
+
runner_exports,
|
|
3533
|
+
readPluginInfo,
|
|
3534
|
+
plugin_info_exports,
|
|
3535
|
+
github_exports,
|
|
2901
3536
|
import_cli_exports,
|
|
3537
|
+
pending_pr_exports,
|
|
2902
3538
|
preflight_exports,
|
|
3539
|
+
repo_exports,
|
|
2903
3540
|
ROUTE_PREFIX,
|
|
2904
3541
|
createRoutes,
|
|
2905
3542
|
routes_exports,
|