dsh-github-flow 0.3.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.
- package/README.en.md +287 -143
- package/README.md +270 -125
- package/docs/UPGRADE-NOTES.md +12 -9
- package/docs/architecture.md +126 -126
- package/lib/api/routes-actions.js +249 -0
- package/lib/api/routes-overview.js +94 -2
- package/lib/cache.d.ts +4 -0
- package/lib/cache.js +14 -0
- package/lib/client.d.ts +14 -1
- package/lib/client.js +4287 -2500
- package/lib/commands/gh.d.ts +6 -1
- package/lib/commands/gh.js +144 -71
- package/lib/executor/git-parser.d.ts +57 -0
- package/lib/executor/git-parser.js +255 -0
- package/lib/executor/platform-guide.d.ts +14 -0
- package/lib/executor/platform-guide.js +40 -0
- package/lib/executor/process-utils.d.ts +51 -0
- package/lib/executor/process-utils.js +162 -0
- package/lib/executor/version-utils.d.ts +12 -0
- package/lib/executor/version-utils.js +27 -0
- package/lib/executor.d.ts +70 -48
- package/lib/executor.js +371 -356
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +37 -3
- package/lib/renderers/agent-task.d.ts +16 -0
- package/lib/renderers/agent-task.js +107 -0
- package/lib/renderers/cache.d.ts +8 -0
- package/lib/renderers/cache.js +66 -0
- package/lib/renderers/codespace.d.ts +16 -0
- package/lib/renderers/codespace.js +103 -0
- package/lib/renderers/common.d.ts +24 -0
- package/lib/renderers/common.js +88 -0
- package/lib/renderers/discussion.d.ts +16 -0
- package/lib/renderers/discussion.js +101 -0
- package/lib/renderers/gist.d.ts +15 -0
- package/lib/renderers/gist.js +99 -0
- package/lib/renderers/issue.d.ts +4 -0
- package/lib/renderers/issue.js +70 -0
- package/lib/renderers/label.d.ts +4 -0
- package/lib/renderers/label.js +48 -0
- package/lib/renderers/markdown.d.ts +19 -32
- package/lib/renderers/markdown.js +19 -624
- package/lib/renderers/pr.d.ts +4 -0
- package/lib/renderers/pr.js +107 -0
- package/lib/renderers/project.d.ts +20 -0
- package/lib/renderers/project.js +128 -0
- package/lib/renderers/release.d.ts +4 -0
- package/lib/renderers/release.js +74 -0
- package/lib/renderers/repo.d.ts +4 -0
- package/lib/renderers/repo.js +149 -0
- package/lib/renderers/ruleset.d.ts +4 -0
- package/lib/renderers/ruleset.js +74 -0
- package/lib/renderers/run.d.ts +4 -0
- package/lib/renderers/run.js +79 -0
- package/lib/renderers/skill.d.ts +34 -0
- package/lib/renderers/skill.js +241 -0
- package/lib/renderers/status.d.ts +28 -0
- package/lib/renderers/status.js +176 -0
- package/lib/telemetry.d.ts +70 -0
- package/lib/telemetry.js +161 -0
- package/lib/tools/agent-task.d.ts +54 -0
- package/lib/tools/agent-task.js +135 -0
- package/lib/tools/cache.d.ts +64 -0
- package/lib/tools/cache.js +186 -0
- package/lib/tools/codespace.d.ts +51 -0
- package/lib/tools/codespace.js +154 -0
- package/lib/tools/discussion.d.ts +70 -0
- package/lib/tools/discussion.js +195 -0
- package/lib/tools/gist.d.ts +155 -0
- package/lib/tools/gist.js +190 -0
- package/lib/tools/help.js +214 -13
- package/lib/tools/image-utils.d.ts +8 -0
- package/lib/tools/image-utils.js +34 -0
- package/lib/tools/issue.d.ts +8 -1
- package/lib/tools/issue.js +26 -4
- package/lib/tools/label.d.ts +80 -0
- package/lib/tools/label.js +237 -0
- package/lib/tools/pr.d.ts +8 -0
- package/lib/tools/pr.js +48 -3
- package/lib/tools/project.d.ts +80 -0
- package/lib/tools/project.js +241 -0
- package/lib/tools/repo.js +13 -1
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/skill.d.ts +24 -1
- package/lib/tools/skill.js +115 -5
- package/lib/tools/status.d.ts +40 -0
- package/lib/tools/status.js +58 -0
- package/lib/types.d.ts +44 -0
- package/lib/ui/auth-guide-card.d.ts +4 -0
- package/lib/ui/auth-guide-card.js +161 -0
- package/lib/ui/branch-selector.d.ts +10 -0
- package/lib/ui/branch-selector.js +163 -0
- package/lib/ui/commit-modal.d.ts +8 -0
- package/lib/ui/commit-modal.js +219 -0
- package/lib/ui/help-modal.d.ts +15 -0
- package/lib/ui/help-modal.js +202 -0
- package/lib/ui/icons.d.ts +7 -0
- package/lib/ui/icons.js +53 -0
- package/lib/ui/network-banner.d.ts +10 -0
- package/lib/ui/network-banner.js +54 -0
- package/lib/ui/storage.d.ts +35 -0
- package/lib/ui/storage.js +158 -0
- package/lib/ui/styles.d.ts +6 -0
- package/lib/ui/styles.js +1544 -0
- package/lib/ui/sync-settings-modal.d.ts +11 -0
- package/lib/ui/sync-settings-modal.js +350 -0
- package/lib/ui/sync-status-badge.d.ts +8 -0
- package/lib/ui/sync-status-badge.js +64 -0
- package/lib/ui/upgrade-card.d.ts +7 -0
- package/lib/ui/upgrade-card.js +222 -0
- package/lib/ui/utils.d.ts +45 -0
- package/lib/ui/utils.js +226 -0
- package/lib/ui/workspace-panel.d.ts +4 -0
- package/lib/ui/workspace-panel.js +489 -0
- package/lib/upgrade-manager.d.ts +5 -1
- package/lib/upgrade-manager.js +24 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
package/lib/telemetry.js
ADDED
|
@@ -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,64 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
export declare function createCacheTool(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
|
+
cache_id: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
key: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
ref: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
limit: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
sort: {
|
|
34
|
+
type: string;
|
|
35
|
+
enum: string[];
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
order: {
|
|
39
|
+
type: string;
|
|
40
|
+
enum: string[];
|
|
41
|
+
description: string;
|
|
42
|
+
};
|
|
43
|
+
all: {
|
|
44
|
+
type: string;
|
|
45
|
+
description: string;
|
|
46
|
+
};
|
|
47
|
+
confirm: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
required: string[];
|
|
53
|
+
};
|
|
54
|
+
output: {
|
|
55
|
+
schema: {
|
|
56
|
+
type: string;
|
|
57
|
+
};
|
|
58
|
+
render(args: any, value: any): {
|
|
59
|
+
type: string;
|
|
60
|
+
text: string;
|
|
61
|
+
}[];
|
|
62
|
+
};
|
|
63
|
+
execute(args: any, execContext: any): Promise<Record<string, any>>;
|
|
64
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { renderCacheMarkdown } 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 createCacheTool(executor) {
|
|
12
|
+
return {
|
|
13
|
+
name: 'github_cache',
|
|
14
|
+
description: '管理 GitHub Actions 构建缓存 (Caches):查看缓存列表与占用容量、按 ID 或 Key 删除指定缓存、以及全量清空仓库缓存。全量清空不可逆,需 confirm: true。',
|
|
15
|
+
parameters: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
action: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: ['list', 'delete'],
|
|
21
|
+
description: 'Cache 操作类型。list 查看缓存列表与体积、delete 删除缓存。',
|
|
22
|
+
},
|
|
23
|
+
repo: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: '目标仓库 [owner/repo],留空则识别当前工作区仓库。',
|
|
26
|
+
},
|
|
27
|
+
cache_id: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: '缓存数字 ID(delete 动作指定单条缓存删除时使用)。',
|
|
30
|
+
},
|
|
31
|
+
key: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: '缓存 Key 键名前缀或完整键名(list 动作过滤检索,或 delete 动作按键删除时使用)。',
|
|
34
|
+
},
|
|
35
|
+
ref: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: '分支或 PR Git Ref(如 "refs/heads/main" 或 "refs/pull/42/merge",用于过滤或定向删除)。',
|
|
38
|
+
},
|
|
39
|
+
limit: {
|
|
40
|
+
type: 'number',
|
|
41
|
+
description: '返回缓存数量上限(list 动作使用,默认 30)。',
|
|
42
|
+
},
|
|
43
|
+
sort: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['created_at', 'last_accessed_at', 'size_in_bytes'],
|
|
46
|
+
description: '排序字段(list 动作使用,默认 last_accessed_at)。',
|
|
47
|
+
},
|
|
48
|
+
order: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
enum: ['asc', 'desc'],
|
|
51
|
+
description: '排序方向(list 动作使用,默认 desc)。',
|
|
52
|
+
},
|
|
53
|
+
all: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
description: '是否清空仓库全部缓存(delete 动作使用,高危操作,必需配合 confirm: true)。',
|
|
56
|
+
},
|
|
57
|
+
confirm: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
description: '高危破坏性操作确认标志(delete 当 all: true 时必需)。未传 true 时输出影响评估报告并拦截。',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['action'],
|
|
63
|
+
},
|
|
64
|
+
output: {
|
|
65
|
+
schema: { type: 'object' },
|
|
66
|
+
render(args, value) {
|
|
67
|
+
return [{ type: 'text', text: renderCacheMarkdown(args, value) }];
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
async execute(args, execContext) {
|
|
71
|
+
const cwd = execContext?.cwd || process.cwd();
|
|
72
|
+
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
73
|
+
let repoTarget = args.repo;
|
|
74
|
+
if (!repoTarget) {
|
|
75
|
+
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
76
|
+
if (meta?.nameWithOwner) {
|
|
77
|
+
repoTarget = meta.nameWithOwner;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!repoTarget) {
|
|
81
|
+
throw new Error('未能自动识别当前工作区关联的 GitHub 仓库。请通过 `repo` 参数显式指定目标仓库 [owner/repo](例如: "ItQianChen/dsh-github-flow"),或在当前工作区目录下关联 Git 远程仓库。');
|
|
82
|
+
}
|
|
83
|
+
const repoArgs = ['-R', repoTarget];
|
|
84
|
+
const runOpts = { cwd, sessionId };
|
|
85
|
+
switch (args.action) {
|
|
86
|
+
case 'list': {
|
|
87
|
+
const listCmd = [
|
|
88
|
+
'cache',
|
|
89
|
+
'list',
|
|
90
|
+
...repoArgs,
|
|
91
|
+
'--json',
|
|
92
|
+
'createdAt,id,key,lastAccessedAt,ref,sizeInBytes,version',
|
|
93
|
+
];
|
|
94
|
+
const limit = typeof args.limit === 'number' && args.limit > 0 ? args.limit : 30;
|
|
95
|
+
listCmd.push('-L', String(limit));
|
|
96
|
+
if (args.key) {
|
|
97
|
+
listCmd.push('-k', String(args.key));
|
|
98
|
+
}
|
|
99
|
+
if (args.ref) {
|
|
100
|
+
listCmd.push('-r', String(args.ref));
|
|
101
|
+
}
|
|
102
|
+
if (args.sort) {
|
|
103
|
+
listCmd.push('--sort', args.sort);
|
|
104
|
+
}
|
|
105
|
+
if (args.order) {
|
|
106
|
+
listCmd.push('--order', args.order);
|
|
107
|
+
}
|
|
108
|
+
const res = await executor.run(listCmd, runOpts);
|
|
109
|
+
if (!res.ok)
|
|
110
|
+
throw new Error(res.error);
|
|
111
|
+
let rawData = res.data;
|
|
112
|
+
if (!rawData && typeof res.rawOutput === 'string') {
|
|
113
|
+
try {
|
|
114
|
+
rawData = JSON.parse(res.rawOutput);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
rawData = [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return normalizeOutput(rawData || []);
|
|
121
|
+
}
|
|
122
|
+
case 'delete': {
|
|
123
|
+
// 1. 全量清空分支
|
|
124
|
+
if (args.all === true) {
|
|
125
|
+
if (args.confirm !== true) {
|
|
126
|
+
return {
|
|
127
|
+
status: 'AWAITING_CONFIRMATION',
|
|
128
|
+
action: 'delete',
|
|
129
|
+
all: true,
|
|
130
|
+
repo: repoTarget,
|
|
131
|
+
warning: `⚠️ 高危破坏性操作:即将清空仓库 ${repoTarget} 的所有 GitHub Actions 构建缓存`,
|
|
132
|
+
impact: `清空缓存不可逆,会导致后续流水线构建无法复用依赖与构建产物,显著增加 CI 运行耗时。`,
|
|
133
|
+
requiredAction: `如确认彻底清空,请在参数中附加 \`confirm: true\` 重新调用。示例:\`github_cache(action: 'delete', all: true, confirm: true)\``,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const delCmd = ['cache', 'delete', '--all', '--succeed-on-no-caches', ...repoArgs];
|
|
137
|
+
if (args.ref) {
|
|
138
|
+
delCmd.push('-r', String(args.ref));
|
|
139
|
+
}
|
|
140
|
+
const res = await executor.run(delCmd, runOpts);
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
throw new Error(res.error);
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
all: true,
|
|
146
|
+
message: `🗑️ 成功清空仓库 ${repoTarget} 的 Actions 构建缓存` + (args.ref ? ` (Ref: ${args.ref})` : ''),
|
|
147
|
+
detail: res.rawOutput || '',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// 2. 按 cache_id 删除
|
|
151
|
+
if (args.cache_id !== undefined && args.cache_id !== null && String(args.cache_id).trim() !== '') {
|
|
152
|
+
const delCmd = ['cache', 'delete', String(args.cache_id), ...repoArgs];
|
|
153
|
+
const res = await executor.run(delCmd, runOpts);
|
|
154
|
+
if (!res.ok)
|
|
155
|
+
throw new Error(res.error);
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
cache_id: String(args.cache_id),
|
|
159
|
+
message: `🗑️ 成功删除仓库 ${repoTarget} 中 ID 为 "${args.cache_id}" 的 Actions 缓存`,
|
|
160
|
+
detail: res.rawOutput || '',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// 3. 按 key 删除
|
|
164
|
+
if (args.key !== undefined && args.key !== null && String(args.key).trim() !== '') {
|
|
165
|
+
const delCmd = ['cache', 'delete', String(args.key), ...repoArgs];
|
|
166
|
+
if (args.ref) {
|
|
167
|
+
delCmd.push('-r', String(args.ref));
|
|
168
|
+
}
|
|
169
|
+
const res = await executor.run(delCmd, runOpts);
|
|
170
|
+
if (!res.ok)
|
|
171
|
+
throw new Error(res.error);
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
key: String(args.key),
|
|
175
|
+
message: `🗑️ 成功删除仓库 ${repoTarget} 中 Key 为 "${args.key}" 的 Actions 缓存` + (args.ref ? ` (Ref: ${args.ref})` : ''),
|
|
176
|
+
detail: res.rawOutput || '',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
throw new Error('删除缓存时必须指定 `cache_id`、`key` 或 `all: true`(全量删除需配合 confirm: true)。');
|
|
180
|
+
}
|
|
181
|
+
default:
|
|
182
|
+
throw new Error(`不支持的 cache 操作: "${args.action}"。可选值: list, delete。`);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -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
|
+
};
|