@zhin.js/adapter-github 1.1.15 → 1.1.16

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 (44) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +4 -0
  3. package/agents/github/messages.js +72 -0
  4. package/agents/github/messages.ts +72 -0
  5. package/agents/github/skills/github-account/handlers.js +30 -25
  6. package/agents/github/skills/github-account/handlers.ts +30 -25
  7. package/agents/github/skills/github-account/tools/bind/index.js +1 -1
  8. package/agents/github/skills/github-account/tools/bind/index.ts +1 -1
  9. package/agents/github/skills/github-account/tools/install/index.js +1 -1
  10. package/agents/github/skills/github-account/tools/install/index.ts +1 -1
  11. package/agents/github/skills/github-account/tools/unbind/index.js +1 -1
  12. package/agents/github/skills/github-account/tools/unbind/index.ts +1 -1
  13. package/agents/github/skills/github-account/tools/whoami/index.js +1 -1
  14. package/agents/github/skills/github-account/tools/whoami/index.ts +1 -1
  15. package/agents/github/skills/github-repository/handlers.js +82 -35
  16. package/agents/github/skills/github-repository/handlers.ts +83 -31
  17. package/agents/github/skills/github-repository/tools/create_pr/index.js +1 -1
  18. package/agents/github/skills/github-repository/tools/create_pr/index.ts +1 -1
  19. package/agents/github/skills/github-repository/tools/patch_file/index.js +1 -1
  20. package/agents/github/skills/github-repository/tools/patch_file/index.ts +1 -1
  21. package/agents/github/skills/github-repository/tools/prepare_workspace/index.js +1 -1
  22. package/agents/github/skills/github-repository/tools/prepare_workspace/index.ts +1 -1
  23. package/agents/github/skills/github-repository/tools/push_branch/index.js +1 -1
  24. package/agents/github/skills/github-repository/tools/push_branch/index.ts +1 -1
  25. package/agents/github/skills/github-subscriptions/handlers.js +85 -22
  26. package/agents/github/skills/github-subscriptions/handlers.ts +86 -22
  27. package/agents/github/skills/github-subscriptions/tools/star/index.js +1 -1
  28. package/agents/github/skills/github-subscriptions/tools/star/index.ts +1 -1
  29. package/agents/github/skills/github-subscriptions/tools/subscribe/index.js +1 -1
  30. package/agents/github/skills/github-subscriptions/tools/subscribe/index.ts +1 -1
  31. package/agents/github/skills/github-subscriptions/tools/subscriptions/index.js +1 -1
  32. package/agents/github/skills/github-subscriptions/tools/subscriptions/index.ts +1 -1
  33. package/agents/github/skills/github-subscriptions/tools/unsubscribe/index.js +1 -1
  34. package/agents/github/skills/github-subscriptions/tools/unsubscribe/index.ts +1 -1
  35. package/commands/github/endpoint/definition.js +2 -2
  36. package/commands/github/endpoint/definition.ts +2 -2
  37. package/lib/github-channel-context.d.ts +2 -1
  38. package/lib/github-channel-context.js +6 -2
  39. package/lib/workspace-manager.d.ts +8 -7
  40. package/lib/workspace-manager.js +62 -50
  41. package/package.json +19 -19
  42. package/schema.json +41 -6
  43. package/src/github-channel-context.ts +7 -1
  44. package/src/workspace-manager.ts +62 -50
@@ -1,3 +1,4 @@
1
+ import { createMessages, resolveLocale, type Locale } from 'zhin.js/i18n';
1
2
  import type { Message } from 'zhin.js';
2
3
  import { getCurrentCommMessage } from '@zhin.js/agent/security';
3
4
  import type { GithubClient } from '../../../../src/client.js';
@@ -15,41 +16,43 @@ function subscriptionsModel(client: GithubClient) {
15
16
  } | undefined;
16
17
  }
17
18
 
18
- export async function executeGithubStar(args: { action: 'star' | 'unstar' | 'check'; repo: string }, client: GithubClient, commMessage?: Message) {
19
+ export async function executeGithubStar(args: { action: 'star' | 'unstar' | 'check'; repo: string }, client: GithubClient, commMessage?: Message, preference?: Locale) {
19
20
  const msg = commMessage ?? getCurrentCommMessage();
21
+ const locale = resolveLocale(preference ?? msg?.locale);
20
22
  const gh = await client.getUserOrDefaultApi(msg?.clientAdapter, msg?.sender?.id);
21
23
  switch (args.action) {
22
24
  case 'star': {
23
25
  const r = await gh.starRepo(args.repo);
24
- return r.ok ? `⭐ 已 Star ${args.repo}` : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
26
+ return r.ok ? t(locale, 'starred', { repo: args.repo }) : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
25
27
  }
26
28
  case 'unstar': {
27
29
  const r = await gh.unstarRepo(args.repo);
28
- return r.ok ? `💔 已取消 Star ${args.repo}` : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
30
+ return r.ok ? t(locale, 'unstarred', { repo: args.repo }) : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
29
31
  }
30
32
  case 'check': {
31
33
  const starred = await gh.isStarred(args.repo);
32
- return starred ? `⭐ 已 Star ${args.repo}` : `☆ 尚未 Star ${args.repo}`;
34
+ return starred ? t(locale, 'starred', { repo: args.repo }) : t(locale, 'notStarred', { repo: args.repo });
33
35
  }
34
36
  default:
35
- return `❌ 未知操作: ${args.action}`;
37
+ return t(locale, 'unknownAction', { action: args.action });
36
38
  }
37
39
  }
38
40
 
39
- export async function executeGithubSubscribe(args: { repo: string; events?: string }, client: GithubClient, commMessage?: Message) {
41
+ export async function executeGithubSubscribe(args: { repo: string; events?: string }, client: GithubClient, commMessage?: Message, preference?: Locale) {
40
42
  const msg = commMessage ?? getCurrentCommMessage();
43
+ const locale = resolveLocale(preference ?? msg?.locale);
41
44
  if (!msg?.clientAdapter || !msg?.sender?.id || !msg?.conversation.id || !msg?.endpointId) {
42
- return '❌ 无法获取当前聊天通道信息';
45
+ return t(locale, 'missingChannel');
43
46
  }
44
47
 
45
48
  const model = subscriptionsModel(client);
46
- if (!model) return '❌ 数据库未就绪';
49
+ if (!model) return t(locale, 'databaseNotReady');
47
50
 
48
51
  const validEvents: EventType[] = ['push', 'issue', 'star', 'fork', 'unstar', 'pull_request'];
49
52
  const events: EventType[] = args.events
50
53
  ? args.events.split(',').map((s) => s.trim()).filter((e): e is EventType => validEvents.includes(e as EventType))
51
54
  : validEvents;
52
- if (!events.length) return `❌ 无效的事件类型,可选: ${validEvents.join(', ')}`;
55
+ if (!events.length) return t(locale, 'invalidEvents', { events: validEvents.join(', ') });
53
56
 
54
57
  const [existing] = await model.select().where({
55
58
  repo: args.repo,
@@ -59,7 +62,7 @@ export async function executeGithubSubscribe(args: { repo: string; events?: stri
59
62
  });
60
63
  if (existing) {
61
64
  await model.update({ events, target_type: msg.conversation.kind || 'private' }).where({ id: existing.id });
62
- return `✅ 已更新订阅 ${args.repo}\n📡 事件: ${events.join(', ')}`;
65
+ return t(locale, 'updated', { repo: args.repo, events: events.join(', ') });
63
66
  }
64
67
 
65
68
  await model.insert({
@@ -71,17 +74,18 @@ export async function executeGithubSubscribe(args: { repo: string; events?: stri
71
74
  adapter: msg.clientAdapter,
72
75
  endpoint: msg.endpointId,
73
76
  });
74
- return `✅ 已订阅 ${args.repo}\n📡 事件: ${events.join(', ')}\n📌 通知将推送到当前通道`;
77
+ return t(locale, 'subscribed', { repo: args.repo, events: events.join(', ') });
75
78
  }
76
79
 
77
- export async function executeGithubUnsubscribe(args: { repo: string }, client: GithubClient, commMessage?: Message) {
80
+ export async function executeGithubUnsubscribe(args: { repo: string }, client: GithubClient, commMessage?: Message, preference?: Locale) {
78
81
  const msg = commMessage ?? getCurrentCommMessage();
82
+ const locale = resolveLocale(preference ?? msg?.locale);
79
83
  if (!msg?.clientAdapter || !msg?.conversation.id || !msg?.endpointId) {
80
- return '❌ 无法获取当前聊天通道信息';
84
+ return t(locale, 'missingChannel');
81
85
  }
82
86
 
83
87
  const model = subscriptionsModel(client);
84
- if (!model) return '❌ 数据库未就绪';
88
+ if (!model) return t(locale, 'databaseNotReady');
85
89
 
86
90
  const [existing] = await model.select().where({
87
91
  repo: args.repo,
@@ -89,31 +93,91 @@ export async function executeGithubUnsubscribe(args: { repo: string }, client: G
89
93
  adapter: msg.clientAdapter,
90
94
  endpoint: msg.endpointId,
91
95
  });
92
- if (!existing) return `📭 当前通道未订阅 ${args.repo}`;
96
+ if (!existing) return t(locale, 'notSubscribed', { repo: args.repo });
93
97
 
94
98
  await model.delete().where({ id: existing.id });
95
- return `✅ 已取消订阅 ${args.repo}`;
99
+ return t(locale, 'unsubscribed', { repo: args.repo });
96
100
  }
97
101
 
98
- export async function executeGithubSubscriptions(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
102
+ export async function executeGithubSubscriptions(_args: Record<string, never>, client: GithubClient, commMessage?: Message, preference?: Locale) {
99
103
  const msg = commMessage ?? getCurrentCommMessage();
104
+ const locale = resolveLocale(preference ?? msg?.locale);
100
105
  if (!msg?.clientAdapter || !msg?.conversation.id || !msg?.endpointId) {
101
- return '❌ 无法获取当前聊天通道信息';
106
+ return t(locale, 'missingChannel');
102
107
  }
103
108
 
104
109
  const model = subscriptionsModel(client);
105
- if (!model) return '❌ 数据库未就绪';
110
+ if (!model) return t(locale, 'databaseNotReady');
106
111
 
107
112
  const subs = await model.select().where({
108
113
  target_id: msg.conversation.id,
109
114
  adapter: msg.clientAdapter,
110
115
  endpoint: msg.endpointId,
111
116
  });
112
- if (!subs?.length) return '📭 当前通道没有任何 GitHub 订阅';
117
+ if (!subs?.length) return t(locale, 'empty');
113
118
 
114
- return `📋 当前通道订阅 (${subs.length}):\n\n` +
119
+ return t(locale, 'list', { count: subs.length }) +
115
120
  subs.map((s: any) => {
116
121
  const events = Array.isArray(s.events) ? s.events : [];
117
- return ` 📦 ${s.repo}\n 📡 ${events.join(', ') || '(无事件)'}`;
122
+ return ` 📦 ${s.repo}\n 📡 ${events.join(', ') || t(locale, 'noEvents')}`;
118
123
  }).join('\n\n');
119
124
  }
125
+
126
+ const t = createMessages({
127
+ "starred": {
128
+ "zh-CN": "⭐ 已 Star {repo}",
129
+ "en": "⭐ Starred: {repo}"
130
+ },
131
+ "unstarred": {
132
+ "zh-CN": "💔 已取消 Star {repo}",
133
+ "en": "💔 Unstarred: {repo}"
134
+ },
135
+ "notStarred": {
136
+ "zh-CN": "☆ 尚未 Star {repo}",
137
+ "en": "☆ Not starred: {repo}"
138
+ },
139
+ "unknownAction": {
140
+ "zh-CN": "❌ 未知操作: {action}",
141
+ "en": "❌ Unknown action: {action}"
142
+ },
143
+ "missingChannel": {
144
+ "zh-CN": "❌ 无法获取当前聊天通道信息",
145
+ "en": "❌ Current chat channel is unavailable"
146
+ },
147
+ "databaseNotReady": {
148
+ "zh-CN": "❌ 数据库未就绪",
149
+ "en": "❌ Database is not ready"
150
+ },
151
+ "invalidEvents": {
152
+ "zh-CN": "❌ 无效的事件类型,可选: {events}",
153
+ "en": "❌ Invalid event types. Available: {events}"
154
+ },
155
+ "updated": {
156
+ "zh-CN": "✅ 已更新订阅 {repo}\n📡 事件: {events}",
157
+ "en": "✅ Subscription updated: {repo}\n📡 Events: {events}"
158
+ },
159
+ "subscribed": {
160
+ "zh-CN": "✅ 已订阅 {repo}\n📡 事件: {events}\n📌 通知将推送到当前通道",
161
+ "en": "✅ Subscribed to {repo}\n📡 Events: {events}\n📌 Notifications will be sent to this channel"
162
+ },
163
+ "notSubscribed": {
164
+ "zh-CN": "📭 当前通道未订阅 {repo}",
165
+ "en": "📭 This channel is not subscribed to {repo}"
166
+ },
167
+ "unsubscribed": {
168
+ "zh-CN": "✅ 已取消订阅 {repo}",
169
+ "en": "✅ Unsubscribed from {repo}"
170
+ },
171
+ "empty": {
172
+ "zh-CN": "📭 当前通道没有任何 GitHub 订阅",
173
+ "en": "📭 This channel has no GitHub subscriptions"
174
+ },
175
+ "list": {
176
+ "zh-CN": "📋 当前通道订阅 ({count}):\n\n",
177
+ "en": "📋 Channel subscriptions ({count}):\n\n"
178
+ },
179
+ "noEvents": {
180
+ "zh-CN": "(无事件)",
181
+ "en": "(no events)"
182
+ }
183
+ });
@@ -11,6 +11,6 @@ export default defineAgentTool({
11
11
  }),
12
12
  tags: ['github'],
13
13
  async execute(input, context) {
14
- return executeGithubStar(input, context.$client);
14
+ return executeGithubStar(input, context.$client, undefined, context.locale);
15
15
  },
16
16
  });
@@ -11,6 +11,6 @@ export default defineAgentTool<{ action: 'star' | 'unstar' | 'check'; repo: stri
11
11
  }),
12
12
  tags: ['github'],
13
13
  async execute(input, context) {
14
- return executeGithubStar(input, context.$client);
14
+ return executeGithubStar(input, context.$client, undefined, context.locale);
15
15
  },
16
16
  });
@@ -11,6 +11,6 @@ export default defineAgentTool({
11
11
  adapter: 'github',
12
12
  tags: ['github'],
13
13
  async execute(input, context) {
14
- return executeGithubSubscribe(input, context.$client);
14
+ return executeGithubSubscribe(input, context.$client, undefined, context.locale);
15
15
  },
16
16
  });
@@ -11,6 +11,6 @@ export default defineAgentTool<{ repo: string; events?: string }>({
11
11
  adapter: 'github',
12
12
  tags: ['github'],
13
13
  async execute(input, context) {
14
- return executeGithubSubscribe(input, context.$client);
14
+ return executeGithubSubscribe(input, context.$client, undefined, context.locale);
15
15
  },
16
16
  });
@@ -8,6 +8,6 @@ export default defineAgentTool({
8
8
  adapter: 'github',
9
9
  tags: ['github'],
10
10
  async execute(input, context) {
11
- return executeGithubSubscriptions({}, context.$client);
11
+ return executeGithubSubscriptions({}, context.$client, undefined, context.locale);
12
12
  },
13
13
  });
@@ -8,6 +8,6 @@ export default defineAgentTool<{}>({
8
8
  adapter: 'github',
9
9
  tags: ['github'],
10
10
  async execute(input, context) {
11
- return executeGithubSubscriptions({}, context.$client);
11
+ return executeGithubSubscriptions({}, context.$client, undefined, context.locale);
12
12
  },
13
13
  });
@@ -10,6 +10,6 @@ export default defineAgentTool({
10
10
  adapter: 'github',
11
11
  tags: ['github'],
12
12
  async execute(input, context) {
13
- return executeGithubUnsubscribe(input, context.$client);
13
+ return executeGithubUnsubscribe(input, context.$client, undefined, context.locale);
14
14
  },
15
15
  });
@@ -10,6 +10,6 @@ export default defineAgentTool<{ repo: string }>({
10
10
  adapter: 'github',
11
11
  tags: ['github'],
12
12
  async execute(input, context) {
13
- return executeGithubUnsubscribe(input, context.$client);
13
+ return executeGithubUnsubscribe(input, context.$client, undefined, context.locale);
14
14
  },
15
15
  });
@@ -15,8 +15,8 @@ export const githubEndpointCommands = createEndpointCommands({
15
15
  adapterDisplayName: 'GitHub',
16
16
  fields: [
17
17
  { key: 'app_id', required: true, env: true, description: 'GitHub App ID' },
18
- { key: 'private_key', required: true, description: 'PEM 文件路径(推荐,如 ./data/xxx.pem)或 PEM 内容' },
19
- { key: 'webhook_secret', env: true, description: 'webhook 签名密钥(不配则 API-only)' },
18
+ { key: 'private_key', required: true, description: 'PEM 文件路径(推荐,如 ./data/xxx.pem)或 PEM 内容', descriptionI18n: { en: "PEM file path (recommended, such as ./data/app.pem) or PEM content" } },
19
+ { key: 'webhook_secret', env: true, description: 'webhook 签名密钥(不配则 API-only)', descriptionI18n: { en: "Webhook signing secret (API-only when omitted)" } },
20
20
  ],
21
21
  running: (use) => use(githubRuntimeStateToken).endpoints.values(),
22
22
  describeEntry: (entry) => `app_id: ${String(entry.app_id)}`,
@@ -15,8 +15,8 @@ export const githubEndpointCommands = createEndpointCommands({
15
15
  adapterDisplayName: 'GitHub',
16
16
  fields: [
17
17
  { key: 'app_id', required: true, env: true, description: 'GitHub App ID' },
18
- { key: 'private_key', required: true, description: 'PEM 文件路径(推荐,如 ./data/xxx.pem)或 PEM 内容' },
19
- { key: 'webhook_secret', env: true, description: 'webhook 签名密钥(不配则 API-only)' },
18
+ { key: 'private_key', required: true, description: 'PEM 文件路径(推荐,如 ./data/xxx.pem)或 PEM 内容', descriptionI18n: { en: "PEM file path (recommended, such as ./data/app.pem) or PEM content" } },
19
+ { key: 'webhook_secret', env: true, description: 'webhook 签名密钥(不配则 API-only)', descriptionI18n: { en: "Webhook signing secret (API-only when omitted)" } },
20
20
  ],
21
21
  running: (use) => use(githubRuntimeStateToken).endpoints.values(),
22
22
  describeEntry: (entry) => `app_id: ${String(entry.app_id)}`,
@@ -2,6 +2,7 @@
2
2
  * Parse GitHub Issue/PR channel context from IM messages.
3
3
  */
4
4
  import type { Message } from 'zhin.js';
5
+ import { type Locale } from 'zhin.js/i18n';
5
6
  import { type ParsedChannel } from './types.js';
6
7
  import type { GhClient } from './gh-client.js';
7
8
  export interface GitHubChannelContext extends ParsedChannel {
@@ -9,7 +10,7 @@ export interface GitHubChannelContext extends ParsedChannel {
9
10
  }
10
11
  export declare function parseMessageChannel(message: Message): GitHubChannelContext | null;
11
12
  export declare function issueBranchName(number: number): string;
12
- export declare function resolveWorkspaceBranch(gh: GhClient, ctx: GitHubChannelContext): Promise<{
13
+ export declare function resolveWorkspaceBranch(gh: GhClient, ctx: GitHubChannelContext, locale?: Locale): Promise<{
13
14
  branch: string;
14
15
  base: string;
15
16
  }>;
@@ -1,3 +1,4 @@
1
+ import { createMessages } from 'zhin.js/i18n';
1
2
  import { buildChannelId, parseChannelId } from './types.js';
2
3
  export function parseMessageChannel(message) {
3
4
  const channelId = message.conversation.id;
@@ -11,14 +12,14 @@ export function parseMessageChannel(message) {
11
12
  export function issueBranchName(number) {
12
13
  return `zhin/bot/issue-${number}`;
13
14
  }
14
- export async function resolveWorkspaceBranch(gh, ctx) {
15
+ export async function resolveWorkspaceBranch(gh, ctx, locale) {
15
16
  if (ctx.type === 'pr') {
16
17
  const pr = await gh.getPR(ctx.repo, ctx.number);
17
18
  if (pr.ok && pr.data?.head?.ref) {
18
19
  const base = pr.data.base?.ref ?? 'main';
19
20
  return { branch: pr.data.head.ref, base };
20
21
  }
21
- throw new Error(`无法读取 PR #${ctx.number} 的 head 分支`);
22
+ throw new Error(t(locale, 'missingHead', { number: ctx.number }));
22
23
  }
23
24
  const repoInfo = await gh.getRepo(ctx.repo);
24
25
  const base = repoInfo.ok && repoInfo.data?.default_branch
@@ -29,3 +30,6 @@ export async function resolveWorkspaceBranch(gh, ctx) {
29
30
  export function formatChannelContext(ctx) {
30
31
  return buildChannelId(ctx.repo, ctx.type, ctx.number);
31
32
  }
33
+ const t = createMessages({
34
+ missingHead: { 'zh-CN': '无法读取 PR #{number} 的 head 分支', en: 'Cannot read the head branch of PR #{number}' },
35
+ });
@@ -1,21 +1,22 @@
1
+ import { type Locale } from 'zhin.js/i18n';
1
2
  import type { GhClient, GitHubBotIdentity } from './gh-client.js';
2
3
  /**
3
4
  * 校验 GitHub "owner/name" 全名,防止路径穿越与 git 选项注入。
4
5
  * 返回通过校验的原值,便于调用方直接使用「已消毒」的变量。
5
6
  */
6
- export declare function assertRepoFullName(repo: string): string;
7
+ export declare function assertRepoFullName(repo: string, locale?: Locale): string;
7
8
  /**
8
9
  * 校验 git ref 名称,拒绝选项注入(`-` 前缀)与 git 非法字符。
9
10
  */
10
- export declare function assertGitRefName(ref: string): string;
11
+ export declare function assertGitRefName(ref: string, locale?: Locale): string;
11
12
  export declare class WorkspaceManager {
12
13
  private readonly gh;
13
14
  private readonly rootDir;
14
15
  constructor(gh: GhClient, rootDir: string);
15
- getRepoPath(repo: string): string;
16
+ getRepoPath(repo: string, locale?: Locale): string;
16
17
  private runGit;
17
- configureBotGit(repoPath: string, identity: GitHubBotIdentity): Promise<void>;
18
- ensureRepo(repo: string): Promise<string>;
19
- checkoutBranch(repo: string, branch: string, baseRef: string): Promise<string>;
20
- commitAndPush(repo: string, branch: string, message: string): Promise<string>;
18
+ configureBotGit(repoPath: string, identity: GitHubBotIdentity, locale?: Locale): Promise<void>;
19
+ ensureRepo(repo: string, locale?: Locale): Promise<string>;
20
+ checkoutBranch(repo: string, branch: string, baseRef: string, locale?: Locale): Promise<string>;
21
+ commitAndPush(repo: string, branch: string, message: string, locale?: Locale): Promise<string>;
21
22
  }
@@ -4,6 +4,7 @@
4
4
  import { spawn } from 'node:child_process';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import { createMessages } from 'zhin.js/i18n';
7
8
  const REPO_FULL_NAME_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
8
9
  const GIT_REF_ILLEGAL_CHARS = new Set('~^:?*[\\`;');
9
10
  function containsIllegalGitRefCharacter(ref) {
@@ -19,27 +20,27 @@ function containsIllegalGitRefCharacter(ref) {
19
20
  * 校验 GitHub "owner/name" 全名,防止路径穿越与 git 选项注入。
20
21
  * 返回通过校验的原值,便于调用方直接使用「已消毒」的变量。
21
22
  */
22
- export function assertRepoFullName(repo) {
23
+ export function assertRepoFullName(repo, locale) {
23
24
  if (typeof repo !== 'string' ||
24
25
  !REPO_FULL_NAME_RE.test(repo) ||
25
26
  repo.split('/').some((seg) => seg === '' || seg.startsWith('-') || /^\.+$/.test(seg))) {
26
- throw new TypeError(`非法的仓库全名: ${JSON.stringify(repo)}`);
27
+ throw new TypeError(t(locale, 'invalidRepo', { value: JSON.stringify(repo) }));
27
28
  }
28
29
  return repo;
29
30
  }
30
31
  /**
31
32
  * 校验 git ref 名称,拒绝选项注入(`-` 前缀)与 git 非法字符。
32
33
  */
33
- export function assertGitRefName(ref) {
34
+ export function assertGitRefName(ref, locale) {
34
35
  if (typeof ref !== 'string' || ref.length === 0) {
35
- throw new TypeError(`非法的 git ref: ${JSON.stringify(ref)}`);
36
+ throw new TypeError(t(locale, 'invalidRef', { value: JSON.stringify(ref) }));
36
37
  }
37
38
  if (ref.startsWith('-') ||
38
39
  ref.includes('..') ||
39
40
  containsIllegalGitRefCharacter(ref) ||
40
41
  ref.endsWith('/') ||
41
42
  ref.endsWith('.')) {
42
- throw new TypeError(`非法的 git ref: ${JSON.stringify(ref)}`);
43
+ throw new TypeError(t(locale, 'invalidRef', { value: JSON.stringify(ref) }));
43
44
  }
44
45
  return ref;
45
46
  }
@@ -50,12 +51,12 @@ export class WorkspaceManager {
50
51
  this.gh = gh;
51
52
  this.rootDir = rootDir;
52
53
  }
53
- getRepoPath(repo) {
54
- repo = assertRepoFullName(repo);
54
+ getRepoPath(repo, locale) {
55
+ repo = assertRepoFullName(repo, locale);
55
56
  const [owner, name] = repo.split('/');
56
57
  return path.join(this.rootDir, owner, name);
57
58
  }
58
- runGit(cwd, args, env) {
59
+ runGit(cwd, args, env, locale) {
59
60
  return new Promise((resolve, reject) => {
60
61
  const proc = spawn('git', args, {
61
62
  cwd,
@@ -68,7 +69,7 @@ export class WorkspaceManager {
68
69
  proc.stderr.on('data', (d) => { stderr += d.toString(); });
69
70
  proc.on('error', (err) => {
70
71
  if (err.code === 'ENOENT')
71
- reject(new Error('git 未安装'));
72
+ reject(new Error(t(locale, 'missingGit')));
72
73
  else
73
74
  reject(err);
74
75
  });
@@ -80,75 +81,86 @@ export class WorkspaceManager {
80
81
  });
81
82
  });
82
83
  }
83
- configureBotGit(repoPath, identity) {
84
- return Promise.all([
85
- this.runGit(repoPath, ['config', 'user.name', identity.login]),
86
- this.runGit(repoPath, ['config', 'user.email', identity.email]),
87
- ]).then(() => undefined);
84
+ async configureBotGit(repoPath, identity, locale) {
85
+ // Both writes acquire the same .git/config.lock.
86
+ await this.runGit(repoPath, ['config', 'user.name', identity.login], undefined, locale);
87
+ await this.runGit(repoPath, ['config', 'user.email', identity.email], undefined, locale);
88
88
  }
89
- async ensureRepo(repo) {
90
- repo = assertRepoFullName(repo);
89
+ async ensureRepo(repo, locale) {
90
+ repo = assertRepoFullName(repo, locale);
91
91
  if (!this.gh.isAppAuth) {
92
- throw new Error('GitHub App 认证未配置,无法托管工作区');
92
+ throw new Error(t(locale, 'appRequired'));
93
93
  }
94
94
  const token = await this.gh.ensureInstallationTokenForRepo(repo);
95
95
  if (!token)
96
- throw new Error(`无法获取 ${repo} 的 Installation Token`);
97
- const repoPath = this.getRepoPath(repo);
96
+ throw new Error(t(locale, 'repoTokenUnavailable', { repo }));
97
+ const repoPath = this.getRepoPath(repo, locale);
98
98
  fs.mkdirSync(path.dirname(repoPath), { recursive: true });
99
99
  const identity = await this.gh.getBotIdentity();
100
100
  if (!identity)
101
- throw new Error('无法解析 GitHub App Bot 身份');
101
+ throw new Error(t(locale, 'missingIdentity'));
102
102
  const cloneUrl = this.gh.buildCloneUrl(repo, token);
103
103
  if (!fs.existsSync(path.join(repoPath, '.git'))) {
104
- await this.runGit(path.dirname(repoPath), ['clone', cloneUrl, path.basename(repoPath)]);
104
+ await this.runGit(path.dirname(repoPath), ['clone', '--', cloneUrl, path.basename(repoPath)], undefined, locale);
105
105
  }
106
106
  else {
107
- await this.runGit(repoPath, ['remote', 'set-url', 'origin', cloneUrl]);
108
- await this.runGit(repoPath, ['fetch', 'origin', '--prune']);
107
+ await this.runGit(repoPath, ['remote', 'set-url', 'origin', cloneUrl], undefined, locale);
108
+ await this.runGit(repoPath, ['fetch', 'origin', '--prune'], undefined, locale);
109
109
  }
110
- await this.configureBotGit(repoPath, identity);
110
+ await this.configureBotGit(repoPath, identity, locale);
111
111
  return repoPath;
112
112
  }
113
- async checkoutBranch(repo, branch, baseRef) {
114
- repo = assertRepoFullName(repo);
115
- branch = assertGitRefName(branch);
116
- baseRef = assertGitRefName(baseRef);
117
- const repoPath = await this.ensureRepo(repo);
118
- const localBranches = await this.runGit(repoPath, ['branch', '--list', branch]);
113
+ async checkoutBranch(repo, branch, baseRef, locale) {
114
+ repo = assertRepoFullName(repo, locale);
115
+ branch = assertGitRefName(branch, locale);
116
+ baseRef = assertGitRefName(baseRef, locale);
117
+ const repoPath = await this.ensureRepo(repo, locale);
118
+ const localBranches = await this.runGit(repoPath, ['branch', '--list', branch], undefined, locale);
119
119
  if (localBranches.trim()) {
120
- await this.runGit(repoPath, ['checkout', branch]);
121
- await this.runGit(repoPath, ['pull', '--rebase', 'origin', branch]).catch(async () => {
122
- await this.runGit(repoPath, ['fetch', 'origin', branch]);
120
+ await this.runGit(repoPath, ['checkout', branch], undefined, locale);
121
+ await this.runGit(repoPath, ['pull', '--rebase', '--', 'origin', branch], undefined, locale).catch(async () => {
122
+ await this.runGit(repoPath, ['fetch', '--', 'origin', branch], undefined, locale);
123
123
  });
124
124
  return repoPath;
125
125
  }
126
- const remoteBranch = await this.runGit(repoPath, ['ls-remote', '--heads', 'origin', branch]);
126
+ const remoteBranch = await this.runGit(repoPath, ['ls-remote', '--heads', '--', 'origin', branch], undefined, locale);
127
127
  if (remoteBranch.trim()) {
128
- await this.runGit(repoPath, ['checkout', '-B', branch, `origin/${branch}`]);
128
+ await this.runGit(repoPath, ['checkout', '-B', branch, `origin/${branch}`], undefined, locale);
129
129
  return repoPath;
130
130
  }
131
- await this.runGit(repoPath, ['fetch', 'origin', baseRef]);
132
- await this.runGit(repoPath, ['checkout', '-B', branch, `origin/${baseRef}`]);
131
+ await this.runGit(repoPath, ['fetch', '--', 'origin', baseRef], undefined, locale);
132
+ await this.runGit(repoPath, ['checkout', '-B', branch, `origin/${baseRef}`], undefined, locale);
133
133
  return repoPath;
134
134
  }
135
- async commitAndPush(repo, branch, message) {
136
- repo = assertRepoFullName(repo);
137
- branch = assertGitRefName(branch);
138
- const repoPath = this.getRepoPath(repo);
135
+ async commitAndPush(repo, branch, message, locale) {
136
+ repo = assertRepoFullName(repo, locale);
137
+ branch = assertGitRefName(branch, locale);
138
+ const repoPath = this.getRepoPath(repo, locale);
139
139
  if (!fs.existsSync(repoPath)) {
140
- throw new Error(`工作区不存在: ${repoPath},请先 github_prepare_workspace`);
140
+ throw new Error(t(locale, 'missingWorkspace', { path: repoPath }));
141
141
  }
142
142
  const token = await this.gh.ensureInstallationTokenForRepo(repo);
143
143
  if (!token)
144
- throw new Error('Installation Token 不可用');
145
- await this.runGit(repoPath, ['remote', 'set-url', 'origin', this.gh.buildCloneUrl(repo, token)]);
146
- const status = await this.runGit(repoPath, ['status', '--porcelain']);
144
+ throw new Error(t(locale, 'missingToken'));
145
+ await this.runGit(repoPath, ['remote', 'set-url', 'origin', this.gh.buildCloneUrl(repo, token)], undefined, locale);
146
+ const status = await this.runGit(repoPath, ['status', '--porcelain'], undefined, locale);
147
147
  if (!status.trim())
148
- return '没有可提交的变更';
149
- await this.runGit(repoPath, ['add', '-A']);
150
- await this.runGit(repoPath, ['commit', '-m', message]);
151
- await this.runGit(repoPath, ['push', '-u', 'origin', branch]);
152
- return `已 push 到 origin/${branch}`;
148
+ return t(locale, 'noChanges');
149
+ await this.runGit(repoPath, ['add', '-A'], undefined, locale);
150
+ await this.runGit(repoPath, ['commit', '-m', message], undefined, locale);
151
+ await this.runGit(repoPath, ['push', '-u', 'origin', branch], undefined, locale);
152
+ return t(locale, 'pushed', { branch });
153
153
  }
154
154
  }
155
+ const t = createMessages({
156
+ invalidRepo: { 'zh-CN': '非法的仓库全名: {value}', en: 'Invalid repository name: {value}' },
157
+ invalidRef: { 'zh-CN': '非法的 git ref: {value}', en: 'Invalid git ref: {value}' },
158
+ missingGit: { 'zh-CN': 'git 未安装', en: 'git is not installed' },
159
+ appRequired: { 'zh-CN': 'GitHub App 认证未配置,无法托管工作区', en: 'GitHub App authentication is not configured; the workspace is unavailable' },
160
+ repoTokenUnavailable: { 'zh-CN': '无法获取 {repo} 的 Installation Token', en: 'Cannot obtain an Installation Token for {repo}' },
161
+ missingIdentity: { 'zh-CN': '无法解析 GitHub App Bot 身份', en: 'Cannot resolve the GitHub App bot identity' },
162
+ missingWorkspace: { 'zh-CN': '工作区不存在: {path},请先准备工作区', en: 'Workspace does not exist: {path}. Prepare the workspace first' },
163
+ missingToken: { 'zh-CN': 'Installation Token 不可用', en: 'Installation Token is unavailable' },
164
+ noChanges: { 'zh-CN': '没有可提交的变更', en: 'No changes to commit' },
165
+ pushed: { 'zh-CN': '已 push 到 origin/{branch}', en: 'Pushed to origin/{branch}' },
166
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-github",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Zhin.js GitHub adapter for Plugin Runtime (App auth + webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -39,25 +39,25 @@
39
39
  "directory": "plugins/adapters/github"
40
40
  },
41
41
  "dependencies": {
42
- "@zhin.js/adapter": "1.1.22",
43
- "@zhin.js/agent-feature": "1.1.9",
44
- "@zhin.js/core": "1.1.50",
45
- "@zhin.js/feature-kit": "1.1.8",
46
- "@zhin.js/host-http": "1.1.9",
47
- "@zhin.js/im-contract": "1.1.6",
42
+ "@zhin.js/adapter": "1.1.23",
43
+ "@zhin.js/agent-feature": "1.1.10",
44
+ "@zhin.js/core": "1.1.51",
45
+ "@zhin.js/feature-kit": "1.1.9",
46
+ "@zhin.js/host-http": "1.1.10",
47
+ "@zhin.js/im-contract": "1.1.7",
48
48
  "@zhin.js/logger": "1.1.2",
49
- "@zhin.js/skill": "1.1.9",
50
- "@zhin.js/tool": "1.1.9"
49
+ "@zhin.js/skill": "1.1.10",
50
+ "@zhin.js/tool": "1.1.10"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "zod": "^4.0.0",
54
- "@zhin.js/adapter": "^1.1.22",
55
- "@zhin.js/agent": "^1.1.40",
56
- "@zhin.js/command": "^1.1.10",
57
- "@zhin.js/core": "^1.1.50",
58
- "@zhin.js/host-http": "^1.1.9",
59
- "@zhin.js/prompt-section": "^1.1.8",
60
- "zhin.js": "^1.1.15"
54
+ "@zhin.js/adapter": "^1.1.23",
55
+ "@zhin.js/agent": "^1.1.42",
56
+ "@zhin.js/command": "^1.1.11",
57
+ "@zhin.js/core": "^1.1.51",
58
+ "@zhin.js/host-http": "^1.1.10",
59
+ "@zhin.js/prompt-section": "^1.1.9",
60
+ "zhin.js": "^1.1.16"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "@zhin.js/agent": {
@@ -81,9 +81,9 @@
81
81
  "typescript": "^6.0.3",
82
82
  "vitest": "^4.1.10",
83
83
  "zod": "^4.4.3",
84
- "@zhin.js/agent": "1.1.40",
85
- "@zhin.js/prompt-section": "1.1.8",
86
- "zhin.js": "1.1.15"
84
+ "@zhin.js/agent": "1.1.42",
85
+ "@zhin.js/prompt-section": "1.1.9",
86
+ "zhin.js": "1.1.16"
87
87
  },
88
88
  "files": [
89
89
  "adapters",