dsh-github-flow 0.1.7 → 0.2.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 (65) hide show
  1. package/README.en.md +287 -69
  2. package/README.md +290 -68
  3. package/docs/architecture.md +71 -14
  4. package/docs/images/1.png +0 -0
  5. package/docs/images/2.png +0 -0
  6. package/docs/images/3.png +0 -0
  7. package/docs/images/4.png +0 -0
  8. package/docs/npm-publish-guide.md +12 -0
  9. package/lib/api/route-helpers.d.ts +47 -0
  10. package/lib/api/route-helpers.js +134 -0
  11. package/lib/api/routes-actions.d.ts +9 -0
  12. package/lib/api/routes-actions.js +115 -0
  13. package/lib/api/routes-lifecycle.d.ts +9 -0
  14. package/lib/api/routes-lifecycle.js +75 -0
  15. package/lib/api/routes-overview.d.ts +11 -0
  16. package/lib/api/routes-overview.js +227 -0
  17. package/lib/api/routes-proxy.d.ts +18 -0
  18. package/lib/api/routes-proxy.js +63 -0
  19. package/lib/api/routes.d.ts +17 -5
  20. package/lib/api/routes.js +18 -417
  21. package/lib/client.js +1179 -40
  22. package/lib/commands/gh.d.ts +2 -1
  23. package/lib/commands/gh.js +40 -3
  24. package/lib/executor.d.ts +17 -0
  25. package/lib/executor.js +40 -2
  26. package/lib/main.d.ts +12 -0
  27. package/lib/main.js +73 -4
  28. package/lib/proxy.d.ts +87 -0
  29. package/lib/proxy.js +275 -0
  30. package/lib/renderers/markdown.d.ts +24 -0
  31. package/lib/renderers/markdown.js +493 -0
  32. package/lib/tools/api.d.ts +4 -0
  33. package/lib/tools/api.js +14 -4
  34. package/lib/tools/help.d.ts +31 -0
  35. package/lib/tools/help.js +212 -0
  36. package/lib/tools/issue.d.ts +32 -1
  37. package/lib/tools/issue.js +94 -17
  38. package/lib/tools/pr.d.ts +22 -1
  39. package/lib/tools/pr.js +67 -15
  40. package/lib/tools/release.d.ts +89 -0
  41. package/lib/tools/release.js +219 -0
  42. package/lib/tools/repo.d.ts +13 -1
  43. package/lib/tools/repo.js +46 -14
  44. package/lib/tools/run.d.ts +22 -1
  45. package/lib/tools/run.js +74 -14
  46. package/lib/tools/secret.d.ts +60 -0
  47. package/lib/tools/secret.js +175 -0
  48. package/lib/tools/skill.d.ts +63 -0
  49. package/lib/tools/skill.js +146 -0
  50. package/lib/types.d.ts +57 -0
  51. package/lib/updater.d.ts +49 -0
  52. package/lib/updater.js +169 -0
  53. package/lib/upgrade-manager.d.ts +58 -0
  54. package/lib/upgrade-manager.js +309 -0
  55. package/lib/version.d.ts +13 -0
  56. package/lib/version.js +13 -0
  57. package/package.json +3 -2
  58. package/lib/client/icons.d.ts +0 -13
  59. package/lib/client/icons.js +0 -41
  60. package/lib/client/index.d.ts +0 -3
  61. package/lib/client/index.js +0 -36
  62. package/lib/client/panel.d.ts +0 -1
  63. package/lib/client/panel.js +0 -102
  64. package/lib/client/style.d.ts +0 -1
  65. package/lib/client/style.js +0 -555
@@ -0,0 +1,89 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createReleaseTool(executor: GhExecutor): {
3
+ name: string;
4
+ description: string;
5
+ parameters: {
6
+ type: string;
7
+ properties: {
8
+ action: {
9
+ type: string;
10
+ enum: string[];
11
+ description: string;
12
+ };
13
+ repo: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ tag: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ title: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ notes: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ generate_notes: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ draft: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ prerelease: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ target: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ assets: {
46
+ type: string;
47
+ items: {
48
+ type: string;
49
+ };
50
+ description: string;
51
+ };
52
+ clobber: {
53
+ type: string;
54
+ description: string;
55
+ };
56
+ pattern: {
57
+ type: string;
58
+ description: string;
59
+ };
60
+ dir: {
61
+ type: string;
62
+ description: string;
63
+ };
64
+ limit: {
65
+ type: string;
66
+ description: string;
67
+ };
68
+ confirm: {
69
+ type: string;
70
+ description: string;
71
+ };
72
+ cleanup_tag: {
73
+ type: string;
74
+ description: string;
75
+ };
76
+ };
77
+ required: string[];
78
+ };
79
+ output: {
80
+ schema: {
81
+ type: string;
82
+ };
83
+ render(args: any, value: any): {
84
+ type: string;
85
+ text: string;
86
+ }[];
87
+ };
88
+ execute(args: any, execContext: any): Promise<Record<string, any>>;
89
+ };
@@ -0,0 +1,219 @@
1
+ import { renderReleaseMarkdown } from '../renderers/markdown.js';
2
+ function normalizeOutput(data) {
3
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
4
+ return data;
5
+ }
6
+ if (Array.isArray(data)) {
7
+ return { items: data, count: data.length };
8
+ }
9
+ return { result: data !== undefined && data !== null ? data : '' };
10
+ }
11
+ export function createReleaseTool(executor) {
12
+ return {
13
+ name: 'github_release',
14
+ description: '管理 GitHub Releases 版本发布与构建资产:查看发版历史、根据提交自动生成发版说明、创建 Release、上传/下载发布资产(如 npm .tgz 产物)。高危操作(delete、clobber 覆盖上传)需 confirm: true。',
15
+ parameters: {
16
+ type: 'object',
17
+ properties: {
18
+ action: {
19
+ type: 'string',
20
+ enum: ['list', 'view', 'create', 'upload', 'download', 'delete'],
21
+ description: 'Release 操作类型。',
22
+ },
23
+ repo: {
24
+ type: 'string',
25
+ description: '目标仓库 [owner/repo],留空则自动识别当前本地 Git 仓库。',
26
+ },
27
+ tag: {
28
+ type: 'string',
29
+ description: '版本 Tag 名称(view/upload/download/delete 动作使用,如 "v0.1.9";view 留空查看最新 Release)。',
30
+ },
31
+ title: {
32
+ type: 'string',
33
+ description: 'Release 标题(create 动作使用,留空且开启 generate_notes 时由 GitHub 自动生成)。',
34
+ },
35
+ notes: {
36
+ type: 'string',
37
+ description: 'Release 说明正文(create 动作使用)。留空且 generate_notes 为 true 时将自动由 GitHub 根据提交记录生成。',
38
+ },
39
+ generate_notes: {
40
+ type: 'boolean',
41
+ description: '是否自动生成 Release 说明(映射为 --generate-notes)。默认 true。',
42
+ },
43
+ draft: {
44
+ type: 'boolean',
45
+ description: '是否创建为草稿 Release(create 动作使用)。',
46
+ },
47
+ prerelease: {
48
+ type: 'boolean',
49
+ description: '是否标记为预发布版本 Pre-release(create 动作使用)。',
50
+ },
51
+ target: {
52
+ type: 'string',
53
+ description: '自动创建 Tag 时的目标分支或完整 commit SHA(create 动作使用,默认主分支)。',
54
+ },
55
+ assets: {
56
+ type: 'array',
57
+ items: { type: 'string' },
58
+ description: '本地资产文件路径列表(create/upload 动作使用,如 ["./dsh-github-flow-0.1.9.tgz"])。',
59
+ },
60
+ clobber: {
61
+ type: 'boolean',
62
+ description: '上传时覆盖同名旧资产(upload 动作使用)。破坏性操作:旧资产会先被删除,上传失败时无法恢复,需 confirm: true。',
63
+ },
64
+ pattern: {
65
+ type: 'string',
66
+ description: '仅下载匹配 glob 的资产(download 动作使用,如 "*.tgz")。',
67
+ },
68
+ dir: {
69
+ type: 'string',
70
+ description: '资产下载目录(download 动作使用,默认当前目录 ".")。',
71
+ },
72
+ limit: {
73
+ type: 'number',
74
+ description: '返回 Release 数量上限(list 动作使用,默认 10,最大 50)。',
75
+ },
76
+ confirm: {
77
+ type: 'boolean',
78
+ description: '高危破坏性操作确认标志(delete 动作或 upload+clobber 时必需)。若为 false 则仅输出影响评估报告。',
79
+ },
80
+ cleanup_tag: {
81
+ type: 'boolean',
82
+ description: '删除 Release 时是否连同删除其 Git Tag(delete 动作可选,映射 --cleanup-tag)。默认 false:仅删 Release 保留 Tag。',
83
+ },
84
+ },
85
+ required: ['action'],
86
+ },
87
+ output: {
88
+ schema: { type: 'object' },
89
+ render(args, value) {
90
+ return [{ type: 'text', text: renderReleaseMarkdown(args, value) }];
91
+ },
92
+ },
93
+ async execute(args, execContext) {
94
+ const cwd = execContext?.cwd || process.cwd();
95
+ const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
96
+ let repoTarget = args.repo;
97
+ if (!repoTarget) {
98
+ const meta = await executor.getRepoMetadata(cwd, sessionId);
99
+ if (meta?.nameWithOwner) {
100
+ repoTarget = meta.nameWithOwner;
101
+ }
102
+ }
103
+ if (!repoTarget) {
104
+ throw new Error('未能自动识别当前工作区关联的 GitHub 仓库。请通过 `repo` 参数显式指定目标仓库 [owner/repo]。');
105
+ }
106
+ const repoArgs = ['-R', repoTarget];
107
+ const runOpts = { cwd, sessionId };
108
+ switch (args.action) {
109
+ case 'list': {
110
+ // 显式 limit 优先;缺省回退 executor.defaultListLimit(上限 50 防止全量 Release 撑爆输出)
111
+ const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.min(args.limit, 50) : Math.min(executor.defaultListLimit ?? 10, 50);
112
+ const res = await executor.run(['release', 'list', ...repoArgs, '-L', String(limit), '--json', 'tagName,name,isDraft,isPrerelease,publishedAt,createdAt'], runOpts);
113
+ if (!res.ok)
114
+ throw new Error(res.error);
115
+ return normalizeOutput(res.data || res.rawOutput);
116
+ }
117
+ case 'view': {
118
+ // gh release view 不带 tag 参数时展示最新 Release,与工具语义一致
119
+ const target = args.tag ? [args.tag] : [];
120
+ const res = await executor.run(['release', 'view', ...target, ...repoArgs, '--json', 'tagName,name,body,publishedAt,isDraft,isPrerelease,assets,url'], runOpts);
121
+ if (!res.ok)
122
+ throw new Error(res.error);
123
+ return normalizeOutput(res.data || res.rawOutput);
124
+ }
125
+ case 'create': {
126
+ if (!args.tag)
127
+ throw new Error('create 操作必须提供 tag(如 "v0.1.9")');
128
+ const createCmd = ['release', 'create', args.tag, ...repoArgs];
129
+ // 资产文件直接跟在 tag 后(gh release create <tag> <files...>)
130
+ if (Array.isArray(args.assets)) {
131
+ for (const a of args.assets) {
132
+ if (typeof a === 'string' && a.trim())
133
+ createCmd.push(a);
134
+ }
135
+ }
136
+ if (args.title)
137
+ createCmd.push('--title', args.title);
138
+ // notes 与 generate_notes 可共存:gh 会在自动生成的说明前 prepend 手写正文
139
+ if (args.notes)
140
+ createCmd.push('--notes', args.notes);
141
+ if (args.generate_notes !== false)
142
+ createCmd.push('--generate-notes');
143
+ if (args.draft === true)
144
+ createCmd.push('--draft');
145
+ if (args.prerelease === true)
146
+ createCmd.push('--prerelease');
147
+ if (args.target)
148
+ createCmd.push('--target', args.target);
149
+ const res = await executor.run(createCmd, runOpts);
150
+ if (!res.ok)
151
+ throw new Error(res.error);
152
+ return { message: `Release ${args.tag} 创建成功`, url: res.rawOutput };
153
+ }
154
+ case 'upload': {
155
+ if (!args.tag)
156
+ throw new Error('upload 操作必须提供 tag');
157
+ if (!Array.isArray(args.assets) || args.assets.length === 0) {
158
+ throw new Error('upload 操作必须提供 assets(文件路径列表)');
159
+ }
160
+ if (args.clobber === true && args.confirm !== true) {
161
+ return {
162
+ status: 'AWAITING_CONFIRMATION',
163
+ warning: `⚠️ 高危操作拦截:你即将覆盖上传 Release ${args.tag} 的同名旧资产(clobber 模式)。`,
164
+ impact: 'clobber 会先删除既有同名资产再上传;若上传中途失败,旧资产已丢失且无法恢复。',
165
+ requiredAction: '如确认覆盖,请向用户核实并附加 `confirm: true` 重新调用。',
166
+ };
167
+ }
168
+ const uploadCmd = ['release', 'upload', args.tag, ...repoArgs];
169
+ for (const a of args.assets) {
170
+ if (typeof a === 'string' && a.trim())
171
+ uploadCmd.push(a);
172
+ }
173
+ if (args.clobber === true)
174
+ uploadCmd.push('--clobber');
175
+ const res = await executor.run(uploadCmd, runOpts);
176
+ if (!res.ok)
177
+ throw new Error(res.error);
178
+ return { message: `已上传 ${args.assets.length} 个资产至 Release ${args.tag}`, output: res.rawOutput };
179
+ }
180
+ case 'download': {
181
+ const target = args.tag ? [args.tag] : [];
182
+ if (!args.tag && !args.pattern) {
183
+ throw new Error('download 不带 tag 时(下载最新 Release)必须提供 pattern 过滤资产');
184
+ }
185
+ const dlCmd = ['release', 'download', ...target, ...repoArgs];
186
+ if (args.pattern)
187
+ dlCmd.push('--pattern', args.pattern);
188
+ if (args.dir)
189
+ dlCmd.push('--dir', args.dir);
190
+ const res = await executor.run(dlCmd, runOpts);
191
+ if (!res.ok)
192
+ throw new Error(res.error);
193
+ return { message: `Release 资产下载完成(目录: ${args.dir || '.'})`, output: res.rawOutput };
194
+ }
195
+ case 'delete': {
196
+ if (!args.tag)
197
+ throw new Error('delete 操作必须提供 tag');
198
+ if (args.confirm !== true) {
199
+ return {
200
+ status: 'AWAITING_CONFIRMATION',
201
+ warning: `⚠️ 高危操作拦截:你即将删除 Release ${args.tag}${args.cleanup_tag ? ' 及其 Git Tag' : ''}。`,
202
+ impact: '删除 Release 不可逆;若同时清理 Tag,引用该 Tag 的历史版本入口将全部失效。',
203
+ requiredAction: '如确认删除,请向用户核实并附加 `confirm: true` 重新调用。',
204
+ };
205
+ }
206
+ const delCmd = ['release', 'delete', args.tag, ...repoArgs, '--yes'];
207
+ if (args.cleanup_tag === true)
208
+ delCmd.push('--cleanup-tag');
209
+ const res = await executor.run(delCmd, runOpts);
210
+ if (!res.ok)
211
+ throw new Error(res.error);
212
+ return { message: `Release ${args.tag} 已删除`, output: res.rawOutput };
213
+ }
214
+ default:
215
+ throw new Error(`未知的 Release 操作: ${args.action}`);
216
+ }
217
+ },
218
+ };
219
+ }
@@ -22,6 +22,18 @@ export declare function createRepoTool(executor: GhExecutor): {
22
22
  type: string;
23
23
  description: string;
24
24
  };
25
+ branch: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ source: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ force: {
34
+ type: string;
35
+ description: string;
36
+ };
25
37
  };
26
38
  required: string[];
27
39
  };
@@ -31,7 +43,7 @@ export declare function createRepoTool(executor: GhExecutor): {
31
43
  };
32
44
  render(args: any, value: any): {
33
45
  type: string;
34
- text: any;
46
+ text: string;
35
47
  }[];
36
48
  };
37
49
  execute(args: any, execContext: any): Promise<Record<string, any>>;
package/lib/tools/repo.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { renderRepoMarkdown } from '../renderers/markdown.js';
1
2
  function normalizeOutput(data) {
2
3
  if (data && typeof data === 'object' && !Array.isArray(data)) {
3
4
  return data;
@@ -10,14 +11,14 @@ function normalizeOutput(data) {
10
11
  export function createRepoTool(executor) {
11
12
  return {
12
13
  name: 'github_repo',
13
- description: '查看 GitHub 仓库元数据与跨仓库检索代码、Issue。默认查询当前工作区关联的仓库。',
14
+ description: '查看 GitHub 仓库元数据、同步 Fork 仓库主干 (sync) 与跨仓库检索代码。默认查询当前工作区关联的仓库。',
14
15
  parameters: {
15
16
  type: 'object',
16
17
  properties: {
17
18
  action: {
18
19
  type: 'string',
19
- enum: ['view', 'search_code', 'search_repos'],
20
- description: '仓库操作类型。',
20
+ enum: ['view', 'search_code', 'search_repos', 'sync'],
21
+ description: '仓库操作类型。新增 sync (一键拉平 Fork 仓库与上游主干)。',
21
22
  },
22
23
  repo: {
23
24
  type: 'string',
@@ -31,27 +32,32 @@ export function createRepoTool(executor) {
31
32
  type: 'number',
32
33
  description: '返回条数,默认 10。',
33
34
  },
35
+ branch: {
36
+ type: 'string',
37
+ description: '要同步的分支名(sync 动作使用,默认主分支如 main)。',
38
+ },
39
+ source: {
40
+ type: 'string',
41
+ description: '上游源仓库 [owner/repo](sync 动作使用,留空自动匹配 upstream 远程配置)。',
42
+ },
43
+ force: {
44
+ type: 'boolean',
45
+ description: '是否强制硬重置同步(sync 动作使用,映射为 --force,解决分叉冲突)。默认 false。',
46
+ },
34
47
  },
35
48
  required: ['action'],
36
49
  },
37
50
  output: {
38
51
  schema: { type: 'object' },
39
52
  render(args, value) {
40
- if (!value)
41
- return [{ type: 'text', text: '无返回内容' }];
42
- if (typeof value.result === 'string') {
43
- return [{ type: 'text', text: value.result }];
44
- }
45
- if (Array.isArray(value.items)) {
46
- return [{ type: 'text', text: JSON.stringify(value.items, null, 2) }];
47
- }
48
- return [{ type: 'text', text: JSON.stringify(value, null, 2) }];
53
+ return [{ type: 'text', text: renderRepoMarkdown(args, value) }];
49
54
  },
50
55
  },
51
56
  async execute(args, execContext) {
52
57
  const cwd = execContext?.cwd || process.cwd();
53
58
  const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
54
- const limitStr = String(args.limit || 10);
59
+ // 显式 limit 优先;缺省回退 executor.defaultListLimit(用户可在插件配置中调整)
60
+ const limitStr = String(typeof args.limit === 'number' && args.limit > 0 ? args.limit : Math.min(executor.defaultListLimit ?? 10, 50));
55
61
  const runOpts = { cwd, sessionId };
56
62
  switch (args.action) {
57
63
  case 'view': {
@@ -64,7 +70,7 @@ export function createRepoTool(executor) {
64
70
  }
65
71
  }
66
72
  const repoArgs = repoTarget ? [repoTarget] : [];
67
- const res = await executor.run(['repo', 'view', ...repoArgs, '--json', 'nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,forkCount,url'], runOpts);
73
+ const res = await executor.run(['repo', 'view', ...repoArgs, '--json', 'nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,forkCount,url,repositoryTopics,licenseInfo'], runOpts);
68
74
  if (!res.ok) {
69
75
  // 若执行 gh 失败(如离线或环境限制),但本地已成功识别仓库元数据,进行优雅兜底返回
70
76
  if (meta && meta.nameWithOwner) {
@@ -91,6 +97,32 @@ export function createRepoTool(executor) {
91
97
  throw new Error(res.error);
92
98
  return normalizeOutput(res.data || res.rawOutput);
93
99
  }
100
+ case 'sync': {
101
+ let repoTarget = args.repo;
102
+ if (!repoTarget) {
103
+ const meta = await executor.getRepoMetadata(cwd, sessionId);
104
+ if (meta?.nameWithOwner) {
105
+ repoTarget = meta.nameWithOwner;
106
+ }
107
+ }
108
+ const syncCmd = ['repo', 'sync'];
109
+ if (repoTarget) {
110
+ syncCmd.push(repoTarget);
111
+ }
112
+ if (args.branch) {
113
+ syncCmd.push('--branch', args.branch);
114
+ }
115
+ if (args.source) {
116
+ syncCmd.push('--source', args.source);
117
+ }
118
+ if (args.force === true) {
119
+ syncCmd.push('--force');
120
+ }
121
+ const res = await executor.run(syncCmd, runOpts);
122
+ if (!res.ok)
123
+ throw new Error(res.error);
124
+ return { message: 'Fork 仓库同步成功,已与上游主干对齐', output: res.rawOutput || 'Synced' };
125
+ }
94
126
  default:
95
127
  throw new Error(`未知的 Repo 操作: ${args.action}`);
96
128
  }
@@ -22,6 +22,27 @@ export declare function createRunTool(executor: GhExecutor): {
22
22
  type: string;
23
23
  description: string;
24
24
  };
25
+ branch: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ status: {
30
+ type: string;
31
+ enum: string[];
32
+ description: string;
33
+ };
34
+ workflow: {
35
+ type: string;
36
+ description: string;
37
+ };
38
+ ref: {
39
+ type: string;
40
+ description: string;
41
+ };
42
+ inputs: {
43
+ type: string;
44
+ description: string;
45
+ };
25
46
  };
26
47
  required: string[];
27
48
  };
@@ -31,7 +52,7 @@ export declare function createRunTool(executor: GhExecutor): {
31
52
  };
32
53
  render(args: any, value: any): {
33
54
  type: string;
34
- text: any;
55
+ text: string;
35
56
  }[];
36
57
  };
37
58
  execute(args: any, execContext: any): Promise<Record<string, any>>;
package/lib/tools/run.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { renderRunMarkdown } from '../renderers/markdown.js';
1
2
  function normalizeOutput(data) {
2
3
  if (data && typeof data === 'object' && !Array.isArray(data)) {
3
4
  return data;
@@ -10,14 +11,14 @@ function normalizeOutput(data) {
10
11
  export function createRunTool(executor) {
11
12
  return {
12
13
  name: 'github_run',
13
- description: '排查和诊断 GitHub Actions 工作流构建状态。特别优化了获取失败步骤日志能力(log_failed),专供 Agent 快速定位 CI 报错。',
14
+ description: '排查和诊断 GitHub Actions 工作流构建状态。特别优化了获取失败步骤日志能力(log_failed),专供 Agent 快速定位 CI 报错,并支持手动触发工作流 (trigger)。',
14
15
  parameters: {
15
16
  type: 'object',
16
17
  properties: {
17
18
  action: {
18
19
  type: 'string',
19
- enum: ['list', 'view', 'log_failed', 'rerun', 'cancel'],
20
- description: 'Actions 操作类型。',
20
+ enum: ['list', 'view', 'log_failed', 'rerun', 'cancel', 'trigger'],
21
+ description: 'Actions 操作类型。新增 trigger (主动触发 workflow_dispatch)。',
21
22
  },
22
23
  run_id: {
23
24
  type: 'string',
@@ -31,21 +32,34 @@ export function createRunTool(executor) {
31
32
  type: 'number',
32
33
  description: '列出 runs 的数量,默认 10。',
33
34
  },
35
+ branch: {
36
+ type: 'string',
37
+ description: '按分支筛选运行记录(list 动作使用,例如 "main" 或当前分支)。',
38
+ },
39
+ status: {
40
+ type: 'string',
41
+ enum: ['queued', 'completed', 'in_progress', 'failure', 'success'],
42
+ description: '按状态筛选运行记录(list 动作使用,排查报错推荐 failure)。',
43
+ },
44
+ workflow: {
45
+ type: 'string',
46
+ description: '工作流名称或文件名(trigger 动作使用,例如 "pr-check.yml" 或 "CI")。',
47
+ },
48
+ ref: {
49
+ type: 'string',
50
+ description: '触发工作流的目标分支或 tag(trigger 动作使用,默认当前工作区分支)。',
51
+ },
52
+ inputs: {
53
+ type: 'object',
54
+ description: '传递给工作流的自定义参数键值对(trigger 动作使用)。',
55
+ },
34
56
  },
35
57
  required: ['action'],
36
58
  },
37
59
  output: {
38
60
  schema: { type: 'object' },
39
61
  render(args, value) {
40
- if (!value)
41
- return [{ type: 'text', text: '无返回数据' }];
42
- if (typeof value.result === 'string') {
43
- return [{ type: 'text', text: value.result }];
44
- }
45
- if (Array.isArray(value.items)) {
46
- return [{ type: 'text', text: JSON.stringify(value.items, null, 2) }];
47
- }
48
- return [{ type: 'text', text: JSON.stringify(value, null, 2) }];
62
+ return [{ type: 'text', text: renderRunMarkdown(args, value) }];
49
63
  },
50
64
  },
51
65
  async execute(args, execContext) {
@@ -65,7 +79,19 @@ export function createRunTool(executor) {
65
79
  const runOpts = { cwd, sessionId };
66
80
  switch (args.action) {
67
81
  case 'list': {
68
- const res = await executor.run(['run', 'list', ...repoArgs, '--json', 'databaseId,name,status,conclusion,event,headBranch,createdAt', '-L', String(args.limit || 10)], runOpts);
82
+ const listCmd = [
83
+ 'run', 'list', ...repoArgs,
84
+ '--json', 'databaseId,name,status,conclusion,event,headBranch,createdAt,url',
85
+ // 显式 limit 优先;缺省回退 executor.defaultListLimit(用户可在插件配置中调整)
86
+ '-L', String(typeof args.limit === 'number' && args.limit > 0 ? args.limit : Math.min(executor.defaultListLimit ?? 10, 50))
87
+ ];
88
+ if (args.branch) {
89
+ listCmd.push('--branch', args.branch);
90
+ }
91
+ if (args.status) {
92
+ listCmd.push('--status', args.status);
93
+ }
94
+ const res = await executor.run(listCmd, runOpts);
69
95
  if (!res.ok)
70
96
  throw new Error(res.error);
71
97
  return normalizeOutput(res.data || res.rawOutput);
@@ -87,10 +113,44 @@ export function createRunTool(executor) {
87
113
  timeoutMs: 60_000,
88
114
  rawText: true,
89
115
  });
90
- if (!res.ok)
116
+ if (!res.ok) {
117
+ // 两类已知的非零退出需要优雅吸收:
118
+ // 1) 工作流本身成功/无失败步骤 —— gh 的确定性提示;
119
+ // 2) gh CLI 在 Windows 受限目录写日志缓存 zip 报 Access is denied ——
120
+ // 这是宿主环境问题而非构建失败,透传原文会让 Agent 误判为构建故障,
121
+ // 给出自查指引(gh cache 目录权限)更可行动。
122
+ const err = String(res.error || '');
123
+ if (err.includes('no failed jobs') || err.includes('not failed')) {
124
+ return { result: `工作流 Run #${args.run_id} 中未检测到任何失败的任务步骤 (状态正常)。` };
125
+ }
126
+ if (err.includes('Access is denied') || err.includes('拒绝访问')) {
127
+ return {
128
+ result: `无法提取 Run #${args.run_id} 的失败日志:gh CLI 向本机缓存目录写入日志压缩包时被系统拒绝 (${err.trim().slice(0, 200)})。建议检查 gh 缓存目录(如 %LocalAppData%\\GitHub CLI)的写权限后重试,或改用 github_api 拉取日志端点。`,
129
+ };
130
+ }
91
131
  throw new Error(res.error);
132
+ }
92
133
  return { result: res.rawOutput || '未找到失败步骤的日志' };
93
134
  }
135
+ case 'trigger': {
136
+ if (!args.workflow)
137
+ throw new Error('trigger 操作必须提供 workflow(工作流名称或文件名,例如 "pr-check.yml")');
138
+ const trigCmd = ['workflow', 'run', args.workflow, ...repoArgs];
139
+ if (args.ref) {
140
+ trigCmd.push('--ref', args.ref);
141
+ }
142
+ if (args.inputs && typeof args.inputs === 'object') {
143
+ for (const [k, v] of Object.entries(args.inputs)) {
144
+ if (v !== undefined && v !== null) {
145
+ trigCmd.push('-f', `${k}=${String(v)}`);
146
+ }
147
+ }
148
+ }
149
+ const res = await executor.run(trigCmd, runOpts);
150
+ if (!res.ok)
151
+ throw new Error(res.error);
152
+ return { message: `已成功触发工作流「${args.workflow}」运行`, output: res.rawOutput || 'Workflow dispatched' };
153
+ }
94
154
  case 'rerun': {
95
155
  if (!args.run_id)
96
156
  throw new Error('rerun 操作必须提供 run_id');
@@ -0,0 +1,60 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createSecretTool(executor: GhExecutor): {
3
+ name: string;
4
+ description: string;
5
+ parameters: {
6
+ type: string;
7
+ properties: {
8
+ action: {
9
+ type: string;
10
+ enum: string[];
11
+ description: string;
12
+ };
13
+ repo: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ name: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ value: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ org: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ env: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ app: {
34
+ type: string;
35
+ enum: string[];
36
+ description: string;
37
+ };
38
+ visibility: {
39
+ type: string;
40
+ enum: string[];
41
+ description: string;
42
+ };
43
+ confirm: {
44
+ type: string;
45
+ description: string;
46
+ };
47
+ };
48
+ required: string[];
49
+ };
50
+ output: {
51
+ schema: {
52
+ type: string;
53
+ };
54
+ render(args: any, value: any): {
55
+ type: string;
56
+ text: string;
57
+ }[];
58
+ };
59
+ execute(args: any, execContext: any): Promise<Record<string, any>>;
60
+ };