@trim21/personal-pi-extensions 0.1.521 → 0.1.523
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/package.json +1 -1
- package/src/aft/bridge.ts +1 -1
- package/src/gh-readonly.ts +158 -48
- package/src/lib/github.ts +9 -0
package/package.json
CHANGED
package/src/aft/bridge.ts
CHANGED
|
@@ -59,7 +59,7 @@ export async function createAftState(
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** semantic_search 等待索引构建完成的上限(与 Rust 侧 AFT_WAIT_FOR_SEMANTIC_READY_MS 一致)。 */
|
|
62
|
-
export const SEMANTIC_INDEX_WAIT_TIMEOUT_MS =
|
|
62
|
+
export const SEMANTIC_INDEX_WAIT_TIMEOUT_MS = 3_600_000;
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* 调用图存储未就绪时查询侧的内联等待窗口。Rust 侧默认 0(纯异步:立即返回
|
package/src/gh-readonly.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - list-github-releases: List releases
|
|
20
20
|
* - read-github-release: Get release details
|
|
21
21
|
* - wait-github-pr-checks: Watch PR CI checks
|
|
22
|
+
* - wait-github-commit-checks: Watch CI checks of a commit (no PR required)
|
|
22
23
|
* - watch-github-run: Watch a workflow run
|
|
23
24
|
*
|
|
24
25
|
* Install:
|
|
@@ -243,6 +244,15 @@ function repoArgs(repo?: string): string[] {
|
|
|
243
244
|
return repo ? ["--repo", repo] : [];
|
|
244
245
|
}
|
|
245
246
|
|
|
247
|
+
/** Split `OWNER/REPO`; throws when the name doesn't have exactly one slash. */
|
|
248
|
+
function splitRepo(nameWithOwner: string): { owner: string; repo: string } {
|
|
249
|
+
const slash = nameWithOwner.indexOf("/");
|
|
250
|
+
if (slash <= 0 || slash === nameWithOwner.length - 1 || nameWithOwner.includes("/", slash + 1)) {
|
|
251
|
+
throw new Error(`invalid repository: ${nameWithOwner} (expected OWNER/REPO)`);
|
|
252
|
+
}
|
|
253
|
+
return { owner: nameWithOwner.slice(0, slash), repo: nameWithOwner.slice(slash + 1) };
|
|
254
|
+
}
|
|
255
|
+
|
|
246
256
|
// ── runtime validation schemas for JSON.parse results ───────────────────────
|
|
247
257
|
|
|
248
258
|
const repoViewSchema = Type.Object({ nameWithOwner: Type.String() });
|
|
@@ -1207,11 +1217,12 @@ export function checkDisplayName(check: MergedCheck): string {
|
|
|
1207
1217
|
* Pure — no network.
|
|
1208
1218
|
*/
|
|
1209
1219
|
export function renderPrChecksList(options: {
|
|
1210
|
-
|
|
1220
|
+
/** Report subject, e.g. `PR #7` or `commit 5a7c407`. */
|
|
1221
|
+
subject: string;
|
|
1211
1222
|
round: number;
|
|
1212
1223
|
checks: readonly MergedCheck[];
|
|
1213
1224
|
}): string {
|
|
1214
|
-
const {
|
|
1225
|
+
const { subject, round, checks } = options;
|
|
1215
1226
|
const completed = checks.filter((c) => c.bucket !== "pending").length;
|
|
1216
1227
|
|
|
1217
1228
|
const pending = checks.filter((c) => c.bucket === "pending");
|
|
@@ -1225,7 +1236,7 @@ export function renderPrChecksList(options: {
|
|
|
1225
1236
|
const body =
|
|
1226
1237
|
checks.length === 0 ? "- _no checks reported_" : lines.length > 0 ? lines.join("\n") : "";
|
|
1227
1238
|
|
|
1228
|
-
return
|
|
1239
|
+
return `${subject} checks — round ${round}: ${completed}/${checks.length} complete${body ? `\n\n${body}` : ""}`;
|
|
1229
1240
|
}
|
|
1230
1241
|
|
|
1231
1242
|
function sleepInterruptibly(ms: number, signal: AbortSignal | undefined): Promise<void> {
|
|
@@ -1255,12 +1266,19 @@ export interface ChecksPollResult {
|
|
|
1255
1266
|
}
|
|
1256
1267
|
|
|
1257
1268
|
export interface PollPrChecksOptions {
|
|
1258
|
-
|
|
1269
|
+
/** Report subject for progress lines, e.g. `PR #7` or `commit 5a7c407`. */
|
|
1270
|
+
subject: string;
|
|
1259
1271
|
owner: string;
|
|
1260
1272
|
repo: string;
|
|
1261
1273
|
headSha: string;
|
|
1262
1274
|
failFast: boolean;
|
|
1263
1275
|
checks: GithubChecksClient;
|
|
1276
|
+
/**
|
|
1277
|
+
* When set, only check runs triggered by this workflow event (e.g. push)
|
|
1278
|
+
* are judged; commit statuses have an unknown trigger event and are
|
|
1279
|
+
* excluded. Unset means all checks of the commit.
|
|
1280
|
+
*/
|
|
1281
|
+
event?: string;
|
|
1264
1282
|
/** Owned by the caller; the poll loop observes it but never aborts it. */
|
|
1265
1283
|
signal: AbortSignal;
|
|
1266
1284
|
/** Test overrides. */
|
|
@@ -1281,7 +1299,7 @@ export interface PollPrChecksOptions {
|
|
|
1281
1299
|
* when no round ever succeeded by the deadline is the last error thrown.
|
|
1282
1300
|
*/
|
|
1283
1301
|
export async function pollPrChecks(options: PollPrChecksOptions): Promise<ChecksPollResult> {
|
|
1284
|
-
const {
|
|
1302
|
+
const { subject, owner, repo, headSha, failFast, checks, signal, onUpdate } = options;
|
|
1285
1303
|
const intervalMs = options.intervalMs ?? CHECKS_POLL_INTERVAL_MS;
|
|
1286
1304
|
const deadlineMs = options.deadlineMs ?? CHECKS_WATCH_DEADLINE_MS;
|
|
1287
1305
|
|
|
@@ -1299,9 +1317,12 @@ export async function pollPrChecks(options: PollPrChecksOptions): Promise<Checks
|
|
|
1299
1317
|
]);
|
|
1300
1318
|
everSucceeded = true;
|
|
1301
1319
|
lastChecks = mergeChecks(statuses, runs);
|
|
1320
|
+
if (options.event) {
|
|
1321
|
+
lastChecks = lastChecks.filter((c) => c.event === options.event);
|
|
1322
|
+
}
|
|
1302
1323
|
onUpdate?.({
|
|
1303
1324
|
content: [
|
|
1304
|
-
{ type: "text", text: renderPrChecksList({
|
|
1325
|
+
{ type: "text", text: renderPrChecksList({ subject, round, checks: lastChecks }) },
|
|
1305
1326
|
],
|
|
1306
1327
|
details: {},
|
|
1307
1328
|
});
|
|
@@ -1351,14 +1372,15 @@ export interface ChecksVerdict {
|
|
|
1351
1372
|
* display-only enrichment. Pure — no network.
|
|
1352
1373
|
*/
|
|
1353
1374
|
export function renderChecksVerdict(options: {
|
|
1354
|
-
|
|
1375
|
+
/** Report subject, e.g. `PR #123` or `commit 5a7c407`. */
|
|
1376
|
+
subject: string;
|
|
1355
1377
|
poll: ChecksPollResult;
|
|
1356
1378
|
/** All Actions jobs of the head commit; failed/incomplete ones are listed. */
|
|
1357
1379
|
actionJobs?: readonly ActionJob[];
|
|
1358
1380
|
/** Set when the Actions job fetch failed; the verdict stays untouched. */
|
|
1359
1381
|
enrichmentError?: string;
|
|
1360
1382
|
}): ChecksVerdict {
|
|
1361
|
-
const {
|
|
1383
|
+
const { subject, poll, actionJobs, enrichmentError } = options;
|
|
1362
1384
|
const totalChecks = poll.checks.length;
|
|
1363
1385
|
const failed = poll.checks.filter((c) => c.bucket === "fail");
|
|
1364
1386
|
const pending = poll.checks.filter((c) => c.bucket === "pending");
|
|
@@ -1366,7 +1388,7 @@ export function renderChecksVerdict(options: {
|
|
|
1366
1388
|
if (failed.length === 0 && poll.outcome === "completed") {
|
|
1367
1389
|
return {
|
|
1368
1390
|
status: "success",
|
|
1369
|
-
text: `##
|
|
1391
|
+
text: `## ${subject} CI Checks - PASSED\n\nAll ${totalChecks} check(s) passed.`,
|
|
1370
1392
|
failedJobs: [],
|
|
1371
1393
|
};
|
|
1372
1394
|
}
|
|
@@ -1400,7 +1422,7 @@ export function renderChecksVerdict(options: {
|
|
|
1400
1422
|
return {
|
|
1401
1423
|
status: "failure",
|
|
1402
1424
|
text:
|
|
1403
|
-
`##
|
|
1425
|
+
`## ${subject} CI Checks - FAILED\n\n` +
|
|
1404
1426
|
`${failed.length} of ${totalChecks} check(s) failed:\n\n${lines.join("\n")}`,
|
|
1405
1427
|
failedJobs,
|
|
1406
1428
|
};
|
|
@@ -1413,7 +1435,7 @@ export function renderChecksVerdict(options: {
|
|
|
1413
1435
|
return {
|
|
1414
1436
|
status: "pending",
|
|
1415
1437
|
text:
|
|
1416
|
-
`##
|
|
1438
|
+
`## ${subject} CI Checks - STILL IN FLIGHT\n\n` +
|
|
1417
1439
|
`${pending.length} of ${totalChecks} check(s) still incomplete after ~${waitedMinutes}m:\n\n` +
|
|
1418
1440
|
(pendingLines.length > 0 ? pendingLines.join("\n") : "- _no checks reported_"),
|
|
1419
1441
|
failedJobs: [],
|
|
@@ -1448,6 +1470,69 @@ export default function ghReadonlyTools(pi: ExtensionAPI) {
|
|
|
1448
1470
|
const githubSearch = createGithubSearch();
|
|
1449
1471
|
const githubChecks = createGithubChecks();
|
|
1450
1472
|
|
|
1473
|
+
/**
|
|
1474
|
+
* Shared wait core of `wait-github-pr-checks` and
|
|
1475
|
+
* `wait-github-commit-checks`: poll the commit's checks, enrich FAILED
|
|
1476
|
+
* reports with Actions job details (display-only), render the verdict.
|
|
1477
|
+
* The caller resolves repo/headSha; `subject` formats the report header.
|
|
1478
|
+
*/
|
|
1479
|
+
async function waitChecksReport(options: {
|
|
1480
|
+
subject: string;
|
|
1481
|
+
owner: string;
|
|
1482
|
+
repo: string;
|
|
1483
|
+
headSha: string;
|
|
1484
|
+
failFast: boolean;
|
|
1485
|
+
event?: string;
|
|
1486
|
+
signal: AbortSignal | undefined;
|
|
1487
|
+
onUpdate: ((msg: CiLogsResult) => void) | undefined;
|
|
1488
|
+
params: unknown;
|
|
1489
|
+
pendant?: ToolPendant;
|
|
1490
|
+
}) {
|
|
1491
|
+
const { subject, owner, repo, headSha, failFast, event, signal, onUpdate, params, pendant } =
|
|
1492
|
+
options;
|
|
1493
|
+
|
|
1494
|
+
// 轮询层要求非空 signal;框架可能不给时构造一个占位的(从不取消)。
|
|
1495
|
+
const pollSignal = signal ?? new AbortController().signal;
|
|
1496
|
+
|
|
1497
|
+
const poll = await pollPrChecks({
|
|
1498
|
+
subject,
|
|
1499
|
+
owner,
|
|
1500
|
+
repo,
|
|
1501
|
+
headSha,
|
|
1502
|
+
failFast,
|
|
1503
|
+
event,
|
|
1504
|
+
checks: githubChecks,
|
|
1505
|
+
signal: pollSignal,
|
|
1506
|
+
onUpdate,
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
// Actions job 详情只做展示补充,不影响判定(判定来自 checks bucket,
|
|
1510
|
+
// 覆盖 Azure 等外部 CI)。抓取失败时降级为提示,不推翻结论。
|
|
1511
|
+
let actionJobs: readonly ActionJob[] | undefined;
|
|
1512
|
+
let enrichmentError: string | undefined;
|
|
1513
|
+
if (poll.checks.some((c) => c.bucket === "fail")) {
|
|
1514
|
+
try {
|
|
1515
|
+
actionJobs = await githubChecks.actionJobs(owner, repo, headSha, pollSignal);
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
enrichmentError =
|
|
1518
|
+
error instanceof Error ? error.message : "Actions job details unavailable";
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
const verdict = renderChecksVerdict({ subject, poll, actionJobs, enrichmentError });
|
|
1523
|
+
return {
|
|
1524
|
+
content: [{ type: "text" as const, text: verdict.text }],
|
|
1525
|
+
details: {
|
|
1526
|
+
status: verdict.status,
|
|
1527
|
+
totalChecks: poll.checks.length,
|
|
1528
|
+
checks: poll.checks,
|
|
1529
|
+
failedJobs: verdict.failedJobs,
|
|
1530
|
+
input: params,
|
|
1531
|
+
...(pendant && { pendant }),
|
|
1532
|
+
},
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1451
1536
|
// ── read-github-issue ──────────────────────────────────────────────────────
|
|
1452
1537
|
pi.registerTool({
|
|
1453
1538
|
name: "read-github-issue",
|
|
@@ -1999,12 +2084,7 @@ export default function ghReadonlyTools(pi: ExtensionAPI) {
|
|
|
1999
2084
|
});
|
|
2000
2085
|
|
|
2001
2086
|
const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
|
|
2002
|
-
const
|
|
2003
|
-
if (slash <= 0 || slash === effectiveRepo.length - 1) {
|
|
2004
|
-
throw new Error(`invalid repository: ${effectiveRepo} (expected OWNER/REPO)`);
|
|
2005
|
-
}
|
|
2006
|
-
const owner = effectiveRepo.slice(0, slash);
|
|
2007
|
-
const repoName = effectiveRepo.slice(slash + 1);
|
|
2087
|
+
const { owner, repo: repoName } = splitRepo(effectiveRepo);
|
|
2008
2088
|
|
|
2009
2089
|
const prOut = await ghExec(
|
|
2010
2090
|
["pr", "view", String(number), "--repo", effectiveRepo, "--json", "headRefOid"],
|
|
@@ -2012,45 +2092,75 @@ export default function ghReadonlyTools(pi: ExtensionAPI) {
|
|
|
2012
2092
|
);
|
|
2013
2093
|
const { headRefOid } = Value.Parse(prHeadSchema, JSON.parse(prOut));
|
|
2014
2094
|
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
const poll = await pollPrChecks({
|
|
2019
|
-
prNumber: number,
|
|
2095
|
+
return waitChecksReport({
|
|
2096
|
+
subject: `PR #${number}`,
|
|
2020
2097
|
owner,
|
|
2021
2098
|
repo: repoName,
|
|
2022
2099
|
headSha: headRefOid,
|
|
2023
2100
|
failFast: fail_fast === true,
|
|
2024
|
-
|
|
2025
|
-
signal: pollSignal,
|
|
2101
|
+
signal,
|
|
2026
2102
|
onUpdate,
|
|
2103
|
+
params,
|
|
2104
|
+
pendant,
|
|
2027
2105
|
});
|
|
2106
|
+
},
|
|
2107
|
+
});
|
|
2028
2108
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2109
|
+
// ── wait-github-commit-checks ──────────────────────────────────────────────
|
|
2110
|
+
pi.registerTool({
|
|
2111
|
+
name: "wait-github-commit-checks",
|
|
2112
|
+
label: "Watch GitHub Commit Checks",
|
|
2113
|
+
description:
|
|
2114
|
+
"Watch CI status checks for a commit until they complete — no pull request required. " +
|
|
2115
|
+
"Same semantics as wait-github-pr-checks: returns when any check fails (immediately under fail_fast) " +
|
|
2116
|
+
"or all checks pass/skip; on timeout the still-in-flight snapshot is returned. " +
|
|
2117
|
+
"With `event`, only check runs triggered by that workflow event (e.g. push) are judged; " +
|
|
2118
|
+
"commit statuses have an unknown trigger event and are excluded under a filter. " +
|
|
2119
|
+
"Use this to wait for the runs a commit's push triggered, or for checks on an arbitrary ref.",
|
|
2120
|
+
promptSnippet: "Watch and wait for GitHub commit CI checks to complete",
|
|
2121
|
+
parameters: Type.Object({
|
|
2122
|
+
commit: Type.Union([Type.Number(), Type.String()], {
|
|
2123
|
+
description:
|
|
2124
|
+
"Commit to wait for: full or partial SHA, branch name, or tag name (resolved to the commit's SHA)",
|
|
2125
|
+
}),
|
|
2126
|
+
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
2127
|
+
event: Type.Optional(
|
|
2128
|
+
Type.String({
|
|
2129
|
+
description:
|
|
2130
|
+
"Only judge check runs triggered by this workflow event (e.g. push, pull_request)",
|
|
2131
|
+
}),
|
|
2132
|
+
),
|
|
2133
|
+
fail_fast: Type.Optional(
|
|
2134
|
+
Type.Boolean({ description: "Exit immediately when any check fails (default: false)" }),
|
|
2135
|
+
),
|
|
2136
|
+
}),
|
|
2137
|
+
async execute(_id, params, signal, onUpdate, ctx) {
|
|
2138
|
+
const { commit, repo, event, fail_fast } = params;
|
|
2041
2139
|
|
|
2042
|
-
const
|
|
2043
|
-
|
|
2044
|
-
content: [{ type: "text", text:
|
|
2045
|
-
details: {
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2140
|
+
const pendant = subtitlePendant(params, "commit");
|
|
2141
|
+
onUpdate?.({
|
|
2142
|
+
content: [{ type: "text", text: `Watching CI checks for commit ${commit}...` }],
|
|
2143
|
+
details: {},
|
|
2144
|
+
});
|
|
2145
|
+
|
|
2146
|
+
const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
|
|
2147
|
+
const { owner, repo: repoName } = splitRepo(effectiveRepo);
|
|
2148
|
+
|
|
2149
|
+
const pollSignal = signal ?? new AbortController().signal;
|
|
2150
|
+
const sha = await githubChecks.headSha(owner, repoName, String(commit), pollSignal);
|
|
2151
|
+
|
|
2152
|
+
return waitChecksReport({
|
|
2153
|
+
subject: `commit ${sha.slice(0, 7)}`,
|
|
2154
|
+
owner,
|
|
2155
|
+
repo: repoName,
|
|
2156
|
+
headSha: sha,
|
|
2157
|
+
failFast: fail_fast === true,
|
|
2158
|
+
event,
|
|
2159
|
+
signal,
|
|
2160
|
+
onUpdate,
|
|
2161
|
+
params,
|
|
2162
|
+
pendant,
|
|
2163
|
+
});
|
|
2054
2164
|
},
|
|
2055
2165
|
});
|
|
2056
2166
|
|
package/src/lib/github.ts
CHANGED
|
@@ -367,6 +367,8 @@ export interface GithubChecksClient {
|
|
|
367
367
|
headSha: string,
|
|
368
368
|
signal: AbortSignal,
|
|
369
369
|
): Promise<readonly ActionJob[]>;
|
|
370
|
+
/** Resolve a SHA, branch name, or tag name to the commit's full SHA. */
|
|
371
|
+
headSha(owner: string, repo: string, ref: string, signal: AbortSignal): Promise<string>;
|
|
370
372
|
}
|
|
371
373
|
|
|
372
374
|
/**
|
|
@@ -471,5 +473,12 @@ export function createGithubChecks(): GithubChecksClient {
|
|
|
471
473
|
}
|
|
472
474
|
return jobs;
|
|
473
475
|
},
|
|
476
|
+
|
|
477
|
+
async headSha(owner, repo, ref, signal) {
|
|
478
|
+
const { data } = await api.call((octokit) =>
|
|
479
|
+
octokit.rest.repos.getCommit({ owner, repo, ref, request: { signal } }),
|
|
480
|
+
);
|
|
481
|
+
return data.sha;
|
|
482
|
+
},
|
|
474
483
|
};
|
|
475
484
|
}
|