dsh-github-flow 0.4.0 → 0.5.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 (62) hide show
  1. package/README.en.md +75 -8
  2. package/README.md +75 -8
  3. package/docs/UPGRADE-NOTES.md +9 -12
  4. package/docs/architecture.md +39 -21
  5. package/lib/api/routes-actions.js +132 -0
  6. package/lib/client.d.ts +4 -3
  7. package/lib/client.js +1436 -80
  8. package/lib/commands/gh.d.ts +6 -1
  9. package/lib/commands/gh.js +144 -71
  10. package/lib/executor/git-parser.d.ts +33 -1
  11. package/lib/executor/git-parser.js +116 -4
  12. package/lib/executor.d.ts +27 -2
  13. package/lib/executor.js +189 -5
  14. package/lib/main.js +28 -4
  15. package/lib/renderers/agent-task.d.ts +16 -0
  16. package/lib/renderers/agent-task.js +107 -0
  17. package/lib/renderers/codespace.d.ts +16 -0
  18. package/lib/renderers/codespace.js +103 -0
  19. package/lib/renderers/discussion.d.ts +16 -0
  20. package/lib/renderers/discussion.js +101 -0
  21. package/lib/renderers/markdown.d.ts +4 -0
  22. package/lib/renderers/markdown.js +4 -0
  23. package/lib/renderers/project.d.ts +20 -0
  24. package/lib/renderers/project.js +128 -0
  25. package/lib/renderers/skill.d.ts +30 -0
  26. package/lib/renderers/skill.js +180 -0
  27. package/lib/renderers/status.d.ts +5 -0
  28. package/lib/renderers/status.js +52 -0
  29. package/lib/telemetry.d.ts +70 -0
  30. package/lib/telemetry.js +161 -0
  31. package/lib/tools/agent-task.d.ts +54 -0
  32. package/lib/tools/agent-task.js +135 -0
  33. package/lib/tools/codespace.d.ts +51 -0
  34. package/lib/tools/codespace.js +154 -0
  35. package/lib/tools/discussion.d.ts +70 -0
  36. package/lib/tools/discussion.js +195 -0
  37. package/lib/tools/help.js +109 -9
  38. package/lib/tools/project.d.ts +80 -0
  39. package/lib/tools/project.js +241 -0
  40. package/lib/tools/repo.js +13 -1
  41. package/lib/tools/skill.d.ts +24 -1
  42. package/lib/tools/skill.js +115 -5
  43. package/lib/tools/status.d.ts +3 -2
  44. package/lib/tools/status.js +9 -4
  45. package/lib/types.d.ts +5 -0
  46. package/lib/ui/commit-modal.js +144 -14
  47. package/lib/ui/help-modal.d.ts +15 -0
  48. package/lib/ui/help-modal.js +202 -0
  49. package/lib/ui/storage.d.ts +9 -0
  50. package/lib/ui/storage.js +24 -0
  51. package/lib/ui/styles.d.ts +1 -1
  52. package/lib/ui/styles.js +259 -0
  53. package/lib/ui/sync-settings-modal.js +1 -1
  54. package/lib/ui/upgrade-card.js +39 -14
  55. package/lib/ui/utils.d.ts +35 -0
  56. package/lib/ui/utils.js +160 -0
  57. package/lib/ui/workspace-panel.js +30 -5
  58. package/lib/upgrade-manager.d.ts +5 -1
  59. package/lib/upgrade-manager.js +24 -1
  60. package/lib/version.d.ts +1 -1
  61. package/lib/version.js +1 -1
  62. package/package.json +2 -2
@@ -0,0 +1,161 @@
1
+ /**
2
+ * DSH GitHub Flow - 运行时遥测审计与降级率收集器
3
+ *
4
+ * 遵循 /dsh-plugin-dev-skill 规范,利用 Cordis 的 tools/result 事件总线
5
+ * 建立内存中固定容量的轻量无锁环形队列,实现零外部数据库依赖、常驻内存严格 <200KB
6
+ * 的工业级运行时可观测性,量化分析 18 大工具矩阵的 API 降级率与健康度指标。
7
+ */
8
+ /**
9
+ * 内存固定容量环形队列(无锁、确定性内存上限)
10
+ */
11
+ export class TelemetryRingBuffer {
12
+ buffer = [];
13
+ capacity;
14
+ constructor(capacity = 200) {
15
+ this.capacity = Math.max(1, capacity);
16
+ }
17
+ push(record) {
18
+ if (this.buffer.length >= this.capacity) {
19
+ this.buffer.shift(); // 先进先出丢弃最旧的一条
20
+ }
21
+ this.buffer.push(record);
22
+ }
23
+ getAll() {
24
+ return [...this.buffer];
25
+ }
26
+ size() {
27
+ return this.buffer.length;
28
+ }
29
+ clear() {
30
+ this.buffer = [];
31
+ }
32
+ }
33
+ /**
34
+ * 遥测收集与健康分析引擎
35
+ */
36
+ export class TelemetryCollector {
37
+ ringBuffer;
38
+ callCounter = 0;
39
+ constructor(capacity = 200) {
40
+ this.ringBuffer = new TelemetryRingBuffer(capacity);
41
+ }
42
+ /**
43
+ * 记录一次工具调用结果(Fail-Open 保护,绝不阻滞主流程)
44
+ */
45
+ recordCall(name, args, result, durationMs) {
46
+ try {
47
+ if (!name || typeof name !== 'string')
48
+ return;
49
+ this.callCounter++;
50
+ const action = typeof args?.action === 'string' ? args.action : undefined;
51
+ const isScopeBlocked = result?.status === 'SCOPE_REQUIRED';
52
+ const isConfirmBlocked = result?.status === 'AWAITING_CONFIRMATION';
53
+ const isBlocked = isScopeBlocked || isConfirmBlocked;
54
+ const isDegraded = result?.degraded === true || result?.data?.degraded === true;
55
+ const isFailed = !isBlocked && (result?.ok === false || result?.success === false || !!result?.error);
56
+ const record = {
57
+ id: `t_${Date.now()}_${this.callCounter}`,
58
+ timestamp: Date.now(),
59
+ toolName: name,
60
+ action,
61
+ durationMs: typeof durationMs === 'number' && durationMs >= 0 ? durationMs : undefined,
62
+ success: !isFailed && !isBlocked,
63
+ degraded: isDegraded,
64
+ scopeBlocked: isScopeBlocked,
65
+ confirmBlocked: isConfirmBlocked,
66
+ errorType: isFailed ? String(result?.error || 'UNKNOWN_ERROR').slice(0, 50) : undefined,
67
+ };
68
+ this.ringBuffer.push(record);
69
+ }
70
+ catch {
71
+ // 遥测打点非阻塞隔离:严禁将任何收集器内部异常外溢给调用方
72
+ }
73
+ }
74
+ /**
75
+ * 生成工具链健康度与降级率统计大盘
76
+ */
77
+ getSummary() {
78
+ const records = this.ringBuffer.getAll();
79
+ const totalCalls = records.length;
80
+ let successCount = 0;
81
+ let failureCount = 0;
82
+ let degradedCount = 0;
83
+ let scopeBlockedCount = 0;
84
+ let confirmBlockedCount = 0;
85
+ const toolStats = {};
86
+ for (const r of records) {
87
+ if (!toolStats[r.toolName]) {
88
+ toolStats[r.toolName] = { total: 0, success: 0, degraded: 0, failed: 0, blocked: 0 };
89
+ }
90
+ const stat = toolStats[r.toolName];
91
+ stat.total++;
92
+ if (r.degraded) {
93
+ degradedCount++;
94
+ stat.degraded++;
95
+ }
96
+ if (r.scopeBlocked) {
97
+ scopeBlockedCount++;
98
+ stat.blocked++;
99
+ }
100
+ if (r.confirmBlocked) {
101
+ confirmBlockedCount++;
102
+ stat.blocked++;
103
+ }
104
+ if (r.success) {
105
+ successCount++;
106
+ stat.success++;
107
+ }
108
+ else if (!r.scopeBlocked && !r.confirmBlocked) {
109
+ failureCount++;
110
+ stat.failed++;
111
+ }
112
+ }
113
+ const degradationRate = totalCalls > 0 ? Number(((degradedCount / totalCalls) * 100).toFixed(1)) : 0;
114
+ const failureRate = totalCalls > 0 ? (failureCount / totalCalls) * 100 : 0;
115
+ // 健康分计算算法:基准 100 分,按降级率(权重 0.4)与失败率(权重 0.5)综合惩罚
116
+ let healthScore = 100;
117
+ if (totalCalls > 0) {
118
+ const penalty = degradationRate * 0.4 + failureRate * 0.5;
119
+ healthScore = Math.max(0, Math.round(100 - penalty));
120
+ }
121
+ let statusLevel = 'EXCELLENT';
122
+ if (healthScore < 70) {
123
+ statusLevel = 'NEEDS_ATTENTION';
124
+ }
125
+ else if (healthScore < 90) {
126
+ statusLevel = 'GOOD';
127
+ }
128
+ // 智能自愈建议推导
129
+ const recommendations = [];
130
+ if (toolStats['github_repo'] && toolStats['github_repo'].degraded > 0) {
131
+ recommendations.push('发现 github_repo 文件读取触发了 API 降级链路,建议将 GitHub CLI 升级至 2.95.0+ 以启用原生免克隆极速读取。');
132
+ }
133
+ if (scopeBlockedCount > 0) {
134
+ recommendations.push('发现因缺少 OAuth Scope 导致调用拦截,请在终端执行相应的 `gh auth refresh -s <scope>` 补全授权。');
135
+ }
136
+ if (degradationRate > 20) {
137
+ recommendations.push('当前工具链整体 API 降级率较高,可能当前宿主 CLI 版本过低,建议运行 `/gh upgrade` 检查升级。');
138
+ }
139
+ if (recommendations.length === 0) {
140
+ recommendations.push('工具链运行状态极佳,所有调用均走原生高速通道,无降级与权限阻断。');
141
+ }
142
+ return {
143
+ totalCalls,
144
+ successCount,
145
+ failureCount,
146
+ degradedCount,
147
+ degradationRate,
148
+ scopeBlockedCount,
149
+ confirmBlockedCount,
150
+ healthScore,
151
+ statusLevel,
152
+ toolStats,
153
+ recommendations,
154
+ };
155
+ }
156
+ clear() {
157
+ this.ringBuffer.clear();
158
+ }
159
+ }
160
+ /** 全局单例收集器实例 */
161
+ export const defaultTelemetryCollector = new TelemetryCollector(200);
@@ -0,0 +1,54 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createAgentTaskTool(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
+ task_id: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ prompt: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ base_branch: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ custom_agent: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ limit: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ log: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ };
42
+ required: string[];
43
+ };
44
+ output: {
45
+ schema: {
46
+ type: string;
47
+ };
48
+ render(args: any, value: any): {
49
+ type: string;
50
+ text: string;
51
+ }[];
52
+ };
53
+ execute(args: any, execContext: any): Promise<any>;
54
+ };
@@ -0,0 +1,135 @@
1
+ import { renderAgentTaskMarkdown } 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
+ const AGENT_TASK_JSON_FIELDS = 'completedAt,createdAt,id,name,pullRequestNumber,pullRequestState,pullRequestTitle,pullRequestUrl,repository,state,updatedAt,user';
12
+ export function createAgentTaskTool(executor) {
13
+ return {
14
+ name: 'github_agent_task',
15
+ description: '管理 GitHub Copilot Coding Agent 云端智能体任务会话:查看已分派任务列表、检视任务状态与会话日志、向云端智能体发起新编码与重构任务。基于 gh CLI preview 特性。',
16
+ parameters: {
17
+ type: 'object',
18
+ properties: {
19
+ action: {
20
+ type: 'string',
21
+ enum: ['list', 'view', 'create'],
22
+ description: 'Agent Task 操作类型。list 查看任务列表、view 检视任务详情与日志、create 新建智能体任务。',
23
+ },
24
+ repo: {
25
+ type: 'string',
26
+ description: '目标仓库 [owner/repo],留空则识别当前工作区仓库。',
27
+ },
28
+ task_id: {
29
+ type: 'string',
30
+ description: '任务标识(view 动作必需,支持 Session ID、PR 编号或任务 URL)。',
31
+ },
32
+ prompt: {
33
+ type: 'string',
34
+ description: '任务描述正文或编码指令(create 动作必需,如 "为用户模块增加单元测试")。',
35
+ },
36
+ base_branch: {
37
+ type: 'string',
38
+ description: 'PR 基准分支(create 动作可选,映射 -b/--base,缺省为主干默认分支)。',
39
+ },
40
+ custom_agent: {
41
+ type: 'string',
42
+ description: '自定义 Agent 规范名称(create 动作可选,映射 -a/--custom-agent,对应 .github/agents/ 规范)。',
43
+ },
44
+ limit: {
45
+ type: 'number',
46
+ description: '返回任务数量上限(list 动作使用,默认 30)。',
47
+ },
48
+ log: {
49
+ type: 'boolean',
50
+ description: '是否一并拉取任务会话执行日志(view 动作可选,映射 --log)。',
51
+ },
52
+ },
53
+ required: ['action'],
54
+ },
55
+ output: {
56
+ schema: { type: 'object' },
57
+ render(args, value) {
58
+ return [{ type: 'text', text: renderAgentTaskMarkdown(args, value) }];
59
+ },
60
+ },
61
+ async execute(args, execContext) {
62
+ const cwd = execContext?.cwd || process.cwd();
63
+ const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
64
+ const repoArgs = args.repo ? ['-R', args.repo] : [];
65
+ const runOpts = { cwd, sessionId };
66
+ switch (args.action) {
67
+ case 'list': {
68
+ const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.min(args.limit, 100) : 30;
69
+ // 注意:gh agent-task list 属于账号级任务列表命令,上游 gh CLI 官方不接受 -R/--repo 标志
70
+ const listCmd = [
71
+ 'agent-task', 'list',
72
+ '-L', String(limit),
73
+ '--json', AGENT_TASK_JSON_FIELDS,
74
+ ];
75
+ const res = await executor.run(listCmd, runOpts);
76
+ if (!res.ok)
77
+ throw new Error(res.error);
78
+ return normalizeOutput(res.data || res.rawOutput);
79
+ }
80
+ case 'view': {
81
+ // 防线:task_id 必须显式提供,防止 CLI 打开交互式选择器导致无头进程挂死
82
+ if (!args.task_id || typeof args.task_id !== 'string' || !args.task_id.trim()) {
83
+ throw new Error('view 操作必须提供 task_id(支持 Session ID、PR 编号或任务 URL),以防止交互式挂起');
84
+ }
85
+ const cleanId = args.task_id.trim();
86
+ const viewCmd = [
87
+ 'agent-task', 'view', cleanId,
88
+ '--json', AGENT_TASK_JSON_FIELDS,
89
+ ...repoArgs,
90
+ ];
91
+ const res = await executor.run(viewCmd, runOpts);
92
+ if (!res.ok)
93
+ throw new Error(res.error);
94
+ let taskData = res.data;
95
+ if (!taskData && res.rawOutput) {
96
+ try {
97
+ taskData = JSON.parse(res.rawOutput);
98
+ }
99
+ catch {
100
+ taskData = { id: cleanId, result: res.rawOutput };
101
+ }
102
+ }
103
+ // 若调用方需要日志,额外抓取 session log 并挂载
104
+ if (args.log === true && taskData && typeof taskData === 'object') {
105
+ const logCmd = ['agent-task', 'view', cleanId, '--log', ...repoArgs];
106
+ const logRes = await executor.run(logCmd, { ...runOpts, rawText: true, timeoutMs: 45_000 });
107
+ if (logRes.ok && logRes.rawOutput) {
108
+ taskData.logs = logRes.rawOutput;
109
+ }
110
+ }
111
+ return taskData || { id: cleanId };
112
+ }
113
+ case 'create': {
114
+ if (!args.prompt || typeof args.prompt !== 'string' || !args.prompt.trim()) {
115
+ throw new Error('create 操作必须提供 prompt(任务描述正文或编码指令)');
116
+ }
117
+ const createCmd = ['agent-task', 'create', args.prompt.trim(), ...repoArgs];
118
+ if (args.base_branch)
119
+ createCmd.push('-b', args.base_branch.trim());
120
+ if (args.custom_agent)
121
+ createCmd.push('-a', args.custom_agent.trim());
122
+ const res = await executor.run(createCmd, { ...runOpts, rawText: true, timeoutMs: 60_000 });
123
+ if (!res.ok)
124
+ throw new Error(res.error);
125
+ return {
126
+ message: '智能体任务已成功创建并派发',
127
+ output: res.rawOutput || 'Agent task dispatched',
128
+ };
129
+ }
130
+ default:
131
+ throw new Error(`未知的 Agent Task 操作: ${args.action}`);
132
+ }
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,51 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createCodespaceTool(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
+ codespace: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ repo: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ org: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ limit: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ confirm: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ };
34
+ required: string[];
35
+ };
36
+ output: {
37
+ schema: {
38
+ type: string;
39
+ };
40
+ render(args: any, value: any): {
41
+ type: string;
42
+ text: string;
43
+ }[];
44
+ };
45
+ execute(args: any, execContext: any): Promise<Record<string, any> | {
46
+ status: string;
47
+ scope: string;
48
+ command: string;
49
+ tool: string;
50
+ }>;
51
+ };
@@ -0,0 +1,154 @@
1
+ import { renderCodespaceMarkdown } from '../renderers/markdown.js';
2
+ import { checkScopeRequired } from './project.js';
3
+ function normalizeOutput(data) {
4
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
5
+ return data;
6
+ }
7
+ if (Array.isArray(data)) {
8
+ return { items: data, count: data.length };
9
+ }
10
+ return { result: data !== undefined && data !== null ? data : '' };
11
+ }
12
+ const CODESPACE_LIST_FIELDS = 'name,displayName,repository,owner,state,gitStatus,lastUsedAt,createdAt,machineName';
13
+ const CODESPACE_VIEW_FIELDS = 'name,displayName,repository,owner,state,gitStatus,lastUsedAt,createdAt,machineName,devcontainerPath,recentFolders,idleTimeoutMinutes';
14
+ export function createCodespaceTool(executor) {
15
+ return {
16
+ name: 'github_codespace',
17
+ description: '管理 GitHub Codespaces 云端远程开发环境:查阅实例列表与运行状态、检视实例配置详情、停止运行中的环境防计费、安全销毁实例。未授权时提供平台感知自愈命令。',
18
+ parameters: {
19
+ type: 'object',
20
+ properties: {
21
+ action: {
22
+ type: 'string',
23
+ enum: ['list', 'view', 'stop', 'delete'],
24
+ description: 'Codespace 操作类型。list 查看环境列表、view 检视详情、stop 停止运行(防持续计费)、delete 销毁实例(需 confirm)。',
25
+ },
26
+ codespace: {
27
+ type: 'string',
28
+ description: 'Codespace 实例名称(view, stop, delete 动作必需,如 "fuzzy-spoon-69vj";防交互式挂起)。',
29
+ },
30
+ repo: {
31
+ type: 'string',
32
+ description: '按目标仓库筛选 [owner/repo](list 动作可选)。',
33
+ },
34
+ org: {
35
+ type: 'string',
36
+ description: '组织管理员按组织筛选(list, stop, delete 动作可选)。',
37
+ },
38
+ limit: {
39
+ type: 'number',
40
+ description: '返回数量上限(list 动作使用,默认 30)。',
41
+ },
42
+ confirm: {
43
+ type: 'boolean',
44
+ description: '危险操作确认标记(delete 动作必需)。销毁云端环境不可逆,未确认时输出影响评估并拦截。',
45
+ },
46
+ },
47
+ required: ['action'],
48
+ },
49
+ output: {
50
+ schema: { type: 'object' },
51
+ render(args, value) {
52
+ return [{ type: 'text', text: renderCodespaceMarkdown(args, value) }];
53
+ },
54
+ },
55
+ async execute(args, execContext) {
56
+ const cwd = execContext?.cwd || process.cwd();
57
+ const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
58
+ const runOpts = { cwd, sessionId };
59
+ switch (args.action) {
60
+ case 'list': {
61
+ const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.min(args.limit, 100) : 30;
62
+ const listCmd = [
63
+ 'codespace', 'list',
64
+ '-L', String(limit),
65
+ '--json', CODESPACE_LIST_FIELDS,
66
+ ];
67
+ if (args.repo)
68
+ listCmd.push('-R', String(args.repo).trim());
69
+ if (args.org)
70
+ listCmd.push('-o', String(args.org).trim());
71
+ const res = await executor.run(listCmd, runOpts);
72
+ if (!res.ok) {
73
+ const scopeFix = checkScopeRequired(res.error || '', 'codespace', 'gh auth refresh -h github.com -s codespace', 'github_codespace');
74
+ if (scopeFix)
75
+ return scopeFix;
76
+ throw new Error(res.error);
77
+ }
78
+ return normalizeOutput(res.data || res.rawOutput);
79
+ }
80
+ case 'view': {
81
+ if (!args.codespace || typeof args.codespace !== 'string' || !args.codespace.trim()) {
82
+ throw new Error('view 操作必须提供 codespace 名称,以防止 CLI 打开交互式选择器导致挂起');
83
+ }
84
+ const cleanName = args.codespace.trim();
85
+ const viewCmd = [
86
+ 'codespace', 'view',
87
+ '-c', cleanName,
88
+ '--json', CODESPACE_VIEW_FIELDS,
89
+ ];
90
+ if (args.repo)
91
+ viewCmd.push('-R', String(args.repo).trim());
92
+ const res = await executor.run(viewCmd, runOpts);
93
+ if (!res.ok) {
94
+ const scopeFix = checkScopeRequired(res.error || '', 'codespace', 'gh auth refresh -h github.com -s codespace', 'github_codespace');
95
+ if (scopeFix)
96
+ return scopeFix;
97
+ throw new Error(res.error);
98
+ }
99
+ return normalizeOutput(res.data || res.rawOutput);
100
+ }
101
+ case 'stop': {
102
+ if (!args.codespace || typeof args.codespace !== 'string' || !args.codespace.trim()) {
103
+ throw new Error('stop 操作必须提供 codespace 名称,以防止 CLI 进入交互式挂起');
104
+ }
105
+ const cleanName = args.codespace.trim();
106
+ const stopCmd = ['codespace', 'stop', '-c', cleanName];
107
+ if (args.org)
108
+ stopCmd.push('-o', String(args.org).trim());
109
+ const res = await executor.run(stopCmd, { ...runOpts, rawText: true, timeoutMs: 45_000 });
110
+ if (!res.ok) {
111
+ const scopeFix = checkScopeRequired(res.error || '', 'codespace', 'gh auth refresh -h github.com -s codespace', 'github_codespace');
112
+ if (scopeFix)
113
+ return scopeFix;
114
+ throw new Error(res.error);
115
+ }
116
+ return {
117
+ message: `云端开发环境「${cleanName}」已成功发送停止指令(已避免继续占用算力计费)`,
118
+ output: res.rawOutput || 'Stopped',
119
+ };
120
+ }
121
+ case 'delete': {
122
+ if (!args.codespace || typeof args.codespace !== 'string' || !args.codespace.trim()) {
123
+ throw new Error('delete 操作必须提供 codespace 名称');
124
+ }
125
+ const cleanName = args.codespace.trim();
126
+ if (args.confirm !== true) {
127
+ return {
128
+ status: 'AWAITING_CONFIRMATION',
129
+ warning: `⚠️ 销毁确认拦截:你即将永久删除云端开发环境「${cleanName}」。`,
130
+ impact: '物理销毁 Codespace 将永久释放云端计算资源,环境中任何未提交并推送到远端 Git 的临时修改都将无法找回。',
131
+ requiredAction: '如确认销毁该实例,请核对环境名称并在参数中附加 `confirm: true` 重新调用。',
132
+ };
133
+ }
134
+ const deleteCmd = ['codespace', 'delete', '-c', cleanName, '-f'];
135
+ if (args.org)
136
+ deleteCmd.push('-o', String(args.org).trim());
137
+ const res = await executor.run(deleteCmd, { ...runOpts, rawText: true, timeoutMs: 60_000 });
138
+ if (!res.ok) {
139
+ const scopeFix = checkScopeRequired(res.error || '', 'codespace', 'gh auth refresh -h github.com -s codespace', 'github_codespace');
140
+ if (scopeFix)
141
+ return scopeFix;
142
+ throw new Error(res.error);
143
+ }
144
+ return {
145
+ message: `云端开发环境「${cleanName}」已成功销毁释放`,
146
+ output: res.rawOutput || 'Deleted',
147
+ };
148
+ }
149
+ default:
150
+ throw new Error(`未知的 Codespace 操作: ${args.action}`);
151
+ }
152
+ },
153
+ };
154
+ }
@@ -0,0 +1,70 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createDiscussionTool(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
+ discussion_number: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ title: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ body: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ category: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ state: {
34
+ type: string;
35
+ enum: string[];
36
+ description: string;
37
+ };
38
+ labels: {
39
+ type: string;
40
+ items: {
41
+ type: string;
42
+ };
43
+ description: string;
44
+ };
45
+ search: {
46
+ type: string;
47
+ description: string;
48
+ };
49
+ comments: {
50
+ type: string;
51
+ description: string;
52
+ };
53
+ limit: {
54
+ type: string;
55
+ description: string;
56
+ };
57
+ };
58
+ required: string[];
59
+ };
60
+ output: {
61
+ schema: {
62
+ type: string;
63
+ };
64
+ render(args: any, value: any): {
65
+ type: string;
66
+ text: string;
67
+ }[];
68
+ };
69
+ execute(args: any, execContext: any): Promise<Record<string, any>>;
70
+ };