@zhin.js/adapter-github 1.1.18 → 1.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-github",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "description": "Zhin.js GitHub adapter for Plugin Runtime (App auth + webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -39,28 +39,28 @@
39
39
  "directory": "plugins/adapters/github"
40
40
  },
41
41
  "dependencies": {
42
- "@zhin.js/adapter": "1.1.25",
43
- "@zhin.js/agent-feature": "1.1.10",
44
- "@zhin.js/client": "1.1.21",
42
+ "@zhin.js/adapter": "1.1.26",
43
+ "@zhin.js/agent-feature": "1.1.11",
44
+ "@zhin.js/client": "1.1.22",
45
45
  "@zhin.js/console-contract": "1.1.3",
46
- "@zhin.js/core": "1.1.53",
47
- "@zhin.js/feature-kit": "1.1.9",
48
- "@zhin.js/host-http": "1.1.11",
46
+ "@zhin.js/core": "1.1.54",
47
+ "@zhin.js/feature-kit": "1.1.10",
48
+ "@zhin.js/host-http": "1.1.12",
49
49
  "@zhin.js/im-contract": "1.1.9",
50
50
  "@zhin.js/logger": "1.1.2",
51
- "@zhin.js/page": "1.1.10",
52
- "@zhin.js/skill": "1.1.10",
53
- "@zhin.js/tool": "1.1.12"
51
+ "@zhin.js/page": "1.1.12",
52
+ "@zhin.js/skill": "1.1.11",
53
+ "@zhin.js/tool": "1.1.13"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "zod": "^4.0.0",
57
- "@zhin.js/adapter": "^1.1.25",
58
- "@zhin.js/agent": "^1.1.44",
59
- "@zhin.js/command": "^1.1.11",
60
- "@zhin.js/core": "^1.1.53",
61
- "@zhin.js/host-http": "^1.1.11",
62
- "@zhin.js/prompt-section": "^1.1.9",
63
- "zhin.js": "^1.1.18"
57
+ "@zhin.js/adapter": "^1.1.26",
58
+ "@zhin.js/agent": "^1.1.45",
59
+ "@zhin.js/command": "^1.1.12",
60
+ "@zhin.js/core": "^1.1.54",
61
+ "@zhin.js/host-http": "^1.1.12",
62
+ "@zhin.js/prompt-section": "^1.1.10",
63
+ "zhin.js": "^1.1.19"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "@zhin.js/agent": {
@@ -84,9 +84,9 @@
84
84
  "typescript": "^6.0.3",
85
85
  "vitest": "^4.1.10",
86
86
  "zod": "^4.4.3",
87
- "@zhin.js/agent": "1.1.44",
88
- "@zhin.js/prompt-section": "1.1.9",
89
- "zhin.js": "1.1.18"
87
+ "@zhin.js/agent": "1.1.45",
88
+ "@zhin.js/prompt-section": "1.1.10",
89
+ "zhin.js": "1.1.19"
90
90
  },
91
91
  "files": [
92
92
  "adapters",
@@ -117,7 +117,7 @@
117
117
  "features": [
118
118
  {
119
119
  "package": "@zhin.js/adapter",
120
- "api": "^1.0.0"
120
+ "api": "^2.0.0"
121
121
  },
122
122
  {
123
123
  "package": "@zhin.js/command",
package/plugin.js CHANGED
@@ -1,7 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { createEndpointRuntimeState } from 'zhin.js/adapter';
3
2
  import { definePlugin, databaseHostToken, pluginConsoleRpcToken } from 'zhin.js';
4
- import { githubRuntimeStateToken } from "./lib/github-runtime-state.js";
5
3
  import { defineGithubOauthUsersTable } from "./lib/oauth-users.js";
6
4
  import { createGithubConsoleRpc } from "./lib/console.js";
7
5
  /**
@@ -29,7 +27,6 @@ export default definePlugin({
29
27
  },
30
28
  setup(context) {
31
29
  // 运行中 endpoint 注册表(github endpoint list 的"运行中"数据源)
32
- context.resources.provide(githubRuntimeStateToken, createEndpointRuntimeState());
33
30
  context.resources.provide(pluginConsoleRpcToken, createGithubConsoleRpc(context.resources, () => context.config.get()));
34
31
  if (context.resources.has(databaseHostToken)) {
35
32
  const host = context.resources.use(databaseHostToken);
package/src/adapter.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { GhClient } from './gh-client.js';
2
+ import { Adapter, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
3
+ import { GithubEndpoint } from './endpoint.js';
4
+ import { resolveGithubConfig, type GithubEndpointConfig } from './protocol.js';
5
+ import type { GithubClientEventMap } from './client.js';
6
+ import { httpHostToken } from '@zhin.js/host-http';
7
+ import { databaseHostToken } from 'zhin.js';
8
+
9
+ export class GithubAdapter extends Adapter<GithubEndpoint, GithubEndpointConfig, GithubClientEventMap> {
10
+ readonly #context: AdapterContext<GithubEndpointConfig>;
11
+ constructor(context: AdapterContext<GithubEndpointConfig>) { super(); this.#context = context; }
12
+ override get configurationIdentityFields(): readonly string[] { return ['id', 'app_id', 'host']; }
13
+ override get configurationUpdateFields(): readonly string[] { return ['private_key', 'webhook_secret', 'webhook_path', 'auto_reply_repos', 'bot_login', 'workspace_root']; }
14
+ override get configurationIdentityProbeFields(): readonly string[] { return ['private_key']; }
15
+ protected override async identifyConfiguration(input: GithubEndpointConfig, signal: AbortSignal, _changed: readonly string[]): Promise<string | undefined> {
16
+ const config = resolveGithubConfig(input), client = new GhClient({ host: config.host, appAuth: { appId: config.appId, privateKey: config.privateKey }, signal });
17
+ const authenticated = await client.verifyAuth(); signal.throwIfAborted();
18
+ if (!authenticated.ok) throw new Error('GitHub application identity verification failed');
19
+ const identity = await client.getBotIdentity(); signal.throwIfAborted();
20
+ if (!identity?.userId) throw new Error('GitHub identity verification returned no bot userId');
21
+ return JSON.stringify(['github', config.host ?? 'github.com', identity.userId]);
22
+ }
23
+ protected override validateConfiguration(config: GithubEndpointConfig): void { resolveGithubConfig(config); }
24
+ protected createEndpoint(input: GithubEndpointConfig, execution: EndpointCreationContext): GithubEndpoint {
25
+ const config = resolveGithubConfig(input);
26
+ return new GithubEndpoint({ id: execution.canonicalId, config, ...(config.webhookSecret ? { http: this.#context.use(httpHostToken) } : {}), ...(this.#context.has?.(databaseHostToken) ? { database: this.#context.use(databaseHostToken) } : {}) });
27
+ }
28
+ }
29
+
30
+ declare module '@zhin.js/feature-kit' {
31
+ interface AdapterRegistry { readonly 'github': GithubAdapter; }
32
+ }
package/src/client.ts CHANGED
@@ -1,62 +1 @@
1
- import type { PluginDatabaseHost } from 'zhin.js';
2
- import { defineEndpointClient } from 'zhin.js/adapter';
3
- import { GhClient } from './gh-client.js';
4
- import { lookupGithubOauthAccessToken } from './oauth-users.js';
5
- import type { ResolvedGithubConfig } from './protocol.js';
6
- import { WorkspaceManager } from './workspace-manager.js';
7
-
8
- /** GitHub SDK surface exposed to plugin handlers and Agent tools. */
9
- export class GithubClient {
10
- #workspaceManager?: WorkspaceManager;
11
-
12
- constructor(
13
- readonly name: string,
14
- readonly api: GhClient,
15
- readonly config: ResolvedGithubConfig,
16
- readonly database?: PluginDatabaseHost,
17
- ) {}
18
-
19
- async getUserOrDefaultApi(platform?: string, platformUid?: string): Promise<GhClient> {
20
- if (platform && platformUid) {
21
- const token = await lookupGithubOauthAccessToken(this.database, platform, platformUid);
22
- if (token) return this.api.withToken(token);
23
- }
24
- return this.api;
25
- }
26
-
27
- get clientId(): string | null {
28
- return this.api.clientId || null;
29
- }
30
-
31
- get host(): string | undefined {
32
- return this.config.host;
33
- }
34
-
35
- get appSlug(): string | null {
36
- return this.api.appSlug || null;
37
- }
38
-
39
- get installations() {
40
- return this.api.installations || [];
41
- }
42
-
43
- get workspaceManager(): WorkspaceManager {
44
- if (!this.#workspaceManager) {
45
- this.#workspaceManager = new WorkspaceManager(this.api, this.config.workspaceRoot);
46
- }
47
- return this.#workspaceManager;
48
- }
49
- }
50
-
51
1
  export type GithubClientEventMap = Record<string, unknown>;
52
-
53
- declare module '@zhin.js/feature-kit' {
54
- interface AdapterClientRegistry {
55
- readonly github: {
56
- readonly client: GithubClient;
57
- readonly events: GithubClientEventMap;
58
- };
59
- }
60
- }
61
-
62
- export const githubClient = defineEndpointClient<GithubClient, GithubClientEventMap>('github');
package/src/console.ts CHANGED
@@ -5,17 +5,14 @@
5
5
  */
6
6
  import { createAdapterConsoleRpc, type AdapterConsoleResources } from 'zhin.js/adapter';
7
7
  import type { PluginConsoleRpc } from 'zhin.js';
8
- import { githubRuntimeStateToken } from './github-runtime-state.js';
9
8
  import { githubConsoleSpec } from './console-spec.js';
10
9
 
11
10
  export function createGithubConsoleRpc(
12
- resources: AdapterConsoleResources,
11
+ _resources: AdapterConsoleResources,
13
12
  getConfig: () => unknown,
14
13
  ): PluginConsoleRpc {
15
- const state = resources.use(githubRuntimeStateToken);
16
14
  return createAdapterConsoleRpc({
17
15
  ...githubConsoleSpec,
18
- running: () => state.endpoints.values(),
19
16
  getConfig,
20
- }, resources);
17
+ });
21
18
  }
package/src/endpoint.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { Endpoint } from 'zhin.js/adapter';
1
+ import { createEndpointSpecification, endpointSpec, type Endpoint, type EndpointSendRequest } from 'zhin.js/adapter';
2
2
  /**
3
3
  * GithubEndpoint — lifecycle, outbound send, inbound admit.
4
4
  */
5
- import { type EndpointSendRequest } from 'zhin.js/adapter';
6
5
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
7
6
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
7
  import type { CapabilityId, PluginDatabaseHost } from 'zhin.js';
@@ -17,98 +16,110 @@ import {
17
16
  type ResolvedGithubConfig,
18
17
  } from './protocol.js';
19
18
  import { registerGithubWebhookRoutes } from './webhook.js';
20
- import { GithubClient } from './client.js';
19
+ import { WorkspaceManager } from './workspace-manager.js';
20
+ import { lookupGithubOauthAccessToken } from './oauth-users.js';
21
21
 
22
22
  export interface GithubEndpointOptions {
23
23
  readonly id: CapabilityId;
24
24
  readonly http?: HttpHost;
25
25
  readonly database?: PluginDatabaseHost;
26
26
  readonly config: ResolvedGithubConfig;
27
- readonly createClient?: (config: ResolvedGithubConfig) => GhClient;
28
27
  }
29
28
 
30
29
  /**
31
30
  * GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
32
31
  * 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
33
32
  */
34
- export class GithubEndpoint extends Endpoint<GithubClient> {
33
+ export class GithubEndpoint extends GhClient implements Endpoint {
34
+ readonly [endpointSpec] = createEndpointSpecification(this, {
35
+ start: signal => this.#startFramework(signal), open: () => this.#openFramework(), close: () => this.#closeFramework(),
36
+ stop: () => this.#stopFramework(), send: request => this.#sendFramework(request),
37
+ });
38
+ #workspaceManager?: WorkspaceManager;
39
+ get database() { return this.#options.database; }
40
+ get host(): string | undefined { return this.#options.config.host; }
41
+ async getUserOrDefaultApi(platform?: string, platformUid?: string): Promise<GhClient> {
42
+ if (platform && platformUid) { const token = await lookupGithubOauthAccessToken(this.database, platform, platformUid); if (token) return this.withToken(token); }
43
+ return this;
44
+ }
45
+ get workspaceManager(): WorkspaceManager { return this.#workspaceManager ??= new WorkspaceManager(this, this.config.workspaceRoot); }
35
46
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
36
47
 
37
48
  readonly #options: GithubEndpointOptions;
38
- readonly client: GithubClient;
39
49
  #routeReleases: HttpRouteRegistration[] = [];
40
50
  #open = false;
41
51
  #started = false;
42
52
 
43
53
  constructor(options: GithubEndpointOptions) {
44
- super();
54
+ super({ host: options.config.host, appAuth: { appId: options.config.appId, privateKey: options.config.privateKey } });
45
55
  this.#logger = getAdapterLogger('github', options.config.id);
46
56
  this.#options = options;
47
- const api = options.createClient?.(options.config) ?? defaultCreateClient(options.config);
48
- this.client = new GithubClient(options.config.id, api, options.config, options.database);
57
+
49
58
  }
50
59
 
51
60
  get config(): ResolvedGithubConfig {
52
- return this.client.config;
61
+ return this.#options.config;
53
62
  }
54
63
 
55
- async start(): Promise<void> {
64
+ async #startFramework(signal: AbortSignal): Promise<void> {
65
+ this.bindExecutionSignal(signal); signal.throwIfAborted();
56
66
  if (this.#started) return;
57
67
  this.#started = true;
58
68
  try {
59
- const result = await this.client.api.verifyAuth();
69
+ const result = await this.verifyAuth();
70
+ signal.throwIfAborted();
60
71
  if (!result.ok) throw new Error(`GitHub 认证失败: ${result.message}`);
61
- if (this.client.config.webhookSecret) {
72
+ if (this.#options.config.webhookSecret) {
62
73
  if (!this.#options.http) {
63
74
  throw new TypeError('GitHub webhook_secret requires httpHostToken');
64
75
  }
65
76
  this.#routeReleases.push(...registerGithubWebhookRoutes(this.#options.http, this));
66
77
  this.#logger.debug(formatCompact({
67
- endpoint: this.client.name,
78
+ endpoint: this.#options.config.id,
68
79
  op: 'webhook',
69
- path: this.client.config.webhookPath,
80
+ path: this.#options.config.webhookPath,
70
81
  }));
71
82
  } else {
72
83
  this.#logger.debug(formatCompact({
73
- endpoint: this.client.name,
84
+ endpoint: this.#options.config.id,
74
85
  op: 'connect',
75
86
  mode: 'api-only',
76
- bot: this.client.api.authenticatedUser,
87
+ bot: this.authenticatedUser,
77
88
  }));
78
89
  }
79
90
  } catch (error) {
80
- await this.stop();
91
+ await this.#stopFramework();
81
92
  this.#logger.error('Failed to connect GitHub endpoint:', error);
82
93
  throw error;
83
94
  }
84
95
  }
85
96
 
86
- open(): void {
97
+ #openFramework(): void {
87
98
  this.#open = true;
88
99
  }
89
100
 
90
- close(): void {
101
+ #closeFramework(): void {
91
102
  this.#open = false;
92
103
  }
93
104
 
94
- async stop(): Promise<void> {
105
+ async #stopFramework(): Promise<void> {
95
106
  this.#open = false;
96
107
  for (const release of this.#routeReleases.splice(0)) release();
97
108
  this.#started = false;
98
109
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
99
110
  }
100
111
 
101
- async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
112
+ async #sendFramework({ conversation, payload, signal }: EndpointSendRequest): Promise<string> {
102
113
  const parsed = parseChannelId(conversation.id);
103
114
  if (!parsed) throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
104
115
  const text = formatOutboundBody(payload);
105
116
  const r = parsed.type === 'issue'
106
- ? await this.client.api.createIssueComment(parsed.repo, parsed.number, text)
107
- : await this.client.api.createPRComment(parsed.repo, parsed.number, text);
117
+ ? await this.createIssueComment({ repo: parsed.repo, number: parsed.number, body: text, signal })
118
+ : await this.createPRComment({ repo: parsed.repo, number: parsed.number, body: text, signal });
108
119
  if (!r.ok) throw new Error(`发送失败: ${JSON.stringify(r.data)}`);
109
120
  this.#logger.debug(formatCompact({
110
121
  op: 'github_send',
111
- endpoint: this.client.name,
122
+ endpoint: this.#options.config.id,
112
123
  target: `${conversation.kind}:${conversation.id}`,
113
124
  messageId: r.data.id,
114
125
  }));
@@ -116,25 +127,33 @@ export class GithubEndpoint extends Endpoint<GithubClient> {
116
127
  }
117
128
 
118
129
  /** Test / internal: admit a parsed comment when open. */
119
- admit(comment: GithubInboundComment): void {
130
+ admit(comment: GithubInboundComment, nativeEvent: unknown = comment): void {
131
+ if (!this.#open) return;
132
+ void this[endpointSpec].events.run(() => this.#admitComment(comment, nativeEvent)).catch(error => {
133
+ this.#logger.warn(formatCompact({ op: 'github_gateway_receive_failed', error: error instanceof Error ? error.message : String(error) }));
134
+ });
135
+ }
136
+
137
+ #admitComment(comment: GithubInboundComment, nativeEvent: unknown = comment): void {
120
138
  if (!this.#open) return;
121
- const botUser = this.client.config.botLogin
122
- || this.client.api.getBotLogin()
123
- || this.client.api.authenticatedUser;
139
+ const botUser = this.#options.config.botLogin
140
+ || this.getBotLogin()
141
+ || this.authenticatedUser;
124
142
  if (botUser && comment.sender === botUser) return;
125
143
  const conversation = githubInboundConversation(String(this.#options.id), comment);
126
144
  const content = enrichInboundContent(
127
145
  formatInboundContent(comment.content),
128
- this.client.config,
146
+ this.#options.config,
129
147
  botUser ?? undefined,
130
148
  comment.repo,
131
149
  );
132
- void this.emit('message.receive', {
150
+ void this[endpointSpec].events.emit('message.receive', {
151
+ event: nativeEvent,
133
152
  conversation,
134
153
  message: { conversation, id: comment.id },
135
154
  content,
136
155
  sender: { id: comment.sender, name: comment.sender },
137
- endpointId: this.client.name,
156
+ endpointId: this.#options.config.id,
138
157
  metadata: Object.freeze({
139
158
  repo: comment.repo,
140
159
  kind: comment.kind,
@@ -151,7 +170,7 @@ export class GithubEndpoint extends Endpoint<GithubClient> {
151
170
 
152
171
  admitPlatform(name: string, event: unknown): void {
153
172
  if (!this.#open) return;
154
- void this.emitPlatform(name, event).catch((error) => {
173
+ void this[endpointSpec].events.run(() => this[endpointSpec].events.platform(name, event)).catch((error) => {
155
174
  this.#logger.warn(formatCompact({
156
175
  op: 'github_platform_event_failed',
157
176
  event: name,