@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.
Files changed (93) hide show
  1. package/CHANGELOG.md +1118 -0
  2. package/README.md +70 -191
  3. package/adapters/github.js +51 -0
  4. package/adapters/github.ts +59 -0
  5. package/agent/prompt-sections/platform.ts +16 -0
  6. package/{skills/github/SKILL.md → agent/skills/github.md} +22 -5
  7. package/agent/tools/bind.ts +13 -0
  8. package/agent/tools/create_pr.ts +20 -0
  9. package/agent/tools/install.ts +13 -0
  10. package/agent/tools/patch_file.ts +19 -0
  11. package/agent/tools/prepare_workspace.ts +15 -0
  12. package/agent/tools/push_branch.ts +18 -0
  13. package/agent/tools/star.ts +16 -0
  14. package/agent/tools/subscribe.ts +16 -0
  15. package/agent/tools/subscriptions.ts +13 -0
  16. package/agent/tools/unbind.ts +13 -0
  17. package/agent/tools/unsubscribe.ts +15 -0
  18. package/agent/tools/whoami.ts +13 -0
  19. package/commands/endpoint/add/[id].js +3 -0
  20. package/commands/endpoint/add/[id].ts +3 -0
  21. package/commands/endpoint/list.js +3 -0
  22. package/commands/endpoint/list.ts +3 -0
  23. package/commands/endpoint/remove/[id].js +3 -0
  24. package/commands/endpoint/remove/[id].ts +3 -0
  25. package/lib/client.d.ts +36 -0
  26. package/lib/client.js +47 -0
  27. package/lib/endpoint.d.ts +32 -22
  28. package/lib/endpoint.js +125 -145
  29. package/lib/gh-client.d.ts +73 -1
  30. package/lib/gh-client.js +99 -1
  31. package/lib/github-bot-handlers.d.ts +27 -0
  32. package/lib/github-bot-handlers.js +76 -0
  33. package/lib/github-channel-context.d.ts +16 -0
  34. package/lib/github-channel-context.js +31 -0
  35. package/lib/github-endpoint-commands.d.ts +1 -0
  36. package/lib/github-endpoint-commands.js +22 -0
  37. package/lib/github-runtime-state.d.ts +1 -0
  38. package/lib/github-runtime-state.js +6 -0
  39. package/lib/github-tool-handlers.d.ts +18 -0
  40. package/lib/github-tool-handlers.js +214 -0
  41. package/lib/index.d.ts +7 -32
  42. package/lib/index.js +7 -385
  43. package/lib/oauth-users.d.ts +33 -0
  44. package/lib/oauth-users.js +38 -0
  45. package/lib/protocol.d.ts +94 -0
  46. package/lib/protocol.js +292 -0
  47. package/lib/types.d.ts +6 -1
  48. package/lib/types.js +0 -1
  49. package/lib/webhook.d.ts +14 -0
  50. package/lib/webhook.js +88 -0
  51. package/lib/workspace-manager.d.ts +21 -0
  52. package/lib/workspace-manager.js +154 -0
  53. package/package.json +77 -23
  54. package/plugin.js +38 -0
  55. package/schema.json +130 -0
  56. package/src/client.ts +65 -0
  57. package/src/endpoint.ts +145 -150
  58. package/src/gh-client.ts +131 -0
  59. package/src/github-bot-handlers.ts +113 -0
  60. package/src/github-channel-context.ts +46 -0
  61. package/src/github-endpoint-commands.ts +23 -0
  62. package/src/github-runtime-state.ts +7 -0
  63. package/src/github-tool-handlers.ts +252 -0
  64. package/src/index.ts +48 -431
  65. package/src/oauth-users.ts +47 -0
  66. package/src/protocol.ts +425 -0
  67. package/src/types.ts +6 -0
  68. package/src/webhook.ts +130 -0
  69. package/src/workspace-manager.ts +168 -0
  70. package/lib/adapter.d.ts +0 -64
  71. package/lib/adapter.d.ts.map +0 -1
  72. package/lib/adapter.js +0 -416
  73. package/lib/adapter.js.map +0 -1
  74. package/lib/agent-prompt.d.ts +0 -3
  75. package/lib/agent-prompt.d.ts.map +0 -1
  76. package/lib/agent-prompt.js +0 -82
  77. package/lib/agent-prompt.js.map +0 -1
  78. package/lib/endpoint.d.ts.map +0 -1
  79. package/lib/endpoint.js.map +0 -1
  80. package/lib/gh-client.d.ts.map +0 -1
  81. package/lib/gh-client.js.map +0 -1
  82. package/lib/index.d.ts.map +0 -1
  83. package/lib/index.js.map +0 -1
  84. package/lib/register-github-mcp.d.ts +0 -6
  85. package/lib/register-github-mcp.d.ts.map +0 -1
  86. package/lib/register-github-mcp.js +0 -35
  87. package/lib/register-github-mcp.js.map +0 -1
  88. package/lib/types.d.ts.map +0 -1
  89. package/lib/types.js.map +0 -1
  90. package/plugin.yml +0 -3
  91. package/src/adapter.ts +0 -448
  92. package/src/agent-prompt.ts +0 -99
  93. package/src/register-github-mcp.ts +0 -60
package/lib/index.js CHANGED
@@ -1,387 +1,9 @@
1
- /**
2
- * GitHub 适配器入口:类型扩展、模型、导出、注册
3
- */
4
- import { formatCompact, registerAgentPromptContributor, unregisterAgentPromptContributor, usePlugin } from 'zhin.js';
5
- import { createGithubAgentPromptContributor } from './agent-prompt.js';
6
- import { GitHubAdapter } from './adapter.js';
7
- import { GhClient } from './gh-client.js';
8
- import { registerGithubMcp } from './register-github-mcp.js';
1
+ export { buildChannelId, enrichInboundContent, formatInboundContent, formatNotification, formatOutboundBody, normalizeWebhookPath, parseChannelId, parseIssueCommentInbound, parsePrReviewCommentInbound, parsePrReviewInbound, resolveGithubConfig, shouldAutoReplyRepo, verifyWebhookSignature, } from './protocol.js';
9
2
  export * from './types.js';
10
- export { GitHubEndpoint, parseMarkdown, toMarkdown } from './endpoint.js';
11
- export { GitHubAdapter } from './adapter.js';
12
3
  export { GhClient } from './gh-client.js';
13
- const plugin = usePlugin();
14
- const { provide, defineModel, useContext, logger } = plugin;
15
- registerGithubMcp(plugin);
16
- defineModel('github_subscriptions', {
17
- id: { type: 'integer', primary: true },
18
- repo: { type: 'text', nullable: false },
19
- events: { type: 'json', default: [] },
20
- target_id: { type: 'text', nullable: false },
21
- target_type: { type: 'text', nullable: false },
22
- adapter: { type: 'text', nullable: false },
23
- endpoint: { type: 'text', nullable: false },
24
- });
25
- defineModel('github_events', {
26
- id: { type: 'integer', primary: true },
27
- repo: { type: 'text', nullable: false },
28
- event_type: { type: 'text', nullable: false },
29
- payload: { type: 'json', default: {} },
30
- });
31
- defineModel('github_oauth_users', {
32
- id: { type: 'integer', primary: true },
33
- platform: { type: 'text', nullable: false },
34
- platform_uid: { type: 'text', nullable: false },
35
- github_login: { type: 'text', nullable: false },
36
- access_token: { type: 'text', nullable: false },
37
- created_at: { type: 'integer', default: 0 },
38
- });
39
- provide({
40
- name: 'github',
41
- description: 'GitHub Adapter — Issues/PRs as chat channels, full repo management via gh CLI',
42
- mounted: async (p) => {
43
- registerAgentPromptContributor(createGithubAgentPromptContributor());
44
- const adapter = new GitHubAdapter(p);
45
- await adapter.start();
46
- return adapter;
47
- },
48
- dispose: async (adapter) => {
49
- unregisterAgentPromptContributor('github');
50
- await adapter.stop();
51
- },
52
- });
53
- // 混合模式:有 router + webhook_secret → Webhook 实时;否则 → 轮询降级
54
- useContext('github', (adapter) => {
55
- if (adapter.hasWebhookConfig) {
56
- // 尝试注册 Webhook(需要 router Context)
57
- const router = plugin.inject('router');
58
- if (router) {
59
- adapter.setupWebhook(router);
60
- logger.debug(formatCompact({ op: 'events', source: 'webhook' }));
61
- }
62
- else {
63
- // router 还没就绪,等它挂载后再注册
64
- plugin.useContext('router', (r) => {
65
- adapter.setupWebhook(r);
66
- logger.debug(formatCompact({ op: 'events', source: 'webhook', deferred: true }));
67
- });
68
- }
69
- }
70
- // Webhook 未配置或未激活时,总是启动轮询作为兜底
71
- if (!adapter.webhookActive) {
72
- adapter.startPolling();
73
- logger.debug(formatCompact({ op: 'events', source: 'poll' }));
74
- }
75
- return () => adapter.stopPolling();
76
- });
77
- // ── Tool 工具注册 ─────────────────────────────────────────────────────────
78
- useContext('tool', 'github', (toolService, adapter) => {
79
- const tools = [
80
- // --- Star ---
81
- {
82
- name: 'github_star',
83
- description: 'Star 或取消 Star 一个 GitHub 仓库(使用你绑定的 GitHub 账号,未绑定则用 Endpoint 默认账号)',
84
- parameters: {
85
- type: 'object',
86
- properties: {
87
- action: { type: 'string', description: 'star|unstar|check', enum: ['star', 'unstar', 'check'] },
88
- repo: { type: 'string', description: 'owner/repo (必填)' },
89
- },
90
- required: ['action', 'repo'],
91
- },
92
- tags: ['github'],
93
- execute: async (args, commMessage) => {
94
- const gh = await adapter.getUserOrDefaultAPI(commMessage?.$adapter, commMessage?.$sender.id);
95
- if (!gh)
96
- return '❌ 没有可用的 GitHub bot';
97
- const { action, repo } = args;
98
- switch (action) {
99
- case 'star': {
100
- const r = await gh.starRepo(repo);
101
- return r.ok ? `⭐ 已 Star ${repo}` : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
102
- }
103
- case 'unstar': {
104
- const r = await gh.unstarRepo(repo);
105
- return r.ok ? `💔 已取消 Star ${repo}` : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
106
- }
107
- case 'check': {
108
- const starred = await gh.isStarred(repo);
109
- return starred ? `⭐ 已 Star ${repo}` : `☆ 尚未 Star ${repo}`;
110
- }
111
- default: return `❌ 未知操作: ${action}`;
112
- }
113
- },
114
- },
115
- // --- Bind (Device Flow) ---
116
- {
117
- name: 'github_bind',
118
- description: '绑定你的 GitHub 账号 — 使用 Device Flow 授权,无需输入密码',
119
- parameters: {
120
- type: 'object',
121
- properties: {},
122
- },
123
- tags: ['github'],
124
- execute: async (_args, commMessage) => {
125
- if (!commMessage?.$adapter || !commMessage?.$sender?.id) {
126
- return '❌ 无法获取当前用户信息';
127
- }
128
- const clientId = adapter.getClientId();
129
- if (!clientId)
130
- return '❌ Endpoint 未配置 GitHub App 或 App 无 client_id,无法进行账号绑定';
131
- const db = plugin.root?.inject('database');
132
- const model = db?.models?.get('github_oauth_users');
133
- if (!model)
134
- return '❌ 数据库未就绪';
135
- // 检查是否已绑定
136
- const [existing] = await model.select().where({ platform: commMessage.$adapter, platform_uid: commMessage.$sender.id });
137
- if (existing) {
138
- return `⚠️ 你已绑定 GitHub 账号: ${existing.github_login}\n如需重新绑定,请先执行 github_unbind`;
139
- }
140
- try {
141
- const host = adapter.getHost();
142
- const codeResp = await GhClient.deviceFlowRequestCode(clientId, host);
143
- // 异步轮询 token(最多等 codeResp.expires_in 秒)
144
- const tokenPromise = GhClient.deviceFlowPollToken(clientId, codeResp.device_code, codeResp.interval, codeResp.expires_in, host);
145
- // 先回复用户授权链接
146
- const replyMsg = [
147
- `🔗 请在浏览器中打开以下链接进行授权:`,
148
- ` ${codeResp.verification_uri}`,
149
- ``,
150
- `📋 输入验证码: **${codeResp.user_code}**`,
151
- ``,
152
- `⏳ 等待授权中…(${Math.floor(codeResp.expires_in / 60)} 分钟内有效)`,
153
- ].join('\n');
154
- // 在后台等待用户授权完成
155
- tokenPromise.then(async (tokenData) => {
156
- if (!tokenData) {
157
- // 授权超时或被拒绝 — 由于 execute 已经返回了,这里只能通过日志记录
158
- logger.warn(formatCompact({ op: 'device_flow', ok: false, platform: commMessage.$adapter, sender: commMessage.$sender.id }));
159
- return;
160
- }
161
- // 使用 token 获取 GitHub 用户名
162
- const userGh = new GhClient({ host, token: tokenData.access_token });
163
- const authResult = await userGh.verifyAuth();
164
- const login = authResult.ok ? authResult.user : 'unknown';
165
- await model.insert({
166
- id: Date.now(),
167
- platform: commMessage.$adapter,
168
- platform_uid: commMessage.$sender.id,
169
- github_login: login,
170
- access_token: tokenData.access_token,
171
- created_at: Date.now(),
172
- });
173
- logger.info(formatCompact({ op: 'bind', platform: commMessage.$adapter, sender: commMessage.$sender.id, login }));
174
- // 尝试回复绑定成功消息
175
- if (commMessage?.$reply) {
176
- await commMessage.$reply(`✅ GitHub 账号绑定成功!\n👤 ${login}`);
177
- }
178
- }).catch(err => {
179
- logger.error('GitHub Device Flow 错误:', err);
180
- });
181
- return replyMsg;
182
- }
183
- catch (e) {
184
- return `❌ Device Flow 启动失败: ${e instanceof Error ? e.message : String(e)}`;
185
- }
186
- },
187
- },
188
- // --- Unbind ---
189
- {
190
- name: 'github_unbind',
191
- description: '解除你绑定的 GitHub 账号',
192
- parameters: {
193
- type: 'object',
194
- properties: {},
195
- },
196
- tags: ['github'],
197
- execute: async (_args, commMessage) => {
198
- if (!commMessage?.$adapter || !commMessage?.$sender?.id) {
199
- return '❌ 无法获取当前用户信息';
200
- }
201
- const db = plugin.root?.inject('database');
202
- const model = db?.models?.get('github_oauth_users');
203
- if (!model)
204
- return '❌ 数据库未就绪';
205
- const [existing] = await model.select().where({ platform: commMessage.$adapter, platform_uid: commMessage.$sender.id });
206
- if (!existing)
207
- return '📭 你尚未绑定 GitHub 账号';
208
- await model.delete().where({ id: existing.id });
209
- return `✅ 已解除 GitHub 账号绑定: ${existing.github_login}`;
210
- },
211
- },
212
- // --- Whoami ---
213
- {
214
- name: 'github_whoami',
215
- description: '查看你绑定的 GitHub 账号信息',
216
- parameters: {
217
- type: 'object',
218
- properties: {},
219
- },
220
- tags: ['github'],
221
- execute: async (_args, commMessage) => {
222
- if (!commMessage?.$adapter || !commMessage?.$sender?.id) {
223
- return '❌ 无法获取当前用户信息';
224
- }
225
- const db = plugin.root?.inject('database');
226
- const model = db?.models?.get('github_oauth_users');
227
- if (!model)
228
- return '❌ 数据库未就绪';
229
- const [existing] = await model.select().where({ platform: commMessage.$adapter, platform_uid: commMessage.$sender.id });
230
- if (!existing)
231
- return '📭 你尚未绑定 GitHub 账号\n🔗 使用 github_bind 绑定你的账号';
232
- // 验证 token 是否仍然有效
233
- const userGh = new GhClient({ host: adapter.getHost(), token: existing.access_token });
234
- const auth = await userGh.verifyAuth();
235
- if (auth.ok) {
236
- return `👤 已绑定 GitHub 账号: ${auth.user}\n📅 绑定时间: ${new Date(existing.created_at).toLocaleString('zh-CN')}`;
237
- }
238
- return `⚠️ 已绑定账号 ${existing.github_login},但 Token 已失效\n🔗 请执行 github_unbind 后重新 github_bind`;
239
- },
240
- },
241
- // --- Install App ---
242
- {
243
- name: 'github_install',
244
- description: '获取安装 GitHub App 的链接 — 安装后 Endpoint 可以访问你的仓库,你也可以使用更多功能',
245
- parameters: {
246
- type: 'object',
247
- properties: {},
248
- },
249
- tags: ['github'],
250
- execute: async () => {
251
- const slug = adapter.getAppSlug();
252
- if (!slug)
253
- return '❌ Endpoint 未配置 GitHub App';
254
- const host = adapter.getHost() || 'github.com';
255
- const installations = adapter.getInstallations();
256
- let msg = `🔗 请点击以下链接安装 GitHub App 到你的仓库:\n https://${host}/apps/${slug}/installations/new`;
257
- if (installations.length) {
258
- msg += `\n\n📋 当前已安装 (${installations.length}):`;
259
- for (const inst of installations) {
260
- msg += `\n • ${inst.account.login} (${inst.account.type})`;
261
- }
262
- }
263
- return msg;
264
- },
265
- },
266
- // --- Subscribe ---
267
- {
268
- name: 'github_subscribe',
269
- description: '订阅 GitHub 仓库的 Webhook 事件,事件将推送到当前聊天通道',
270
- parameters: {
271
- type: 'object',
272
- properties: {
273
- repo: { type: 'string', description: 'owner/repo (必填)' },
274
- events: { type: 'string', description: '要订阅的事件类型,逗号分隔 (push/issue/star/fork/unstar/pull_request),默认全部' },
275
- },
276
- required: ['repo'],
277
- },
278
- platforms: ['github'],
279
- tags: ['github'],
280
- execute: async (args, commMessage) => {
281
- if (!commMessage?.$adapter || !commMessage?.$sender.id || !commMessage?.$channel?.id || !commMessage?.$endpoint) {
282
- return '❌ 无法获取当前聊天通道信息';
283
- }
284
- const db = plugin.root?.inject('database');
285
- const model = db?.models?.get('github_subscriptions');
286
- if (!model)
287
- return '❌ 数据库未就绪';
288
- const validEvents = ['push', 'issue', 'star', 'fork', 'unstar', 'pull_request'];
289
- const events = args.events
290
- ? args.events.split(',').map((s) => s.trim()).filter((e) => validEvents.includes(e))
291
- : validEvents;
292
- if (!events.length)
293
- return `❌ 无效的事件类型,可选: ${validEvents.join(', ')}`;
294
- const [existing] = await model.select().where({
295
- repo: args.repo,
296
- target_id: commMessage.$channel?.id,
297
- adapter: commMessage.$adapter,
298
- endpoint: commMessage.$endpoint,
299
- });
300
- if (existing) {
301
- await model.update({ events, target_type: commMessage.$channel?.type || 'private' }).where({ id: existing.id });
302
- return `✅ 已更新订阅 ${args.repo}\n📡 事件: ${events.join(', ')}`;
303
- }
304
- await model.insert({
305
- id: Date.now(),
306
- repo: args.repo,
307
- events,
308
- target_id: commMessage.$channel?.id,
309
- target_type: commMessage.$channel?.type || 'private',
310
- adapter: commMessage.$adapter,
311
- endpoint: commMessage.$endpoint,
312
- });
313
- return `✅ 已订阅 ${args.repo}\n📡 事件: ${events.join(', ')}\n📌 通知将推送到当前通道`;
314
- },
315
- },
316
- // --- Unsubscribe ---
317
- {
318
- name: 'github_unsubscribe',
319
- description: '取消订阅 GitHub 仓库的 Webhook 事件',
320
- parameters: {
321
- type: 'object',
322
- properties: {
323
- repo: { type: 'string', description: 'owner/repo (必填)' },
324
- },
325
- required: ['repo'],
326
- },
327
- platforms: ['github'],
328
- tags: ['github'],
329
- execute: async (args, commMessage) => {
330
- if (!commMessage?.$adapter || !commMessage?.$channel?.id || !commMessage?.$endpoint) {
331
- return '❌ 无法获取当前聊天通道信息';
332
- }
333
- const db = plugin.root?.inject('database');
334
- const model = db?.models?.get('github_subscriptions');
335
- if (!model)
336
- return '❌ 数据库未就绪';
337
- const [existing] = await model.select().where({
338
- repo: args.repo,
339
- target_id: commMessage.$channel?.id,
340
- adapter: commMessage.$adapter,
341
- endpoint: commMessage.$endpoint,
342
- });
343
- if (!existing)
344
- return `📭 当前通道未订阅 ${args.repo}`;
345
- await model.delete().where({ id: existing.id });
346
- return `✅ 已取消订阅 ${args.repo}`;
347
- },
348
- },
349
- // --- Subscriptions ---
350
- {
351
- name: 'github_subscriptions',
352
- description: '查看当前聊天通道的 GitHub 仓库订阅列表',
353
- parameters: {
354
- type: 'object',
355
- properties: {},
356
- },
357
- platforms: ['github'],
358
- tags: ['github'],
359
- execute: async (_args, commMessage) => {
360
- if (!commMessage?.$adapter || !commMessage?.$channel?.id || !commMessage?.$endpoint) {
361
- return '❌ 无法获取当前聊天通道信息';
362
- }
363
- const db = plugin.root?.inject('database');
364
- const model = db?.models?.get('github_subscriptions');
365
- if (!model)
366
- return '❌ 数据库未就绪';
367
- const subs = await model.select().where({
368
- target_id: commMessage.$channel?.id,
369
- adapter: commMessage.$adapter,
370
- endpoint: commMessage.$endpoint,
371
- });
372
- if (!subs?.length)
373
- return '📭 当前通道没有任何 GitHub 订阅';
374
- return `📋 当前通道订阅 (${subs.length}):\n\n` +
375
- subs.map((s) => {
376
- const events = Array.isArray(s.events) ? s.events : [];
377
- return ` 📦 ${s.repo}\n 📡 ${events.join(', ') || '(无事件)'}`;
378
- }).join('\n\n');
379
- },
380
- },
381
- ];
382
- const disposers = tools.map(t => toolService.addTool(t, plugin.name));
383
- logger.debug(`GitHub 工具已注册: ${tools.map(t => t.name).join(', ')}`);
384
- return () => disposers.forEach(d => d());
385
- });
386
- logger.debug('GitHub 适配器已加载 (gh CLI 认证)');
387
- //# sourceMappingURL=index.js.map
4
+ export { GithubClient, githubClient } from './client.js';
5
+ export { WorkspaceManager } from './workspace-manager.js';
6
+ export { parseMessageChannel, issueBranchName, resolveWorkspaceBranch, formatChannelContext, } from './github-channel-context.js';
7
+ export { GithubEndpoint, defaultCreateClient, } from './endpoint.js';
8
+ export { GITHUB_OAUTH_USERS_TABLE, GITHUB_OAUTH_USERS_SCHEMA, defineGithubOauthUsersTable, lookupGithubOauthAccessToken, } from './oauth-users.js';
9
+ export { registerGithubWebhookRoutes, handleGithubWebhookRequest, dispatchGithubWebhookPayload, } from './webhook.js';
@@ -0,0 +1,33 @@
1
+ import type { DatabaseHost, PluginDatabaseHost } from 'zhin.js';
2
+ export declare const GITHUB_OAUTH_USERS_TABLE = "github_oauth_users";
3
+ export declare const GITHUB_OAUTH_USERS_SCHEMA: {
4
+ readonly id: {
5
+ readonly type: "integer";
6
+ readonly primary: true;
7
+ };
8
+ readonly platform: {
9
+ readonly type: "text";
10
+ readonly nullable: false;
11
+ };
12
+ readonly platform_uid: {
13
+ readonly type: "text";
14
+ readonly nullable: false;
15
+ };
16
+ readonly github_login: {
17
+ readonly type: "text";
18
+ readonly nullable: false;
19
+ };
20
+ readonly access_token: {
21
+ readonly type: "text";
22
+ readonly nullable: false;
23
+ };
24
+ readonly created_at: {
25
+ readonly type: "integer";
26
+ readonly nullable: false;
27
+ };
28
+ };
29
+ export declare function defineGithubOauthUsersTable(host: {
30
+ define(name: string, definition: Record<string, unknown>): void;
31
+ }): void;
32
+ /** Resolve a stored PAT/device-flow token; null when DB missing or no row. */
33
+ export declare function lookupGithubOauthAccessToken(database: DatabaseHost | PluginDatabaseHost | undefined, platform: string, platformUid: string): Promise<string | null>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * github_oauth_users — SSOT for Runtime user-token binding (gh bind / Endpoint lookup).
3
+ */
4
+ import { formatCompact, getLogger } from '@zhin.js/logger';
5
+ const logger = getLogger('github');
6
+ export const GITHUB_OAUTH_USERS_TABLE = 'github_oauth_users';
7
+ export const GITHUB_OAUTH_USERS_SCHEMA = {
8
+ id: { type: 'integer', primary: true },
9
+ platform: { type: 'text', nullable: false },
10
+ platform_uid: { type: 'text', nullable: false },
11
+ github_login: { type: 'text', nullable: false },
12
+ access_token: { type: 'text', nullable: false },
13
+ created_at: { type: 'integer', nullable: false },
14
+ };
15
+ export function defineGithubOauthUsersTable(host) {
16
+ host.define(GITHUB_OAUTH_USERS_TABLE, { ...GITHUB_OAUTH_USERS_SCHEMA });
17
+ }
18
+ /** Resolve a stored PAT/device-flow token; null when DB missing or no row. */
19
+ export async function lookupGithubOauthAccessToken(database, platform, platformUid) {
20
+ if (!database?.started)
21
+ return null;
22
+ const model = database.models.get(GITHUB_OAUTH_USERS_TABLE);
23
+ if (!model)
24
+ return null;
25
+ try {
26
+ const rows = await model.select().where({ platform, platform_uid: platformUid });
27
+ const token = rows[0]?.access_token;
28
+ return typeof token === 'string' && token.trim() ? token : null;
29
+ }
30
+ catch (error) {
31
+ logger.debug(formatCompact({
32
+ op: 'oauth_lookup_fail',
33
+ platform,
34
+ error: error instanceof Error ? error.message : String(error),
35
+ }));
36
+ return null;
37
+ }
38
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * GitHub webhook / comment helpers — no legacy Adapter/Endpoint / segment-mapper.
3
+ * Canonicalization is owned by gateway/core before endpoint.send.
4
+ */
5
+ import type { IncomingMessage } from 'node:http';
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
7
+ import { type GenericWebhookPayload, type IssueCommentPayload, type PRReviewCommentPayload, type PRReviewPayload } from './types.js';
8
+ export type { EventType, GenericWebhookPayload, GitHubComment, GitHubEndpointConfig, GitHubIssue, GitHubOAuthUser, GitHubPR, GitHubRepo, GitHubUser, IssueCommentPayload, ParsedChannel, PRReviewCommentPayload, PRReviewPayload, Subscription, } from './types.js';
9
+ export { buildChannelId, parseChannelId } from './types.js';
10
+ /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
11
+ export interface GithubAdapterConfig {
12
+ readonly id?: string;
13
+ readonly host?: string;
14
+ readonly app_id?: string | number;
15
+ readonly appId?: string | number;
16
+ readonly private_key?: string;
17
+ readonly privateKey?: string;
18
+ readonly webhook_secret?: string;
19
+ readonly webhookSecret?: string;
20
+ readonly webhook_path?: string;
21
+ readonly webhookPath?: string;
22
+ readonly poll_interval?: number;
23
+ readonly pollInterval?: number;
24
+ readonly auto_reply_repos?: readonly string[];
25
+ readonly autoReplyRepos?: readonly string[];
26
+ readonly bot_login?: string;
27
+ readonly botLogin?: string;
28
+ readonly workspace_root?: string;
29
+ readonly workspaceRoot?: string;
30
+ /** Transitional: legacy root `endpoints[]` with `context: github`. */
31
+ readonly endpoints?: ReadonlyArray<Partial<ResolvedGithubConfig> & {
32
+ readonly context?: string;
33
+ readonly app_id?: string | number;
34
+ readonly private_key?: string;
35
+ readonly webhook_secret?: string;
36
+ readonly webhook_path?: string;
37
+ readonly poll_interval?: number;
38
+ readonly auto_reply_repos?: readonly string[];
39
+ readonly bot_login?: string;
40
+ readonly workspace_root?: string;
41
+ }>;
42
+ }
43
+ export interface ResolvedGithubConfig {
44
+ readonly context: 'github';
45
+ readonly id: string;
46
+ readonly host?: string;
47
+ readonly appId?: string | number;
48
+ readonly privateKey?: string;
49
+ readonly webhookSecret?: string;
50
+ readonly webhookPath: string;
51
+ readonly pollInterval: number;
52
+ readonly autoReplyRepos: readonly string[];
53
+ readonly botLogin?: string;
54
+ readonly workspaceRoot?: string;
55
+ }
56
+ export interface GithubWireSegment {
57
+ readonly type: string;
58
+ readonly data?: Record<string, unknown>;
59
+ }
60
+ export interface GithubInboundComment {
61
+ readonly id: string;
62
+ readonly channelId: string;
63
+ readonly sender: string;
64
+ readonly content: string;
65
+ readonly repo: string;
66
+ readonly kind: 'issue_comment' | 'pr_review_comment' | 'pr_review';
67
+ readonly createdAt: number;
68
+ }
69
+ /**
70
+ * 入站归一化 → ConversationRef:Issue/PR 评论区即仓库容器内的 channel 会话,
71
+ * 仓库(owner/repo)进 `parent`。出站侧用 `parseChannelId(conversation.id)`
72
+ * 在平台边界还原 repo/type/number。
73
+ */
74
+ export declare function githubInboundConversation(endpointKey: string, comment: GithubInboundComment): ConversationRef;
75
+ export declare function resolveGithubConfig(config?: GithubAdapterConfig): ResolvedGithubConfig;
76
+ export declare function normalizeWebhookPath(path: string): string;
77
+ export declare function shouldAutoReplyRepo(config: Pick<ResolvedGithubConfig, 'autoReplyRepos'>, repo: string): boolean;
78
+ /** Prepend synthetic @bot for auto_reply repos (gateway text path). */
79
+ export declare function enrichInboundContent(content: string, config: Pick<ResolvedGithubConfig, 'autoReplyRepos' | 'botLogin'>, botLogin: string | undefined, repo: string): string;
80
+ /** Build inbound text for OutboundMessageService.receive from markdown/comment body. */
81
+ export declare function formatInboundContent(body: string): string;
82
+ /** Build markdown body for Issue/PR comment send. */
83
+ export declare function formatOutboundBody(payload: unknown): string;
84
+ export declare function verifyWebhookSignature(secret: string, rawBody: string, signatureHeader: string | undefined): boolean;
85
+ export declare function headerValue(headers: IncomingMessage['headers'], name: string): string;
86
+ export declare function readTextBody(request: IncomingMessage, options?: {
87
+ readonly limit?: number;
88
+ }): Promise<string>;
89
+ export declare function parseIssueCommentInbound(payload: IssueCommentPayload): GithubInboundComment | null;
90
+ export declare function parsePrReviewCommentInbound(payload: PRReviewCommentPayload): GithubInboundComment | null;
91
+ export declare function parsePrReviewInbound(payload: PRReviewPayload): GithubInboundComment | null;
92
+ export declare function formatNotification(event: string, p: GenericWebhookPayload): string;
93
+ /** Parse @mentions in markdown into a flat display string (gateway text path). */
94
+ export declare function parseMarkdownMentions(md: string): string;