@zhin.js/adapter-github 1.0.1 → 1.1.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 +1118 -0
- package/README.md +70 -191
- package/adapters/github.js +51 -0
- package/adapters/github.ts +59 -0
- package/agent/prompt-sections/platform.ts +16 -0
- package/{skills/github/SKILL.md → agent/skills/github.md} +22 -5
- package/agent/tools/bind.ts +13 -0
- package/agent/tools/create_pr.ts +20 -0
- package/agent/tools/install.ts +13 -0
- package/agent/tools/patch_file.ts +19 -0
- package/agent/tools/prepare_workspace.ts +15 -0
- package/agent/tools/push_branch.ts +18 -0
- package/agent/tools/star.ts +16 -0
- package/agent/tools/subscribe.ts +16 -0
- package/agent/tools/subscriptions.ts +13 -0
- package/agent/tools/unbind.ts +13 -0
- package/agent/tools/unsubscribe.ts +15 -0
- package/agent/tools/whoami.ts +13 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +36 -0
- package/lib/client.js +47 -0
- package/lib/endpoint.d.ts +32 -22
- package/lib/endpoint.js +125 -145
- package/lib/gh-client.d.ts +73 -1
- package/lib/gh-client.js +99 -1
- package/lib/github-bot-handlers.d.ts +27 -0
- package/lib/github-bot-handlers.js +76 -0
- package/lib/github-channel-context.d.ts +16 -0
- package/lib/github-channel-context.js +31 -0
- package/lib/github-endpoint-commands.d.ts +1 -0
- package/lib/github-endpoint-commands.js +22 -0
- package/lib/github-runtime-state.d.ts +1 -0
- package/lib/github-runtime-state.js +6 -0
- package/lib/github-tool-handlers.d.ts +18 -0
- package/lib/github-tool-handlers.js +214 -0
- package/lib/index.d.ts +7 -32
- package/lib/index.js +7 -385
- package/lib/oauth-users.d.ts +33 -0
- package/lib/oauth-users.js +38 -0
- package/lib/protocol.d.ts +94 -0
- package/lib/protocol.js +292 -0
- package/lib/types.d.ts +6 -1
- package/lib/types.js +0 -1
- package/lib/webhook.d.ts +14 -0
- package/lib/webhook.js +88 -0
- package/lib/workspace-manager.d.ts +21 -0
- package/lib/workspace-manager.js +154 -0
- package/package.json +77 -23
- package/plugin.js +38 -0
- package/schema.json +130 -0
- package/src/client.ts +65 -0
- package/src/endpoint.ts +145 -150
- package/src/gh-client.ts +131 -0
- package/src/github-bot-handlers.ts +113 -0
- package/src/github-channel-context.ts +46 -0
- package/src/github-endpoint-commands.ts +23 -0
- package/src/github-runtime-state.ts +7 -0
- package/src/github-tool-handlers.ts +252 -0
- package/src/index.ts +48 -431
- package/src/oauth-users.ts +47 -0
- package/src/protocol.ts +425 -0
- package/src/types.ts +6 -0
- package/src/webhook.ts +130 -0
- package/src/workspace-manager.ts +168 -0
- package/lib/adapter.d.ts +0 -64
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -416
- package/lib/adapter.js.map +0 -1
- package/lib/agent-prompt.d.ts +0 -3
- package/lib/agent-prompt.d.ts.map +0 -1
- package/lib/agent-prompt.js +0 -82
- package/lib/agent-prompt.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/gh-client.d.ts.map +0 -1
- package/lib/gh-client.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/register-github-mcp.d.ts +0 -6
- package/lib/register-github-mcp.d.ts.map +0 -1
- package/lib/register-github-mcp.js +0 -35
- package/lib/register-github-mcp.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -448
- package/src/agent-prompt.ts +0 -99
- package/src/register-github-mcp.ts +0 -60
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeGithubUnsubscribe } from '../../src/github-tool-handlers.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ repo: string }>({
|
|
6
|
+
description: '取消订阅 GitHub 仓库的 Webhook 事件',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
repo: z.string().min(1),
|
|
9
|
+
}),
|
|
10
|
+
adapter: 'github',
|
|
11
|
+
tags: ['github'],
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
return executeGithubUnsubscribe(input, context.$client, context.message);
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeGithubWhoami } from '../../src/github-tool-handlers.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{}>({
|
|
6
|
+
description: '查看你绑定的 GitHub 账号信息',
|
|
7
|
+
adapter: 'github',
|
|
8
|
+
inputSchema: z.object({}),
|
|
9
|
+
tags: ['github'],
|
|
10
|
+
async execute(input, context) {
|
|
11
|
+
return executeGithubWhoami({}, context.$client, context.message);
|
|
12
|
+
},
|
|
13
|
+
});
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PluginDatabaseHost } from 'zhin.js';
|
|
2
|
+
import { GhClient } from './gh-client.js';
|
|
3
|
+
import type { ResolvedGithubConfig } from './protocol.js';
|
|
4
|
+
import { WorkspaceManager } from './workspace-manager.js';
|
|
5
|
+
/** GitHub SDK surface exposed to plugin handlers and Agent tools. */
|
|
6
|
+
export declare class GithubClient {
|
|
7
|
+
#private;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly api: GhClient;
|
|
10
|
+
readonly config: ResolvedGithubConfig;
|
|
11
|
+
readonly database?: PluginDatabaseHost | undefined;
|
|
12
|
+
constructor(name: string, api: GhClient, config: ResolvedGithubConfig, database?: PluginDatabaseHost | undefined);
|
|
13
|
+
getUserOrDefaultApi(platform?: string, platformUid?: string): Promise<GhClient>;
|
|
14
|
+
get clientId(): string | null;
|
|
15
|
+
get host(): string | undefined;
|
|
16
|
+
get appSlug(): string | null;
|
|
17
|
+
get installations(): {
|
|
18
|
+
id: number;
|
|
19
|
+
account: {
|
|
20
|
+
login: string;
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
23
|
+
target_type: string;
|
|
24
|
+
}[];
|
|
25
|
+
get workspaceManager(): WorkspaceManager;
|
|
26
|
+
}
|
|
27
|
+
export type GithubClientEventMap = Record<string, unknown>;
|
|
28
|
+
declare module '@zhin.js/feature-kit' {
|
|
29
|
+
interface AdapterClientRegistry {
|
|
30
|
+
readonly github: {
|
|
31
|
+
readonly client: GithubClient;
|
|
32
|
+
readonly events: GithubClientEventMap;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export declare const githubClient: import("@zhin.js/adapter").EndpointClientToken<GithubClient, GithubClientEventMap>;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
3
|
+
import { lookupGithubOauthAccessToken } from './oauth-users.js';
|
|
4
|
+
import { WorkspaceManager } from './workspace-manager.js';
|
|
5
|
+
/** GitHub SDK surface exposed to plugin handlers and Agent tools. */
|
|
6
|
+
export class GithubClient {
|
|
7
|
+
name;
|
|
8
|
+
api;
|
|
9
|
+
config;
|
|
10
|
+
database;
|
|
11
|
+
#workspaceManager;
|
|
12
|
+
constructor(name, api, config, database) {
|
|
13
|
+
this.name = name;
|
|
14
|
+
this.api = api;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.database = database;
|
|
17
|
+
}
|
|
18
|
+
async getUserOrDefaultApi(platform, platformUid) {
|
|
19
|
+
if (platform && platformUid) {
|
|
20
|
+
const token = await lookupGithubOauthAccessToken(this.database, platform, platformUid);
|
|
21
|
+
if (token)
|
|
22
|
+
return this.api.withToken(token);
|
|
23
|
+
}
|
|
24
|
+
return this.api;
|
|
25
|
+
}
|
|
26
|
+
get clientId() {
|
|
27
|
+
return this.api.clientId || null;
|
|
28
|
+
}
|
|
29
|
+
get host() {
|
|
30
|
+
return this.config.host;
|
|
31
|
+
}
|
|
32
|
+
get appSlug() {
|
|
33
|
+
return this.api.appSlug || null;
|
|
34
|
+
}
|
|
35
|
+
get installations() {
|
|
36
|
+
return this.api.installations || [];
|
|
37
|
+
}
|
|
38
|
+
get workspaceManager() {
|
|
39
|
+
if (!this.#workspaceManager) {
|
|
40
|
+
const root = this.config.workspaceRoot
|
|
41
|
+
?? path.join(process.cwd(), 'data', 'github-workspaces');
|
|
42
|
+
this.#workspaceManager = new WorkspaceManager(this.api, root);
|
|
43
|
+
}
|
|
44
|
+
return this.#workspaceManager;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const githubClient = defineEndpointClient('github');
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* GithubEndpoint — lifecycle, outbound send, inbound admit.
|
|
3
4
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
5
|
+
import { type EndpointSendRequest } from 'zhin.js/adapter';
|
|
6
|
+
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
|
+
import type { CapabilityId, PluginDatabaseHost } from 'zhin.js';
|
|
7
8
|
import { GhClient } from './gh-client.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
get logger(): import("zhin.js").Logger;
|
|
17
|
-
constructor(adapter: GitHubAdapter, $config: GitHubEndpointConfig);
|
|
18
|
-
$connect(): Promise<void>;
|
|
19
|
-
$disconnect(): Promise<void>;
|
|
20
|
-
$formatMessage(payload: IssueCommentPayload): Message<IssueCommentPayload>;
|
|
21
|
-
formatPRReviewComment(payload: PRReviewCommentPayload): Message<PRReviewCommentPayload>;
|
|
22
|
-
formatPRReview(payload: PRReviewPayload): Message<PRReviewPayload> | null;
|
|
23
|
-
$sendMessage(options: SendOptions): Promise<string>;
|
|
24
|
-
$recallMessage(id: string): Promise<void>;
|
|
9
|
+
import { type GithubInboundComment, type ResolvedGithubConfig } from './protocol.js';
|
|
10
|
+
import { GithubClient } from './client.js';
|
|
11
|
+
export interface GithubEndpointOptions {
|
|
12
|
+
readonly id: CapabilityId;
|
|
13
|
+
readonly http?: HttpHost;
|
|
14
|
+
readonly database?: PluginDatabaseHost;
|
|
15
|
+
readonly config: ResolvedGithubConfig;
|
|
16
|
+
readonly createClient?: (config: ResolvedGithubConfig) => GhClient;
|
|
25
17
|
}
|
|
26
|
-
|
|
18
|
+
/**
|
|
19
|
+
* GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
|
|
20
|
+
* 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
|
|
21
|
+
*/
|
|
22
|
+
export declare class GithubEndpoint extends Endpoint<GithubClient> {
|
|
23
|
+
#private;
|
|
24
|
+
readonly client: GithubClient;
|
|
25
|
+
constructor(options: GithubEndpointOptions);
|
|
26
|
+
get config(): ResolvedGithubConfig;
|
|
27
|
+
start(): Promise<void>;
|
|
28
|
+
open(): void;
|
|
29
|
+
close(): void;
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
32
|
+
/** Test / internal: admit a parsed comment when open. */
|
|
33
|
+
admit(comment: GithubInboundComment): void;
|
|
34
|
+
admitPlatform(name: string, event: unknown): void;
|
|
35
|
+
}
|
|
36
|
+
export declare function defaultCreateClient(config: ResolvedGithubConfig): GhClient;
|
package/lib/endpoint.js
CHANGED
|
@@ -1,160 +1,140 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
3
|
+
import { GhClient } from './gh-client.js';
|
|
4
|
+
import { enrichInboundContent, formatInboundContent, formatOutboundBody, githubInboundConversation, parseChannelId, } from './protocol.js';
|
|
5
|
+
import { registerGithubWebhookRoutes } from './webhook.js';
|
|
6
|
+
import { GithubClient } from './client.js';
|
|
1
7
|
/**
|
|
2
|
-
* GitHub
|
|
8
|
+
* GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
|
|
9
|
+
* 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
|
|
3
10
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
if (lastIdx < md.length)
|
|
19
|
-
segments.push({ type: 'text', data: { text: md.slice(lastIdx) } });
|
|
20
|
-
return segments.length ? segments : [{ type: 'text', data: { text: md } }];
|
|
21
|
-
}
|
|
22
|
-
export function toMarkdown(content) {
|
|
23
|
-
if (!Array.isArray(content))
|
|
24
|
-
content = [content];
|
|
25
|
-
return content.map(seg => {
|
|
26
|
-
if (typeof seg === 'string')
|
|
27
|
-
return seg;
|
|
28
|
-
switch (seg.type) {
|
|
29
|
-
case 'text': return seg.data.text || '';
|
|
30
|
-
case 'at': return `@${seg.data.name || seg.data.id}`;
|
|
31
|
-
case 'image': return seg.data.url ? `` : '[image]';
|
|
32
|
-
case 'link': return `[${seg.data.text || seg.data.url}](${seg.data.url})`;
|
|
33
|
-
default: return seg.data?.text || `[${seg.type}]`;
|
|
34
|
-
}
|
|
35
|
-
}).join('');
|
|
36
|
-
}
|
|
37
|
-
export class GitHubEndpoint {
|
|
38
|
-
adapter;
|
|
39
|
-
$config;
|
|
40
|
-
$connected = false;
|
|
41
|
-
gh;
|
|
42
|
-
get $id() { return this.$config.name; }
|
|
43
|
-
get logger() {
|
|
44
|
-
return this.adapter.plugin.logger;
|
|
11
|
+
export class GithubEndpoint extends Endpoint {
|
|
12
|
+
#logger;
|
|
13
|
+
#options;
|
|
14
|
+
client;
|
|
15
|
+
#routeReleases = [];
|
|
16
|
+
#open = false;
|
|
17
|
+
#started = false;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super();
|
|
20
|
+
this.#logger = getAdapterLogger('github', options.config.id);
|
|
21
|
+
this.#options = options;
|
|
22
|
+
const api = options.createClient?.(options.config) ?? defaultCreateClient(options.config);
|
|
23
|
+
this.client = new GithubClient(options.config.id, api, options.config, options.database);
|
|
45
24
|
}
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
this.$config = $config;
|
|
49
|
-
const { host, app_id, private_key } = $config;
|
|
50
|
-
const appAuth = app_id && private_key
|
|
51
|
-
? { appId: app_id, privateKey: private_key }
|
|
52
|
-
: undefined;
|
|
53
|
-
this.gh = new GhClient({ host, appAuth });
|
|
25
|
+
get config() {
|
|
26
|
+
return this.client.config;
|
|
54
27
|
}
|
|
55
|
-
async
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
28
|
+
async start() {
|
|
29
|
+
if (this.#started)
|
|
30
|
+
return;
|
|
31
|
+
this.#started = true;
|
|
32
|
+
try {
|
|
33
|
+
const result = await this.client.api.verifyAuth();
|
|
34
|
+
if (!result.ok)
|
|
35
|
+
throw new Error(`GitHub 认证失败: ${result.message}`);
|
|
36
|
+
if (this.client.config.webhookSecret) {
|
|
37
|
+
if (!this.#options.http) {
|
|
38
|
+
throw new TypeError('GitHub webhook_secret requires httpHostToken');
|
|
39
|
+
}
|
|
40
|
+
this.#routeReleases.push(...registerGithubWebhookRoutes(this.#options.http, this));
|
|
41
|
+
this.#logger.debug(formatCompact({
|
|
42
|
+
endpoint: this.client.name,
|
|
43
|
+
op: 'webhook',
|
|
44
|
+
path: this.client.config.webhookPath,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.#logger.debug(formatCompact({
|
|
49
|
+
endpoint: this.client.name,
|
|
50
|
+
op: 'connect',
|
|
51
|
+
mode: 'api-only',
|
|
52
|
+
bot: this.client.api.authenticatedUser,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await this.stop();
|
|
58
|
+
this.#logger.error('Failed to connect GitHub endpoint:', error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
65
61
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const number = payload.issue.number;
|
|
69
|
-
const isPR = 'pull_request' in payload.issue;
|
|
70
|
-
const channelId = buildChannelId(repo, isPR ? 'pr' : 'issue', number);
|
|
71
|
-
const gh = this.gh;
|
|
72
|
-
const result = Message.from(payload, {
|
|
73
|
-
$id: payload.comment.id.toString(),
|
|
74
|
-
$adapter: 'github',
|
|
75
|
-
$endpoint: this.$config.name,
|
|
76
|
-
$sender: { id: payload.sender.login, name: payload.sender.login },
|
|
77
|
-
$channel: { id: channelId, type: 'group' },
|
|
78
|
-
$content: parseMarkdown(payload.comment.body),
|
|
79
|
-
$raw: payload.comment.body,
|
|
80
|
-
$timestamp: new Date(payload.comment.created_at).getTime(),
|
|
81
|
-
$recall: async () => { await gh.deleteIssueComment(repo, payload.comment.id); },
|
|
82
|
-
$reply: async (content, quote) => {
|
|
83
|
-
const text = toMarkdown(content);
|
|
84
|
-
const finalBody = quote ? `> ${payload.comment.body.split('\n')[0]}\n\n${text}` : text;
|
|
85
|
-
const r = await gh.createIssueComment(repo, number, finalBody);
|
|
86
|
-
return r.ok ? r.data.id.toString() : '';
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
return result;
|
|
62
|
+
open() {
|
|
63
|
+
this.#open = true;
|
|
90
64
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const number = payload.pull_request.number;
|
|
94
|
-
const channelId = buildChannelId(repo, 'pr', number);
|
|
95
|
-
const gh = this.gh;
|
|
96
|
-
const body = payload.comment.path
|
|
97
|
-
? `**${payload.comment.path}**\n${payload.comment.diff_hunk ? '```diff\n' + payload.comment.diff_hunk + '\n```\n' : ''}${payload.comment.body}`
|
|
98
|
-
: payload.comment.body;
|
|
99
|
-
return Message.from(payload, {
|
|
100
|
-
$id: payload.comment.id.toString(),
|
|
101
|
-
$adapter: 'github',
|
|
102
|
-
$endpoint: this.$config.name,
|
|
103
|
-
$sender: { id: payload.sender.login, name: payload.sender.login },
|
|
104
|
-
$channel: { id: channelId, type: 'group' },
|
|
105
|
-
$content: parseMarkdown(body),
|
|
106
|
-
$raw: body,
|
|
107
|
-
$timestamp: new Date(payload.comment.created_at).getTime(),
|
|
108
|
-
$recall: async () => { await gh.deletePRReviewComment(repo, payload.comment.id); },
|
|
109
|
-
$reply: async (content) => {
|
|
110
|
-
const r = await gh.createPRComment(repo, number, toMarkdown(content));
|
|
111
|
-
return r.ok ? r.data.id.toString() : '';
|
|
112
|
-
},
|
|
113
|
-
});
|
|
65
|
+
close() {
|
|
66
|
+
this.#open = false;
|
|
114
67
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const gh = this.gh;
|
|
122
|
-
const stateLabel = {
|
|
123
|
-
approved: '✅ APPROVED', changes_requested: '🔄 CHANGES REQUESTED',
|
|
124
|
-
commented: '💬 COMMENTED', dismissed: '❌ DISMISSED',
|
|
125
|
-
};
|
|
126
|
-
const body = `**[${stateLabel[payload.review.state] || payload.review.state}]**\n${payload.review.body}`;
|
|
127
|
-
return Message.from(payload, {
|
|
128
|
-
$id: payload.review.id.toString(),
|
|
129
|
-
$adapter: 'github',
|
|
130
|
-
$endpoint: this.$config.name,
|
|
131
|
-
$sender: { id: payload.sender.login, name: payload.sender.login },
|
|
132
|
-
$channel: { id: channelId, type: 'group' },
|
|
133
|
-
$content: parseMarkdown(body),
|
|
134
|
-
$raw: body,
|
|
135
|
-
$timestamp: new Date(payload.review.submitted_at).getTime(),
|
|
136
|
-
$recall: async () => { },
|
|
137
|
-
$reply: async (content) => {
|
|
138
|
-
const r = await gh.createPRComment(repo, number, toMarkdown(content));
|
|
139
|
-
return r.ok ? r.data.id.toString() : '';
|
|
140
|
-
},
|
|
141
|
-
});
|
|
68
|
+
async stop() {
|
|
69
|
+
this.#open = false;
|
|
70
|
+
for (const release of this.#routeReleases.splice(0))
|
|
71
|
+
release();
|
|
72
|
+
this.#started = false;
|
|
73
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
142
74
|
}
|
|
143
|
-
async
|
|
144
|
-
const parsed = parseChannelId(
|
|
75
|
+
async send({ conversation, payload }) {
|
|
76
|
+
const parsed = parseChannelId(conversation.id);
|
|
145
77
|
if (!parsed)
|
|
146
|
-
throw new Error(`无效的 GitHub
|
|
147
|
-
const text =
|
|
78
|
+
throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
|
|
79
|
+
const text = formatOutboundBody(payload);
|
|
148
80
|
const r = parsed.type === 'issue'
|
|
149
|
-
? await this.
|
|
150
|
-
: await this.
|
|
81
|
+
? await this.client.api.createIssueComment(parsed.repo, parsed.number, text)
|
|
82
|
+
: await this.client.api.createPRComment(parsed.repo, parsed.number, text);
|
|
151
83
|
if (!r.ok)
|
|
152
84
|
throw new Error(`发送失败: ${JSON.stringify(r.data)}`);
|
|
153
|
-
this
|
|
154
|
-
|
|
85
|
+
this.#logger.debug(formatCompact({
|
|
86
|
+
op: 'github_send',
|
|
87
|
+
endpoint: this.client.name,
|
|
88
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
89
|
+
messageId: r.data.id,
|
|
90
|
+
}));
|
|
91
|
+
return String(r.data.id);
|
|
155
92
|
}
|
|
156
|
-
|
|
157
|
-
|
|
93
|
+
/** Test / internal: admit a parsed comment when open. */
|
|
94
|
+
admit(comment) {
|
|
95
|
+
if (!this.#open)
|
|
96
|
+
return;
|
|
97
|
+
const botUser = this.client.config.botLogin
|
|
98
|
+
|| this.client.api.getBotLogin()
|
|
99
|
+
|| this.client.api.authenticatedUser;
|
|
100
|
+
if (botUser && comment.sender === botUser)
|
|
101
|
+
return;
|
|
102
|
+
const conversation = githubInboundConversation(String(this.#options.id), comment);
|
|
103
|
+
const content = enrichInboundContent(formatInboundContent(comment.content), this.client.config, botUser ?? undefined, comment.repo);
|
|
104
|
+
void this.emit('message.receive', {
|
|
105
|
+
conversation,
|
|
106
|
+
message: { conversation, id: comment.id },
|
|
107
|
+
content,
|
|
108
|
+
sender: { id: comment.sender, name: comment.sender },
|
|
109
|
+
endpointId: this.client.name,
|
|
110
|
+
metadata: Object.freeze({
|
|
111
|
+
repo: comment.repo,
|
|
112
|
+
kind: comment.kind,
|
|
113
|
+
createdAt: comment.createdAt,
|
|
114
|
+
}),
|
|
115
|
+
}).catch((err) => {
|
|
116
|
+
this.#logger.warn(formatCompact({
|
|
117
|
+
op: 'github_gateway_receive_failed',
|
|
118
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
120
|
+
}));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
admitPlatform(name, event) {
|
|
124
|
+
if (!this.#open)
|
|
125
|
+
return;
|
|
126
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
127
|
+
this.#logger.warn(formatCompact({
|
|
128
|
+
op: 'github_platform_event_failed',
|
|
129
|
+
event: name,
|
|
130
|
+
error: error instanceof Error ? error.message : String(error),
|
|
131
|
+
}));
|
|
132
|
+
});
|
|
158
133
|
}
|
|
159
134
|
}
|
|
160
|
-
|
|
135
|
+
export function defaultCreateClient(config) {
|
|
136
|
+
const appAuth = config.appId && config.privateKey
|
|
137
|
+
? { appId: config.appId, privateKey: config.privateKey }
|
|
138
|
+
: undefined;
|
|
139
|
+
return new GhClient({ host: config.host, appAuth });
|
|
140
|
+
}
|
package/lib/gh-client.d.ts
CHANGED
|
@@ -13,6 +13,17 @@ export interface AppAuth {
|
|
|
13
13
|
/** PEM 格式私钥内容,或私钥文件路径 */
|
|
14
14
|
privateKey: string;
|
|
15
15
|
}
|
|
16
|
+
export interface GitHubBotIdentity {
|
|
17
|
+
login: string;
|
|
18
|
+
email: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
userId: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GitHubCommitAuthor {
|
|
23
|
+
name: string;
|
|
24
|
+
email: string;
|
|
25
|
+
date?: string;
|
|
26
|
+
}
|
|
16
27
|
export interface GhClientOptions {
|
|
17
28
|
/** GitHub Enterprise 主机名(默认 github.com) */
|
|
18
29
|
host?: string;
|
|
@@ -153,6 +164,64 @@ export declare class GhClient {
|
|
|
153
164
|
status: number;
|
|
154
165
|
data: any;
|
|
155
166
|
}>;
|
|
167
|
+
getFileContent(repo: string, path: string, ref?: string): Promise<{
|
|
168
|
+
ok: boolean;
|
|
169
|
+
status: number;
|
|
170
|
+
data: {
|
|
171
|
+
content: string;
|
|
172
|
+
sha: string;
|
|
173
|
+
encoding: string;
|
|
174
|
+
};
|
|
175
|
+
}>;
|
|
176
|
+
createOrUpdateFile(repo: string, path: string, content: string, message: string, options?: {
|
|
177
|
+
branch?: string;
|
|
178
|
+
sha?: string;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
ok: boolean;
|
|
181
|
+
status: number;
|
|
182
|
+
data: {
|
|
183
|
+
commit: {
|
|
184
|
+
sha: string;
|
|
185
|
+
html_url?: string;
|
|
186
|
+
};
|
|
187
|
+
content: {
|
|
188
|
+
html_url?: string;
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
}>;
|
|
192
|
+
getRef(repo: string, ref: string): Promise<{
|
|
193
|
+
ok: boolean;
|
|
194
|
+
status: number;
|
|
195
|
+
data: {
|
|
196
|
+
ref: string;
|
|
197
|
+
object: {
|
|
198
|
+
sha: string;
|
|
199
|
+
type: string;
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
}>;
|
|
203
|
+
createRef(repo: string, ref: string, sha: string): Promise<{
|
|
204
|
+
ok: boolean;
|
|
205
|
+
status: number;
|
|
206
|
+
data: any;
|
|
207
|
+
}>;
|
|
208
|
+
createPullRequest(repo: string, title: string, head: string, base: string, body?: string): Promise<{
|
|
209
|
+
ok: boolean;
|
|
210
|
+
status: number;
|
|
211
|
+
data: {
|
|
212
|
+
number: number;
|
|
213
|
+
html_url: string;
|
|
214
|
+
head: {
|
|
215
|
+
ref: string;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
}>;
|
|
219
|
+
getBotLogin(): string | null;
|
|
220
|
+
getCommitAuthor(): GitHubCommitAuthor | null;
|
|
221
|
+
getBotIdentitySync(): GitHubBotIdentity | null;
|
|
222
|
+
getBotIdentity(): Promise<GitHubBotIdentity | null>;
|
|
223
|
+
buildCloneUrl(repo: string, token: string): string;
|
|
224
|
+
resolveBotUserId(): Promise<number | null>;
|
|
156
225
|
starRepo(repo: string): Promise<{
|
|
157
226
|
ok: boolean;
|
|
158
227
|
status: number;
|
|
@@ -193,6 +262,10 @@ export declare class GhClient {
|
|
|
193
262
|
get appSlug(): string | null;
|
|
194
263
|
/** App 的 client_id(verifyAuth 后从 /app 获取,用于 Device Flow) */
|
|
195
264
|
private _clientId;
|
|
265
|
+
/** Bot 机器用户 numeric id(/users/{slug}[bot]) */
|
|
266
|
+
private _botUserId;
|
|
267
|
+
/** 确保 App 认证的 Installation Token 对指定 repo 有效,并返回 token */
|
|
268
|
+
ensureInstallationTokenForRepo(repo: string): Promise<string | undefined>;
|
|
196
269
|
get clientId(): string | null;
|
|
197
270
|
/**
|
|
198
271
|
* 获取仓库事件(支持 ETag 条件请求)
|
|
@@ -225,4 +298,3 @@ export declare class GhClient {
|
|
|
225
298
|
scope: string;
|
|
226
299
|
} | null>;
|
|
227
300
|
}
|
|
228
|
-
//# sourceMappingURL=gh-client.d.ts.map
|