@zhin.js/adapter-github 5.0.2 → 5.0.3

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @zhin.js/adapter-github
2
2
 
3
+ ## 5.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [afc0e66]
8
+ - Updated dependencies [2e41ad5]
9
+ - Updated dependencies [9f57124]
10
+ - @zhin.js/core@1.5.2
11
+ - @zhin.js/adapter@1.1.5
12
+ - @zhin.js/im-contract@1.0.1
13
+ - @zhin.js/plugin-runtime@1.1.3
14
+ - @zhin.js/command@1.0.7
15
+ - @zhin.js/host-http@1.0.6
16
+ - @zhin.js/agent@1.1.3
17
+ - zhin.js@6.0.2
18
+
3
19
  ## 5.0.2
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -6,7 +6,7 @@ GitHub Plugin Runtime 适配器 — Issue/PR 评论区即聊天通道,GitHub A
6
6
 
7
7
  - **聊天通道**:Issue/PR 评论区映射为群聊,支持收发消息
8
8
  - **Webhook 入站**:HMAC-SHA256 验签 → `messageGatewayToken`
9
- - **出站**:`send({ target, payload })` → Issue/PR comment(target 为 channel ID)
9
+ - **出站**:`send({ conversation, payload })` → Issue/PR comment(`conversation.id` 为 channel ID)
10
10
  - **GitHub App 认证**:JWT → Installation Token
11
11
  - **Agent 工具**:`agent/` 下 star/bind/subscribe/workspace 等保留
12
12
 
@@ -85,7 +85,7 @@ plugins:
85
85
  | `src/gh-client.ts` | GitHub API 客户端 |
86
86
 
87
87
  - 入站:`httpHostToken` POST → `messageGatewayToken.receive`
88
- - 出站:`send({ target, payload })`
88
+ - 出站:`send({ conversation, payload })`
89
89
 
90
90
  ## License
91
91
 
package/lib/endpoint.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EndpointInstance } from '@zhin.js/adapter';
1
+ import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
2
2
  import type { MessageGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId, DatabaseHost } from '@zhin.js/plugin-runtime';
@@ -42,10 +42,7 @@ export declare class GithubEndpoint implements EndpointInstance {
42
42
  open(): void;
43
43
  close(): void;
44
44
  stop(): Promise<void>;
45
- send({ target, payload }: {
46
- readonly target: string;
47
- readonly payload: unknown;
48
- }): Promise<string>;
45
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
49
46
  /** Test / internal: admit a parsed comment when open. */
50
47
  admit(comment: GithubInboundComment): void;
51
48
  }
package/lib/endpoint.js CHANGED
@@ -6,7 +6,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
6
6
  import { GhClient } from './gh-client.js';
7
7
  import { registerGithubAgentEndpoint } from './github-agent-deps.js';
8
8
  import { lookupGithubOauthAccessToken } from './oauth-users.js';
9
- import { enrichInboundContent, formatInboundContent, formatOutboundBody, parseChannelId, } from './protocol.js';
9
+ import { enrichInboundContent, formatInboundContent, formatOutboundBody, githubInboundConversation, parseChannelId, } from './protocol.js';
10
10
  import { registerGithubWebhookRoutes } from './webhook.js';
11
11
  import { WorkspaceManager } from './workspace-manager.js';
12
12
  const logger = getLogger('github');
@@ -114,10 +114,10 @@ export class GithubEndpoint {
114
114
  this.#started = false;
115
115
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.name }));
116
116
  }
117
- async send({ target, payload }) {
118
- const parsed = parseChannelId(target);
117
+ async send({ conversation, payload }) {
118
+ const parsed = parseChannelId(conversation.id);
119
119
  if (!parsed)
120
- throw new Error(`无效的 GitHub channel ID: ${target}`);
120
+ throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
121
121
  const text = formatOutboundBody(payload);
122
122
  const r = parsed.type === 'issue'
123
123
  ? await this.gh.createIssueComment(parsed.repo, parsed.number, text)
@@ -127,7 +127,7 @@ export class GithubEndpoint {
127
127
  logger.debug(formatCompact({
128
128
  op: 'github_send',
129
129
  endpoint: this.name,
130
- target,
130
+ target: `${conversation.kind}:${conversation.id}`,
131
131
  messageId: r.data.id,
132
132
  }));
133
133
  return String(r.data.id);
@@ -139,13 +139,13 @@ export class GithubEndpoint {
139
139
  const botUser = this.config.botLogin || this.gh.getBotLogin() || this.gh.authenticatedUser;
140
140
  if (botUser && comment.sender === botUser)
141
141
  return;
142
+ const conversation = githubInboundConversation(String(this.#options.id), comment);
142
143
  const content = enrichInboundContent(formatInboundContent(comment.content), this.config, botUser ?? undefined, comment.repo);
143
144
  void this.#options.gateway.receive({
144
- adapter: this.#options.id,
145
- target: comment.channelId,
145
+ conversation,
146
+ message: { conversation, id: comment.id },
146
147
  content,
147
148
  sender: comment.sender,
148
- id: comment.id,
149
149
  metadata: Object.freeze({
150
150
  endpoint: this.name,
151
151
  repo: comment.repo,
@@ -155,7 +155,7 @@ export class GithubEndpoint {
155
155
  }).catch((err) => {
156
156
  logger.warn(formatCompact({
157
157
  op: 'github_gateway_receive_failed',
158
- target: comment.channelId,
158
+ target: `${conversation.kind}:${conversation.id}`,
159
159
  error: err instanceof Error ? err.message : String(err),
160
160
  }));
161
161
  });
package/lib/protocol.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  */
5
5
  import type { IncomingMessage } from 'node:http';
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
6
7
  import { type GenericWebhookPayload, type IssueCommentPayload, type PRReviewCommentPayload, type PRReviewPayload } from './types.js';
7
8
  export type { EventType, GenericWebhookPayload, GitHubComment, GitHubEndpointConfig, GitHubIssue, GitHubOAuthUser, GitHubPR, GitHubRepo, GitHubUser, IssueCommentPayload, ParsedChannel, PRReviewCommentPayload, PRReviewPayload, Subscription, } from './types.js';
8
9
  export { buildChannelId, parseChannelId } from './types.js';
@@ -65,6 +66,12 @@ export interface GithubInboundComment {
65
66
  readonly kind: 'issue_comment' | 'pr_review_comment' | 'pr_review';
66
67
  readonly createdAt: number;
67
68
  }
69
+ /**
70
+ * 入站归一化 → ConversationRef:Issue/PR 评论区即仓库容器内的 channel 会话,
71
+ * 仓库(owner/repo)进 `parent`。出站侧用 `parseChannelId(conversation.id)`
72
+ * 在平台边界还原 repo/type/number。
73
+ */
74
+ export declare function githubInboundConversation(endpointId: string, comment: GithubInboundComment): ConversationRef;
68
75
  export declare function resolveGithubConfig(config?: GithubAdapterConfig): ResolvedGithubConfig;
69
76
  export declare function normalizeWebhookPath(path: string): string;
70
77
  export declare function shouldAutoReplyRepo(config: Pick<ResolvedGithubConfig, 'autoReplyRepos'>, repo: string): boolean;
package/lib/protocol.js CHANGED
@@ -9,6 +9,19 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
9
9
  const logger = getLogger('github');
10
10
  import { buildChannelId, } from './types.js';
11
11
  export { buildChannelId, parseChannelId } from './types.js';
12
+ /**
13
+ * 入站归一化 → ConversationRef:Issue/PR 评论区即仓库容器内的 channel 会话,
14
+ * 仓库(owner/repo)进 `parent`。出站侧用 `parseChannelId(conversation.id)`
15
+ * 在平台边界还原 repo/type/number。
16
+ */
17
+ export function githubInboundConversation(endpointId, comment) {
18
+ return {
19
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
20
+ kind: 'channel',
21
+ id: comment.channelId,
22
+ parent: { kind: 'channel', id: comment.repo },
23
+ };
24
+ }
12
25
  export function resolveGithubConfig(config = {}) {
13
26
  const entry = config.endpoints?.find((item) => item.context === 'github');
14
27
  const appIdRaw = pickCredential(config.appId != null ? String(config.appId) : undefined, config.app_id != null ? String(config.app_id) : undefined, entry?.appId != null ? String(entry.appId) : undefined, entry?.app_id != null ? String(entry.app_id) : undefined, process.env.GITHUB_APP_ID);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-github",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "description": "Zhin.js GitHub adapter for Plugin Runtime (App auth + webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,21 +41,22 @@
41
41
  "directory": "plugins/adapters/github"
42
42
  },
43
43
  "dependencies": {
44
- "@zhin.js/adapter": "1.1.4",
45
- "@zhin.js/command": "1.0.6",
46
- "@zhin.js/core": "1.5.1",
47
- "@zhin.js/host-http": "1.0.5",
44
+ "@zhin.js/adapter": "1.1.5",
45
+ "@zhin.js/command": "1.0.7",
46
+ "@zhin.js/core": "1.5.2",
47
+ "@zhin.js/host-http": "1.0.6",
48
+ "@zhin.js/im-contract": "1.0.1",
48
49
  "@zhin.js/logger": "1.0.75",
49
- "@zhin.js/plugin-runtime": "1.1.2"
50
+ "@zhin.js/plugin-runtime": "1.1.3"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "zod": "^4.0.0",
53
- "@zhin.js/adapter": "1.1.4",
54
- "@zhin.js/agent": "1.1.2",
55
- "@zhin.js/core": "1.5.1",
56
- "@zhin.js/host-http": "1.0.5",
57
- "@zhin.js/plugin-runtime": "1.1.2",
58
- "zhin.js": "6.0.1"
54
+ "@zhin.js/adapter": "1.1.5",
55
+ "@zhin.js/agent": "1.1.3",
56
+ "@zhin.js/core": "1.5.2",
57
+ "@zhin.js/host-http": "1.0.6",
58
+ "@zhin.js/plugin-runtime": "1.1.3",
59
+ "zhin.js": "6.0.2"
59
60
  },
60
61
  "peerDependenciesMeta": {
61
62
  "zhin.js": {
@@ -73,8 +74,8 @@
73
74
  "typescript": "^6.0.3",
74
75
  "vitest": "^4.1.10",
75
76
  "zod": "^4.4.3",
76
- "@zhin.js/agent": "1.1.2",
77
- "zhin.js": "6.0.1"
77
+ "@zhin.js/agent": "1.1.3",
78
+ "zhin.js": "6.0.2"
78
79
  },
79
80
  "files": [
80
81
  "adapters",
package/src/endpoint.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * GithubEndpoint — lifecycle, outbound send, inbound admit.
3
3
  */
4
4
  import path from 'node:path';
5
- import type { EndpointInstance } from '@zhin.js/adapter';
5
+ import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
@@ -14,6 +14,7 @@ import {
14
14
  enrichInboundContent,
15
15
  formatInboundContent,
16
16
  formatOutboundBody,
17
+ githubInboundConversation,
17
18
  parseChannelId,
18
19
  type GithubInboundComment,
19
20
  type ResolvedGithubConfig,
@@ -147,9 +148,9 @@ export class GithubEndpoint implements EndpointInstance {
147
148
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.name }));
148
149
  }
149
150
 
150
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
151
- const parsed = parseChannelId(target);
152
- if (!parsed) throw new Error(`无效的 GitHub channel ID: ${target}`);
151
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
152
+ const parsed = parseChannelId(conversation.id);
153
+ if (!parsed) throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
153
154
  const text = formatOutboundBody(payload);
154
155
  const r = parsed.type === 'issue'
155
156
  ? await this.gh.createIssueComment(parsed.repo, parsed.number, text)
@@ -158,7 +159,7 @@ export class GithubEndpoint implements EndpointInstance {
158
159
  logger.debug(formatCompact({
159
160
  op: 'github_send',
160
161
  endpoint: this.name,
161
- target,
162
+ target: `${conversation.kind}:${conversation.id}`,
162
163
  messageId: r.data.id,
163
164
  }));
164
165
  return String(r.data.id);
@@ -169,6 +170,7 @@ export class GithubEndpoint implements EndpointInstance {
169
170
  if (!this.#open) return;
170
171
  const botUser = this.config.botLogin || this.gh.getBotLogin() || this.gh.authenticatedUser;
171
172
  if (botUser && comment.sender === botUser) return;
173
+ const conversation = githubInboundConversation(String(this.#options.id), comment);
172
174
  const content = enrichInboundContent(
173
175
  formatInboundContent(comment.content),
174
176
  this.config,
@@ -176,11 +178,10 @@ export class GithubEndpoint implements EndpointInstance {
176
178
  comment.repo,
177
179
  );
178
180
  void this.#options.gateway.receive({
179
- adapter: this.#options.id,
180
- target: comment.channelId,
181
+ conversation,
182
+ message: { conversation, id: comment.id },
181
183
  content,
182
184
  sender: comment.sender,
183
- id: comment.id,
184
185
  metadata: Object.freeze({
185
186
  endpoint: this.name,
186
187
  repo: comment.repo,
@@ -190,7 +191,7 @@ export class GithubEndpoint implements EndpointInstance {
190
191
  }).catch((err) => {
191
192
  logger.warn(formatCompact({
192
193
  op: 'github_gateway_receive_failed',
193
- target: comment.channelId,
194
+ target: `${conversation.kind}:${conversation.id}`,
194
195
  error: err instanceof Error ? err.message : String(err),
195
196
  }));
196
197
  });
package/src/protocol.ts CHANGED
@@ -7,6 +7,7 @@ import { createHmac, timingSafeEqual } from 'node:crypto';
7
7
  import type { IncomingMessage } from 'node:http';
8
8
  import { pickCredential } from '@zhin.js/adapter';
9
9
  import { isMediaRef } from '@zhin.js/core';
10
+ import type { ConversationRef } from '@zhin.js/im-contract';
10
11
  import { formatCompact, getLogger } from '@zhin.js/logger';
11
12
 
12
13
  const logger = getLogger('github');
@@ -100,6 +101,23 @@ export interface GithubInboundComment {
100
101
  readonly createdAt: number;
101
102
  }
102
103
 
104
+ /**
105
+ * 入站归一化 → ConversationRef:Issue/PR 评论区即仓库容器内的 channel 会话,
106
+ * 仓库(owner/repo)进 `parent`。出站侧用 `parseChannelId(conversation.id)`
107
+ * 在平台边界还原 repo/type/number。
108
+ */
109
+ export function githubInboundConversation(
110
+ endpointId: string,
111
+ comment: GithubInboundComment,
112
+ ): ConversationRef {
113
+ return {
114
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
115
+ kind: 'channel',
116
+ id: comment.channelId,
117
+ parent: { kind: 'channel', id: comment.repo },
118
+ };
119
+ }
120
+
103
121
  export function resolveGithubConfig(config: GithubAdapterConfig = {}): ResolvedGithubConfig {
104
122
  const entry = config.endpoints?.find((item) => item.context === 'github');
105
123
  const appIdRaw = pickCredential(