@zhin.js/adapter-github 6.0.0 → 6.0.1
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 +26 -0
- package/README.md +18 -2
- package/adapters/github.js +0 -7
- package/adapters/github.ts +0 -7
- package/agent/prompt-sections/platform.ts +16 -0
- package/agent/tools/bind.ts +4 -3
- package/agent/tools/create_pr.ts +3 -2
- package/agent/tools/install.ts +4 -3
- package/agent/tools/patch_file.ts +3 -2
- package/agent/tools/prepare_workspace.ts +3 -2
- package/agent/tools/push_branch.ts +3 -2
- package/agent/tools/star.ts +3 -2
- package/agent/tools/subscribe.ts +3 -3
- package/agent/tools/subscriptions.ts +4 -4
- package/agent/tools/unbind.ts +4 -3
- package/agent/tools/unsubscribe.ts +3 -3
- package/agent/tools/whoami.ts +4 -3
- package/lib/client.d.ts +36 -0
- package/lib/client.js +47 -0
- package/lib/endpoint.d.ts +10 -24
- package/lib/endpoint.js +35 -65
- package/lib/github-bot-handlers.d.ts +5 -4
- package/lib/github-bot-handlers.js +12 -13
- package/lib/github-endpoint-commands.d.ts +1 -1
- package/lib/github-tool-handlers.d.ts +9 -8
- package/lib/github-tool-handlers.js +26 -33
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +1 -1
- package/lib/protocol.js +1 -1
- package/lib/webhook.d.ts +1 -0
- package/lib/webhook.js +1 -0
- package/package.json +19 -13
- package/plugin.js +0 -17
- package/src/client.ts +65 -0
- package/src/endpoint.ts +36 -75
- package/src/github-bot-handlers.ts +13 -9
- package/src/github-tool-handlers.ts +27 -32
- package/src/index.ts +1 -9
- package/src/protocol.ts +1 -1
- package/src/webhook.ts +5 -0
- package/lib/agent-prompt.d.ts +0 -2
- package/lib/agent-prompt.js +0 -84
- package/lib/github-agent-deps.d.ts +0 -47
- package/lib/github-agent-deps.js +0 -43
- package/src/agent-prompt.ts +0 -101
- package/src/github-agent-deps.ts +0 -82
package/src/client.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import type { PluginDatabaseHost } from 'zhin.js';
|
|
3
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
4
|
+
import { GhClient } from './gh-client.js';
|
|
5
|
+
import { lookupGithubOauthAccessToken } from './oauth-users.js';
|
|
6
|
+
import type { ResolvedGithubConfig } from './protocol.js';
|
|
7
|
+
import { WorkspaceManager } from './workspace-manager.js';
|
|
8
|
+
|
|
9
|
+
/** GitHub SDK surface exposed to plugin handlers and Agent tools. */
|
|
10
|
+
export class GithubClient {
|
|
11
|
+
#workspaceManager?: WorkspaceManager;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly name: string,
|
|
15
|
+
readonly api: GhClient,
|
|
16
|
+
readonly config: ResolvedGithubConfig,
|
|
17
|
+
readonly database?: PluginDatabaseHost,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async getUserOrDefaultApi(platform?: string, platformUid?: string): Promise<GhClient> {
|
|
21
|
+
if (platform && platformUid) {
|
|
22
|
+
const token = await lookupGithubOauthAccessToken(this.database, platform, platformUid);
|
|
23
|
+
if (token) return this.api.withToken(token);
|
|
24
|
+
}
|
|
25
|
+
return this.api;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get clientId(): string | null {
|
|
29
|
+
return this.api.clientId || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get host(): string | undefined {
|
|
33
|
+
return this.config.host;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get appSlug(): string | null {
|
|
37
|
+
return this.api.appSlug || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get installations() {
|
|
41
|
+
return this.api.installations || [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get workspaceManager(): WorkspaceManager {
|
|
45
|
+
if (!this.#workspaceManager) {
|
|
46
|
+
const root = this.config.workspaceRoot
|
|
47
|
+
?? path.join(process.cwd(), 'data', 'github-workspaces');
|
|
48
|
+
this.#workspaceManager = new WorkspaceManager(this.api, root);
|
|
49
|
+
}
|
|
50
|
+
return this.#workspaceManager;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type GithubClientEventMap = Record<string, unknown>;
|
|
55
|
+
|
|
56
|
+
declare module '@zhin.js/feature-kit' {
|
|
57
|
+
interface AdapterClientRegistry {
|
|
58
|
+
readonly github: {
|
|
59
|
+
readonly client: GithubClient;
|
|
60
|
+
readonly events: GithubClientEventMap;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const githubClient = defineEndpointClient<GithubClient, GithubClientEventMap>('github');
|
package/src/endpoint.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
1
2
|
/**
|
|
2
3
|
* GithubEndpoint — lifecycle, outbound send, inbound admit.
|
|
3
4
|
*/
|
|
4
|
-
import
|
|
5
|
-
import type { EndpointInstance, EndpointSendRequest } from 'zhin.js/adapter';
|
|
6
|
-
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
5
|
+
import { type EndpointSendRequest } from 'zhin.js/adapter';
|
|
7
6
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
8
7
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
9
8
|
import type { CapabilityId, PluginDatabaseHost } from 'zhin.js';
|
|
10
9
|
import { GhClient } from './gh-client.js';
|
|
11
|
-
import { registerGithubAgentEndpoint } from './github-agent-deps.js';
|
|
12
|
-
import { lookupGithubOauthAccessToken } from './oauth-users.js';
|
|
13
10
|
import {
|
|
14
11
|
enrichInboundContent,
|
|
15
12
|
formatInboundContent,
|
|
@@ -20,12 +17,10 @@ import {
|
|
|
20
17
|
type ResolvedGithubConfig,
|
|
21
18
|
} from './protocol.js';
|
|
22
19
|
import { registerGithubWebhookRoutes } from './webhook.js';
|
|
23
|
-
import {
|
|
20
|
+
import { GithubClient } from './client.js';
|
|
24
21
|
|
|
25
22
|
export interface GithubEndpointOptions {
|
|
26
23
|
readonly id: CapabilityId;
|
|
27
|
-
readonly gateway: MessageGateway;
|
|
28
|
-
readonly sideEvents?: SideEventGateway;
|
|
29
24
|
readonly http?: HttpHost;
|
|
30
25
|
readonly database?: PluginDatabaseHost;
|
|
31
26
|
readonly config: ResolvedGithubConfig;
|
|
@@ -36,94 +31,49 @@ export interface GithubEndpointOptions {
|
|
|
36
31
|
* GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
|
|
37
32
|
* 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
|
|
38
33
|
*/
|
|
39
|
-
export class GithubEndpoint
|
|
34
|
+
export class GithubEndpoint extends Endpoint<GithubClient> {
|
|
40
35
|
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
41
36
|
|
|
42
37
|
readonly #options: GithubEndpointOptions;
|
|
43
|
-
readonly
|
|
44
|
-
readonly config: ResolvedGithubConfig;
|
|
45
|
-
readonly name: string;
|
|
46
|
-
#workspaceManager: WorkspaceManager | null = null;
|
|
38
|
+
readonly client: GithubClient;
|
|
47
39
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
48
40
|
#open = false;
|
|
49
41
|
#started = false;
|
|
50
|
-
#unregisterAgent?: () => void;
|
|
51
42
|
|
|
52
43
|
constructor(options: GithubEndpointOptions) {
|
|
44
|
+
super();
|
|
53
45
|
this.#logger = getAdapterLogger('github', options.config.id);
|
|
54
46
|
this.#options = options;
|
|
55
|
-
|
|
56
|
-
this.
|
|
57
|
-
this.gh = options.createClient?.(options.config) ?? defaultCreateClient(options.config);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
getAPI(): GhClient {
|
|
61
|
-
return this.gh;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async getUserOrDefaultAPI(platform?: string, platformUid?: string): Promise<GhClient | null> {
|
|
65
|
-
if (platform && platformUid) {
|
|
66
|
-
const token = await lookupGithubOauthAccessToken(
|
|
67
|
-
this.#options.database,
|
|
68
|
-
platform,
|
|
69
|
-
platformUid,
|
|
70
|
-
);
|
|
71
|
-
if (token) return this.gh.withToken(token);
|
|
72
|
-
}
|
|
73
|
-
return this.gh;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getClientId(): string | null {
|
|
77
|
-
return this.gh.clientId || null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getHost(): string | undefined {
|
|
81
|
-
return this.config.host;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
getAppSlug(): string | null {
|
|
85
|
-
return this.gh.appSlug || null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
getInstallations() {
|
|
89
|
-
return this.gh.installations || [];
|
|
47
|
+
const api = options.createClient?.(options.config) ?? defaultCreateClient(options.config);
|
|
48
|
+
this.client = new GithubClient(options.config.id, api, options.config, options.database);
|
|
90
49
|
}
|
|
91
50
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const workspaceRoot = this.config.workspaceRoot
|
|
95
|
-
?? path.join(process.cwd(), 'data', 'github-workspaces');
|
|
96
|
-
this.#workspaceManager = new WorkspaceManager(this.gh, workspaceRoot);
|
|
97
|
-
return this.#workspaceManager;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
getDatabase(): PluginDatabaseHost | undefined {
|
|
101
|
-
return this.#options.database;
|
|
51
|
+
get config(): ResolvedGithubConfig {
|
|
52
|
+
return this.client.config;
|
|
102
53
|
}
|
|
103
54
|
|
|
104
55
|
async start(): Promise<void> {
|
|
105
56
|
if (this.#started) return;
|
|
106
57
|
this.#started = true;
|
|
107
58
|
try {
|
|
108
|
-
const result = await this.
|
|
59
|
+
const result = await this.client.api.verifyAuth();
|
|
109
60
|
if (!result.ok) throw new Error(`GitHub 认证失败: ${result.message}`);
|
|
110
|
-
|
|
111
|
-
if (this.config.webhookSecret) {
|
|
61
|
+
if (this.client.config.webhookSecret) {
|
|
112
62
|
if (!this.#options.http) {
|
|
113
63
|
throw new TypeError('GitHub webhook_secret requires httpHostToken');
|
|
114
64
|
}
|
|
115
65
|
this.#routeReleases.push(...registerGithubWebhookRoutes(this.#options.http, this));
|
|
116
66
|
this.#logger.debug(formatCompact({
|
|
117
|
-
endpoint: this.name,
|
|
67
|
+
endpoint: this.client.name,
|
|
118
68
|
op: 'webhook',
|
|
119
|
-
path: this.config.webhookPath,
|
|
69
|
+
path: this.client.config.webhookPath,
|
|
120
70
|
}));
|
|
121
71
|
} else {
|
|
122
72
|
this.#logger.debug(formatCompact({
|
|
123
|
-
endpoint: this.name,
|
|
73
|
+
endpoint: this.client.name,
|
|
124
74
|
op: 'connect',
|
|
125
75
|
mode: 'api-only',
|
|
126
|
-
bot: this.
|
|
76
|
+
bot: this.client.api.authenticatedUser,
|
|
127
77
|
}));
|
|
128
78
|
}
|
|
129
79
|
} catch (error) {
|
|
@@ -144,8 +94,6 @@ export class GithubEndpoint implements EndpointInstance {
|
|
|
144
94
|
async stop(): Promise<void> {
|
|
145
95
|
this.#open = false;
|
|
146
96
|
for (const release of this.#routeReleases.splice(0)) release();
|
|
147
|
-
this.#unregisterAgent?.();
|
|
148
|
-
this.#unregisterAgent = undefined;
|
|
149
97
|
this.#started = false;
|
|
150
98
|
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
151
99
|
}
|
|
@@ -155,12 +103,12 @@ export class GithubEndpoint implements EndpointInstance {
|
|
|
155
103
|
if (!parsed) throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
|
|
156
104
|
const text = formatOutboundBody(payload);
|
|
157
105
|
const r = parsed.type === 'issue'
|
|
158
|
-
? await this.
|
|
159
|
-
: await this.
|
|
106
|
+
? await this.client.api.createIssueComment(parsed.repo, parsed.number, text)
|
|
107
|
+
: await this.client.api.createPRComment(parsed.repo, parsed.number, text);
|
|
160
108
|
if (!r.ok) throw new Error(`发送失败: ${JSON.stringify(r.data)}`);
|
|
161
109
|
this.#logger.debug(formatCompact({
|
|
162
110
|
op: 'github_send',
|
|
163
|
-
endpoint: this.name,
|
|
111
|
+
endpoint: this.client.name,
|
|
164
112
|
target: `${conversation.kind}:${conversation.id}`,
|
|
165
113
|
messageId: r.data.id,
|
|
166
114
|
}));
|
|
@@ -170,21 +118,23 @@ export class GithubEndpoint implements EndpointInstance {
|
|
|
170
118
|
/** Test / internal: admit a parsed comment when open. */
|
|
171
119
|
admit(comment: GithubInboundComment): void {
|
|
172
120
|
if (!this.#open) return;
|
|
173
|
-
const botUser = this.config.botLogin
|
|
121
|
+
const botUser = this.client.config.botLogin
|
|
122
|
+
|| this.client.api.getBotLogin()
|
|
123
|
+
|| this.client.api.authenticatedUser;
|
|
174
124
|
if (botUser && comment.sender === botUser) return;
|
|
175
125
|
const conversation = githubInboundConversation(String(this.#options.id), comment);
|
|
176
126
|
const content = enrichInboundContent(
|
|
177
127
|
formatInboundContent(comment.content),
|
|
178
|
-
this.config,
|
|
128
|
+
this.client.config,
|
|
179
129
|
botUser ?? undefined,
|
|
180
130
|
comment.repo,
|
|
181
131
|
);
|
|
182
|
-
void this
|
|
132
|
+
void this.emit('message.receive', {
|
|
183
133
|
conversation,
|
|
184
134
|
message: { conversation, id: comment.id },
|
|
185
135
|
content,
|
|
186
136
|
sender: { id: comment.sender, name: comment.sender },
|
|
187
|
-
endpointId: this.name,
|
|
137
|
+
endpointId: this.client.name,
|
|
188
138
|
metadata: Object.freeze({
|
|
189
139
|
repo: comment.repo,
|
|
190
140
|
kind: comment.kind,
|
|
@@ -198,6 +148,17 @@ export class GithubEndpoint implements EndpointInstance {
|
|
|
198
148
|
}));
|
|
199
149
|
});
|
|
200
150
|
}
|
|
151
|
+
|
|
152
|
+
admitPlatform(name: string, event: unknown): void {
|
|
153
|
+
if (!this.#open) return;
|
|
154
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
155
|
+
this.#logger.warn(formatCompact({
|
|
156
|
+
op: 'github_platform_event_failed',
|
|
157
|
+
event: name,
|
|
158
|
+
error: error instanceof Error ? error.message : String(error),
|
|
159
|
+
}));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
201
162
|
}
|
|
202
163
|
|
|
203
164
|
export function defaultCreateClient(config: ResolvedGithubConfig): GhClient {
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { Message } from 'zhin.js';
|
|
5
5
|
import { getCurrentCommMessage } from '@zhin.js/agent/security';
|
|
6
|
-
import {
|
|
6
|
+
import type { GithubClient } from './client.js';
|
|
7
7
|
import {
|
|
8
8
|
parseMessageChannel,
|
|
9
9
|
resolveWorkspaceBranch,
|
|
10
10
|
formatChannelContext,
|
|
11
11
|
} from './github-channel-context.js';
|
|
12
12
|
|
|
13
|
-
function requireBotGh() {
|
|
14
|
-
const gh =
|
|
13
|
+
function requireBotGh(client: GithubClient) {
|
|
14
|
+
const gh = client.api;
|
|
15
15
|
if (!gh?.isAppAuth) {
|
|
16
16
|
throw new Error('需要 GitHub App 认证(app_id + private_key),Bot 写操作不可用');
|
|
17
17
|
}
|
|
@@ -35,12 +35,13 @@ function resolveChannel(msg: Message, repo?: string) {
|
|
|
35
35
|
|
|
36
36
|
export async function executeGithubPrepareWorkspace(
|
|
37
37
|
args: { repo?: string },
|
|
38
|
+
client: GithubClient,
|
|
38
39
|
commMessage?: Message,
|
|
39
40
|
) {
|
|
40
41
|
const msg = resolveCommMessage(commMessage);
|
|
41
42
|
const ctx = resolveChannel(msg, args.repo);
|
|
42
|
-
const gh = requireBotGh();
|
|
43
|
-
const wm =
|
|
43
|
+
const gh = requireBotGh(client);
|
|
44
|
+
const wm = client.workspaceManager;
|
|
44
45
|
const { branch, base } = await resolveWorkspaceBranch(gh, ctx);
|
|
45
46
|
const repoPath = await wm.checkoutBranch(ctx.repo, branch, base);
|
|
46
47
|
return [
|
|
@@ -53,11 +54,12 @@ export async function executeGithubPrepareWorkspace(
|
|
|
53
54
|
|
|
54
55
|
export async function executeGithubPatchFile(
|
|
55
56
|
args: { repo?: string; path: string; content: string; message: string; branch?: string },
|
|
57
|
+
client: GithubClient,
|
|
56
58
|
commMessage?: Message,
|
|
57
59
|
) {
|
|
58
60
|
const msg = resolveCommMessage(commMessage);
|
|
59
61
|
const ctx = resolveChannel(msg, args.repo);
|
|
60
|
-
const gh = requireBotGh();
|
|
62
|
+
const gh = requireBotGh(client);
|
|
61
63
|
const { branch } = args.branch
|
|
62
64
|
? { branch: args.branch }
|
|
63
65
|
: await resolveWorkspaceBranch(gh, ctx);
|
|
@@ -80,12 +82,13 @@ export async function executeGithubPatchFile(
|
|
|
80
82
|
|
|
81
83
|
export async function executeGithubPushBranch(
|
|
82
84
|
args: { repo?: string; branch?: string; message: string },
|
|
85
|
+
client: GithubClient,
|
|
83
86
|
commMessage?: Message,
|
|
84
87
|
) {
|
|
85
88
|
const msg = resolveCommMessage(commMessage);
|
|
86
89
|
const ctx = resolveChannel(msg, args.repo);
|
|
87
|
-
const gh = requireBotGh();
|
|
88
|
-
const wm =
|
|
90
|
+
const gh = requireBotGh(client);
|
|
91
|
+
const wm = client.workspaceManager;
|
|
89
92
|
const branch = args.branch ?? (await resolveWorkspaceBranch(gh, ctx)).branch;
|
|
90
93
|
const result = await wm.commitAndPush(ctx.repo, branch, args.message);
|
|
91
94
|
return `✅ ${result}\n📍 ${ctx.repo} @ \`${branch}\``;
|
|
@@ -93,11 +96,12 @@ export async function executeGithubPushBranch(
|
|
|
93
96
|
|
|
94
97
|
export async function executeGithubCreatePr(
|
|
95
98
|
args: { repo?: string; title: string; body?: string; head?: string; base?: string },
|
|
99
|
+
client: GithubClient,
|
|
96
100
|
commMessage?: Message,
|
|
97
101
|
) {
|
|
98
102
|
const msg = resolveCommMessage(commMessage);
|
|
99
103
|
const ctx = resolveChannel(msg, args.repo);
|
|
100
|
-
const gh = requireBotGh();
|
|
104
|
+
const gh = requireBotGh(client);
|
|
101
105
|
const resolved = await resolveWorkspaceBranch(gh, ctx);
|
|
102
106
|
const head = args.head ?? resolved.branch;
|
|
103
107
|
const base = args.base ?? resolved.base;
|
|
@@ -3,10 +3,10 @@ import type { Message } from 'zhin.js';
|
|
|
3
3
|
import { getCurrentCommMessage } from '@zhin.js/agent/security';
|
|
4
4
|
import { GhClient } from './gh-client.js';
|
|
5
5
|
import type { EventType } from './types.js';
|
|
6
|
-
import {
|
|
6
|
+
import type { GithubClient } from './client.js';
|
|
7
7
|
|
|
8
|
-
function oauthModel() {
|
|
9
|
-
const db =
|
|
8
|
+
function oauthModel(client: GithubClient) {
|
|
9
|
+
const db = client.database as {
|
|
10
10
|
models?: Map<string, unknown>;
|
|
11
11
|
} | null | undefined;
|
|
12
12
|
return db?.models?.get('github_oauth_users') as {
|
|
@@ -16,8 +16,8 @@ function oauthModel() {
|
|
|
16
16
|
} | undefined;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function subscriptionsModel() {
|
|
20
|
-
const db =
|
|
19
|
+
function subscriptionsModel(client: GithubClient) {
|
|
20
|
+
const db = client.database as {
|
|
21
21
|
models?: Map<string, unknown>;
|
|
22
22
|
} | null | undefined;
|
|
23
23
|
return db?.models?.get('github_subscriptions') as {
|
|
@@ -29,18 +29,16 @@ function subscriptionsModel() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function depsLogger() {
|
|
32
|
-
return
|
|
32
|
+
return {
|
|
33
33
|
debug: (...args: unknown[]) => console.debug(...args),
|
|
34
34
|
warn: (...args: unknown[]) => console.warn(...args),
|
|
35
35
|
error: (...args: unknown[]) => console.error(...args),
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export async function executeGithubStar(args: { action: 'star' | 'unstar' | 'check'; repo: string }, commMessage?: Message) {
|
|
40
|
-
const adapter = getAdapter();
|
|
39
|
+
export async function executeGithubStar(args: { action: 'star' | 'unstar' | 'check'; repo: string }, client: GithubClient, commMessage?: Message) {
|
|
41
40
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
42
|
-
const gh = await
|
|
43
|
-
if (!gh) return '❌ 没有可用的 GitHub bot';
|
|
41
|
+
const gh = await client.getUserOrDefaultApi(msg?.$adapter, msg?.$sender.id);
|
|
44
42
|
switch (args.action) {
|
|
45
43
|
case 'star': {
|
|
46
44
|
const r = await gh.starRepo(args.repo);
|
|
@@ -59,16 +57,15 @@ export async function executeGithubStar(args: { action: 'star' | 'unstar' | 'che
|
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
export async function executeGithubBind(_args: Record<string, never>, commMessage?: Message) {
|
|
63
|
-
const adapter = getAdapter();
|
|
60
|
+
export async function executeGithubBind(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
64
61
|
const log = depsLogger();
|
|
65
62
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
66
63
|
if (!msg?.$adapter || !msg?.$sender?.id) return '❌ 无法获取当前用户信息';
|
|
67
64
|
|
|
68
|
-
const clientId =
|
|
65
|
+
const clientId = client.clientId;
|
|
69
66
|
if (!clientId) return '❌ Endpoint 未配置 GitHub App 或 App 无 client_id,无法进行账号绑定';
|
|
70
67
|
|
|
71
|
-
const model = oauthModel();
|
|
68
|
+
const model = oauthModel(client);
|
|
72
69
|
if (!model) return '❌ 数据库未就绪';
|
|
73
70
|
|
|
74
71
|
const [existing] = await model.select().where({ platform: msg.$adapter, platform_uid: msg.$sender.id });
|
|
@@ -77,7 +74,7 @@ export async function executeGithubBind(_args: Record<string, never>, commMessag
|
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
try {
|
|
80
|
-
const host =
|
|
77
|
+
const host = client.host;
|
|
81
78
|
const codeResp = await GhClient.deviceFlowRequestCode(clientId, host);
|
|
82
79
|
const tokenPromise = GhClient.deviceFlowPollToken(
|
|
83
80
|
clientId, codeResp.device_code, codeResp.interval, codeResp.expires_in, host,
|
|
@@ -125,11 +122,11 @@ export async function executeGithubBind(_args: Record<string, never>, commMessag
|
|
|
125
122
|
}
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
export async function executeGithubUnbind(_args: Record<string, never>, commMessage?: Message) {
|
|
125
|
+
export async function executeGithubUnbind(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
129
126
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
130
127
|
if (!msg?.$adapter || !msg?.$sender?.id) return '❌ 无法获取当前用户信息';
|
|
131
128
|
|
|
132
|
-
const model = oauthModel();
|
|
129
|
+
const model = oauthModel(client);
|
|
133
130
|
if (!model) return '❌ 数据库未就绪';
|
|
134
131
|
|
|
135
132
|
const [existing] = await model.select().where({ platform: msg.$adapter, platform_uid: msg.$sender.id });
|
|
@@ -139,18 +136,17 @@ export async function executeGithubUnbind(_args: Record<string, never>, commMess
|
|
|
139
136
|
return `✅ 已解除 GitHub 账号绑定: ${existing.github_login}`;
|
|
140
137
|
}
|
|
141
138
|
|
|
142
|
-
export async function executeGithubWhoami(_args: Record<string, never>, commMessage?: Message) {
|
|
143
|
-
const adapter = getAdapter();
|
|
139
|
+
export async function executeGithubWhoami(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
144
140
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
145
141
|
if (!msg?.$adapter || !msg?.$sender?.id) return '❌ 无法获取当前用户信息';
|
|
146
142
|
|
|
147
|
-
const model = oauthModel();
|
|
143
|
+
const model = oauthModel(client);
|
|
148
144
|
if (!model) return '❌ 数据库未就绪';
|
|
149
145
|
|
|
150
146
|
const [existing] = await model.select().where({ platform: msg.$adapter, platform_uid: msg.$sender.id });
|
|
151
147
|
if (!existing) return '📭 你尚未绑定 GitHub 账号\n🔗 使用 github_bind 绑定你的账号';
|
|
152
148
|
|
|
153
|
-
const userGh = new GhClient({ host:
|
|
149
|
+
const userGh = new GhClient({ host: client.host, token: existing.access_token });
|
|
154
150
|
const auth = await userGh.verifyAuth();
|
|
155
151
|
if (auth.ok) {
|
|
156
152
|
return `👤 已绑定 GitHub 账号: ${auth.user}\n📅 绑定时间: ${new Date(existing.created_at).toLocaleString('zh-CN')}`;
|
|
@@ -158,12 +154,11 @@ export async function executeGithubWhoami(_args: Record<string, never>, commMess
|
|
|
158
154
|
return `⚠️ 已绑定账号 ${existing.github_login},但 Token 已失效\n🔗 请执行 github_unbind 后重新 github_bind`;
|
|
159
155
|
}
|
|
160
156
|
|
|
161
|
-
export async function executeGithubInstall() {
|
|
162
|
-
const
|
|
163
|
-
const slug = adapter.getAppSlug();
|
|
157
|
+
export async function executeGithubInstall(client: GithubClient) {
|
|
158
|
+
const slug = client.appSlug;
|
|
164
159
|
if (!slug) return '❌ Endpoint 未配置 GitHub App';
|
|
165
|
-
const host =
|
|
166
|
-
const installations =
|
|
160
|
+
const host = client.host || 'github.com';
|
|
161
|
+
const installations = client.installations;
|
|
167
162
|
let msg = `🔗 请点击以下链接安装 GitHub App 到你的仓库:\n https://${host}/apps/${slug}/installations/new`;
|
|
168
163
|
if (installations.length) {
|
|
169
164
|
msg += `\n\n📋 当前已安装 (${installations.length}):`;
|
|
@@ -174,13 +169,13 @@ export async function executeGithubInstall() {
|
|
|
174
169
|
return msg;
|
|
175
170
|
}
|
|
176
171
|
|
|
177
|
-
export async function executeGithubSubscribe(args: { repo: string; events?: string }, commMessage?: Message) {
|
|
172
|
+
export async function executeGithubSubscribe(args: { repo: string; events?: string }, client: GithubClient, commMessage?: Message) {
|
|
178
173
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
179
174
|
if (!msg?.$adapter || !msg?.$sender.id || !msg?.$channel?.id || !msg?.$endpoint) {
|
|
180
175
|
return '❌ 无法获取当前聊天通道信息';
|
|
181
176
|
}
|
|
182
177
|
|
|
183
|
-
const model = subscriptionsModel();
|
|
178
|
+
const model = subscriptionsModel(client);
|
|
184
179
|
if (!model) return '❌ 数据库未就绪';
|
|
185
180
|
|
|
186
181
|
const validEvents: EventType[] = ['push', 'issue', 'star', 'fork', 'unstar', 'pull_request'];
|
|
@@ -212,13 +207,13 @@ export async function executeGithubSubscribe(args: { repo: string; events?: stri
|
|
|
212
207
|
return `✅ 已订阅 ${args.repo}\n📡 事件: ${events.join(', ')}\n📌 通知将推送到当前通道`;
|
|
213
208
|
}
|
|
214
209
|
|
|
215
|
-
export async function executeGithubUnsubscribe(args: { repo: string }, commMessage?: Message) {
|
|
210
|
+
export async function executeGithubUnsubscribe(args: { repo: string }, client: GithubClient, commMessage?: Message) {
|
|
216
211
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
217
212
|
if (!msg?.$adapter || !msg?.$channel?.id || !msg?.$endpoint) {
|
|
218
213
|
return '❌ 无法获取当前聊天通道信息';
|
|
219
214
|
}
|
|
220
215
|
|
|
221
|
-
const model = subscriptionsModel();
|
|
216
|
+
const model = subscriptionsModel(client);
|
|
222
217
|
if (!model) return '❌ 数据库未就绪';
|
|
223
218
|
|
|
224
219
|
const [existing] = await model.select().where({
|
|
@@ -233,13 +228,13 @@ export async function executeGithubUnsubscribe(args: { repo: string }, commMessa
|
|
|
233
228
|
return `✅ 已取消订阅 ${args.repo}`;
|
|
234
229
|
}
|
|
235
230
|
|
|
236
|
-
export async function executeGithubSubscriptions(_args: Record<string, never>, commMessage?: Message) {
|
|
231
|
+
export async function executeGithubSubscriptions(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
237
232
|
const msg = commMessage ?? getCurrentCommMessage();
|
|
238
233
|
if (!msg?.$adapter || !msg?.$channel?.id || !msg?.$endpoint) {
|
|
239
234
|
return '❌ 无法获取当前聊天通道信息';
|
|
240
235
|
}
|
|
241
236
|
|
|
242
|
-
const model = subscriptionsModel();
|
|
237
|
+
const model = subscriptionsModel(client);
|
|
243
238
|
if (!model) return '❌ 数据库未就绪';
|
|
244
239
|
|
|
245
240
|
const subs = await model.select().where({
|
package/src/index.ts
CHANGED
|
@@ -20,16 +20,8 @@ export {
|
|
|
20
20
|
|
|
21
21
|
export * from './types.js';
|
|
22
22
|
|
|
23
|
-
export {
|
|
24
|
-
getAdapter,
|
|
25
|
-
getGithubAgentDeps,
|
|
26
|
-
registerGithubAgentEndpoint,
|
|
27
|
-
setGithubAgentDeps,
|
|
28
|
-
type GithubAgentDeps,
|
|
29
|
-
type GithubAgentEndpoint,
|
|
30
|
-
} from './github-agent-deps.js';
|
|
31
|
-
|
|
32
23
|
export { GhClient } from './gh-client.js';
|
|
24
|
+
export { GithubClient, githubClient, type GithubClientEventMap } from './client.js';
|
|
33
25
|
export { WorkspaceManager } from './workspace-manager.js';
|
|
34
26
|
export {
|
|
35
27
|
parseMessageChannel,
|
package/src/protocol.ts
CHANGED
|
@@ -206,7 +206,7 @@ export function enrichInboundContent(
|
|
|
206
206
|
return `@${login} ${content}`;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
/** Build inbound text for
|
|
209
|
+
/** Build inbound text for OutboundMessageService.receive from markdown/comment body. */
|
|
210
210
|
export function formatInboundContent(body: string): string {
|
|
211
211
|
return body;
|
|
212
212
|
}
|
package/src/webhook.ts
CHANGED
|
@@ -22,6 +22,7 @@ const logger = getLogger('github');
|
|
|
22
22
|
|
|
23
23
|
export interface GithubWebhookHandler {
|
|
24
24
|
readonly config: ResolvedGithubConfig;
|
|
25
|
+
admitPlatform(name: string, event: unknown): void;
|
|
25
26
|
admit(comment: GithubInboundComment): void;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -106,6 +107,10 @@ export async function dispatchGithubWebhookPayload(
|
|
|
106
107
|
const body = payload as Record<string, unknown>;
|
|
107
108
|
const repo = (body.repository as { full_name?: string } | undefined)?.full_name;
|
|
108
109
|
logger.debug(`Webhook: ${event}${(body.action as string) ? `.${body.action}` : ''} ${repo || ''}`);
|
|
110
|
+
handler.admitPlatform(
|
|
111
|
+
typeof body.action === 'string' && body.action ? `${event}.${body.action}` : event,
|
|
112
|
+
payload,
|
|
113
|
+
);
|
|
109
114
|
|
|
110
115
|
if (event === 'issue_comment') {
|
|
111
116
|
const inbound = parseIssueCommentInbound(payload as IssueCommentPayload);
|
package/lib/agent-prompt.d.ts
DELETED
package/lib/agent-prompt.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { filterTools } from 'zhin.js/ai';
|
|
2
|
-
function selectGithubDeferredTools(query, goal, deferredCatalog, maxTools) {
|
|
3
|
-
const pool = deferredCatalog.filter(t => !t.name.startsWith('mcp_filesystem') && !t.name.startsWith('mcp_icqq_'));
|
|
4
|
-
const pinned = [];
|
|
5
|
-
const bash = pool.find(t => t.name === 'bash');
|
|
6
|
-
if (bash)
|
|
7
|
-
pinned.push(bash);
|
|
8
|
-
const preferNames = [
|
|
9
|
-
...pool.filter(t => t.name.startsWith('github_')).map(t => t.name),
|
|
10
|
-
...pool.filter(t => t.name.startsWith('mcp_github_')).map(t => t.name),
|
|
11
|
-
];
|
|
12
|
-
for (const name of preferNames) {
|
|
13
|
-
if (pinned.length >= maxTools)
|
|
14
|
-
break;
|
|
15
|
-
const t = pool.find(x => x.name === name);
|
|
16
|
-
if (t && !pinned.some(p => p.name === name))
|
|
17
|
-
pinned.push(t);
|
|
18
|
-
}
|
|
19
|
-
const extra = filterTools(`${query} ${goal}`, pool, { maxTools, minScore: 0.08 })
|
|
20
|
-
.map((t) => ({ name: t.name, description: t.description }));
|
|
21
|
-
const merged = [...pinned];
|
|
22
|
-
for (const t of extra) {
|
|
23
|
-
if (merged.length >= maxTools)
|
|
24
|
-
break;
|
|
25
|
-
if (!merged.some(p => p.name === t.name))
|
|
26
|
-
merged.push(t);
|
|
27
|
-
}
|
|
28
|
-
return merged.slice(0, maxTools);
|
|
29
|
-
}
|
|
30
|
-
function isGithubDelegatedTask(query, goal) {
|
|
31
|
-
const text = `${query} ${goal}`;
|
|
32
|
-
return /github|gh_|mcp_github|\bissue\b|pull\s*request|\bpr\b/i.test(text);
|
|
33
|
-
}
|
|
34
|
-
const ORCHESTRATOR_GITHUB = [
|
|
35
|
-
'On GitHub: use run_deferred_task with tool_query "github_".',
|
|
36
|
-
'Discuss issues/PRs in chat context; do not call github_* tools on this orchestrator.',
|
|
37
|
-
'Bot write operations use github_* tools (Installation Token), not mcp_github_*.',
|
|
38
|
-
].join('\n');
|
|
39
|
-
const WORKER_GITHUB = [
|
|
40
|
-
'Use github_prepare_workspace before multi-file edits in a repo.',
|
|
41
|
-
'Small single-file change: github_patch_file (Contents API).',
|
|
42
|
-
'Multi-file / tests: workspace + bash, then github_push_branch (requires approval) and github_create_pr for Issues.',
|
|
43
|
-
'Issue thread: new branch + new PR. PR thread: push to existing PR head branch.',
|
|
44
|
-
'Do NOT use mcp_github_* for writes — PAT acts as human, not Bot.',
|
|
45
|
-
'Summarize outcomes (PR link, branch) for the orchestrator.',
|
|
46
|
-
].map(line => `- ${line}`).join('\n');
|
|
47
|
-
export function createGithubAgentPromptContributor() {
|
|
48
|
-
return {
|
|
49
|
-
platform: 'github',
|
|
50
|
-
async buildSections(ctx) {
|
|
51
|
-
if (ctx.slot === 'orchestrator') {
|
|
52
|
-
return [{
|
|
53
|
-
id: 'platform.github.orchestrator',
|
|
54
|
-
title: '## GitHub',
|
|
55
|
-
body: ORCHESTRATOR_GITHUB,
|
|
56
|
-
priority: 50,
|
|
57
|
-
}];
|
|
58
|
-
}
|
|
59
|
-
if (ctx.slot === 'deferred_worker') {
|
|
60
|
-
const query = ctx.deferred?.toolQuery ?? ctx.deferred?.goal ?? '';
|
|
61
|
-
const goal = ctx.deferred?.goal ?? '';
|
|
62
|
-
if (!isGithubDelegatedTask(query, goal))
|
|
63
|
-
return null;
|
|
64
|
-
return [{
|
|
65
|
-
id: 'platform.github.deferred_worker',
|
|
66
|
-
title: '## GitHub(本任务)',
|
|
67
|
-
body: WORKER_GITHUB,
|
|
68
|
-
priority: 50,
|
|
69
|
-
}];
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
},
|
|
73
|
-
matchesDeferredTask(ctx) {
|
|
74
|
-
const query = ctx.deferred?.toolQuery ?? ctx.deferred?.goal ?? ctx.userMessagePreview ?? '';
|
|
75
|
-
const goal = ctx.deferred?.goal ?? ctx.userMessagePreview ?? '';
|
|
76
|
-
return isGithubDelegatedTask(query, goal);
|
|
77
|
-
},
|
|
78
|
-
selectDeferredTools(query, goal, catalog, maxTools) {
|
|
79
|
-
if (!isGithubDelegatedTask(query, goal))
|
|
80
|
-
return null;
|
|
81
|
-
return selectGithubDeferredTools(query, goal, catalog, maxTools);
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|