@yvhitxcel/opencode-remote 0.15.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.md +82 -0
- package/bin/opencode-remote.js +70 -0
- package/bin/opencode-weixin.js +10 -0
- package/dist/AGENTS.md +20 -0
- package/dist/MEMORY.md +21 -0
- package/dist/bot-runner.js +180 -0
- package/dist/cli.js +256 -0
- package/dist/core/approval.js +95 -0
- package/dist/core/auth.js +119 -0
- package/dist/core/config.js +61 -0
- package/dist/core/notifications.js +134 -0
- package/dist/core/qiniu.js +267 -0
- package/dist/core/registry.js +86 -0
- package/dist/core/router.js +344 -0
- package/dist/core/session.js +403 -0
- package/dist/core/setup.js +418 -0
- package/dist/core/types.js +16 -0
- package/dist/feishu/adapter.js +72 -0
- package/dist/feishu/bot.js +168 -0
- package/dist/feishu/commands.js +601 -0
- package/dist/feishu/handler.js +380 -0
- package/dist/index.js +60 -0
- package/dist/opencode/client.js +823 -0
- package/dist/package-lock.json +762 -0
- package/dist/patch_spawn.js +28 -0
- package/dist/plugins/agents/acp/acp-adapter.js +42 -0
- package/dist/plugins/agents/claude-code/index.js +69 -0
- package/dist/plugins/agents/codex/index.js +44 -0
- package/dist/plugins/agents/copilot/index.js +44 -0
- package/dist/plugins/agents/opencode/index.js +66 -0
- package/dist/telegram/bot.js +288 -0
- package/dist/utils/message-split.js +38 -0
- package/dist/web/code-viewer.js +266 -0
- package/dist/weixin/adapter.js +135 -0
- package/dist/weixin/api.js +179 -0
- package/dist/weixin/bot.js +183 -0
- package/dist/weixin/commands.js +758 -0
- package/dist/weixin/handler.js +577 -0
- package/dist/weixin/node_modules/encodeurl/LICENSE +22 -0
- package/dist/weixin/node_modules/encodeurl/README.md +109 -0
- package/dist/weixin/node_modules/encodeurl/index.js +60 -0
- package/dist/weixin/node_modules/encodeurl/package.json +40 -0
- package/dist/weixin/node_modules/qiniu/.claude/settings.local.json +7 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/ci-test.yml +36 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/npm-publish.yml +20 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/version-check.yml +19 -0
- package/dist/weixin/node_modules/qiniu/.idea/MarsCodeWorkspaceAppSettings.xml +7 -0
- package/dist/weixin/node_modules/qiniu/.idea/codeStyles/Project.xml +44 -0
- package/dist/weixin/node_modules/qiniu/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/dist/weixin/node_modules/qiniu/.idea/git_toolbox_blame.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/jsLibraryMappings.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/modules.xml +8 -0
- package/dist/weixin/node_modules/qiniu/.idea/nodejs-sdk.iml +12 -0
- package/dist/weixin/node_modules/qiniu/.idea/vcs.xml +6 -0
- package/dist/weixin/node_modules/qiniu/CHANGELOG.md +292 -0
- package/dist/weixin/node_modules/qiniu/README.md +56 -0
- package/dist/weixin/node_modules/qiniu/StorageResponseInterface.d.ts +239 -0
- package/dist/weixin/node_modules/qiniu/codecov.yml +28 -0
- package/dist/weixin/node_modules/qiniu/index.d.ts +1995 -0
- package/dist/weixin/node_modules/qiniu/index.js +32 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/HISTORY.md +14 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/LICENSE +22 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/README.md +128 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/index.js +60 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/package.json +40 -0
- package/dist/weixin/node_modules/qiniu/package.json +80 -0
- package/dist/weixin/node_modules/qiniu/qiniu/auth/digest.js +13 -0
- package/dist/weixin/node_modules/qiniu/qiniu/cdn.js +149 -0
- package/dist/weixin/node_modules/qiniu/qiniu/conf.js +254 -0
- package/dist/weixin/node_modules/qiniu/qiniu/fop.js +112 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/client.js +253 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpoint.js +66 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsProvider.js +27 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsRetryPolicy.js +76 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/base.js +31 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/index.js +9 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/qiniuAuth.js +53 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/retryDomains.js +101 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/ua.js +36 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/region.js +349 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsProvider.js +788 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsRetryPolicy.js +242 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/responseWrapper.js +40 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/index.js +4 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/retrier.js +99 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/retryPolicy.js +55 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rpc.js +237 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/app.js +123 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/credentials.js +57 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/room.js +118 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/util.js +16 -0
- package/dist/weixin/node_modules/qiniu/qiniu/sms/message.js +58 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/form.js +442 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/internal.js +214 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/resume.js +1272 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/rs.js +1764 -0
- package/dist/weixin/node_modules/qiniu/qiniu/util.js +382 -0
- package/dist/weixin/node_modules/qiniu/qiniu/zone.js +230 -0
- package/dist/weixin/node_modules/qiniu/tsconfig.json +112 -0
- package/dist/weixin/types.js +25 -0
- package/package.json +56 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Plugin registry for messengers and agents
|
|
2
|
+
class PluginRegistry {
|
|
3
|
+
messengers = new Map();
|
|
4
|
+
agents = new Map();
|
|
5
|
+
agentAliases = new Map();
|
|
6
|
+
|
|
7
|
+
registerMessenger(adapter) {
|
|
8
|
+
if (this.messengers.has(adapter.name)) {
|
|
9
|
+
console.warn(`Messenger "${adapter.name}" already registered, overwriting`);
|
|
10
|
+
}
|
|
11
|
+
this.messengers.set(adapter.name, adapter);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
registerAgent(adapter) {
|
|
15
|
+
if (this.agents.has(adapter.name)) {
|
|
16
|
+
console.warn(`Agent "${adapter.name}" already registered, overwriting`);
|
|
17
|
+
}
|
|
18
|
+
this.agents.set(adapter.name, adapter);
|
|
19
|
+
for (const alias of adapter.aliases || []) {
|
|
20
|
+
if (this.agentAliases.has(alias)) {
|
|
21
|
+
console.warn(`Agent alias "${alias}" already registered, overwriting`);
|
|
22
|
+
}
|
|
23
|
+
this.agentAliases.set(alias, adapter.name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getMessenger(name) {
|
|
28
|
+
return this.messengers.get(name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getAgent(name) {
|
|
32
|
+
return this.agents.get(name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
findAgent(nameOrAlias) {
|
|
36
|
+
const agent = this.agents.get(nameOrAlias);
|
|
37
|
+
if (agent) return agent;
|
|
38
|
+
const realName = this.agentAliases.get(nameOrAlias);
|
|
39
|
+
if (realName) {
|
|
40
|
+
return this.agents.get(realName);
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
listMessengers() {
|
|
46
|
+
return Array.from(this.messengers.keys());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
listAgents() {
|
|
50
|
+
return Array.from(this.agents.keys());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async loadBuiltInPlugins() {
|
|
54
|
+
try {
|
|
55
|
+
const { OpenCodeAgentAdapter } = await import('../plugins/agents/opencode/index.js');
|
|
56
|
+
this.registerAgent(new OpenCodeAgentAdapter());
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.warn('Failed to load OpenCode agent:', e.message);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const { ClaudeCodeAgentAdapter } = await import('../plugins/agents/claude-code/index.js');
|
|
63
|
+
this.registerAgent(new ClaudeCodeAgentAdapter());
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.warn('Failed to load Claude Code agent:', e.message);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const { CodexAgentAdapter } = await import('../plugins/agents/codex/index.js');
|
|
70
|
+
this.registerAgent(new CodexAgentAdapter());
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.warn('Failed to load Codex agent:', e.message);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const { CopilotAgentAdapter } = await import('../plugins/agents/copilot/index.js');
|
|
77
|
+
this.registerAgent(new CopilotAgentAdapter());
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.warn('Failed to load Copilot agent:', e.message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(`Plugin registry: ${this.agents.size} agents registered`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const registry = new PluginRegistry();
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// Message router - full command definitions shared across all platforms
|
|
2
|
+
import { registry } from './registry.js';
|
|
3
|
+
import { initOpenCode, listProviders, updateGlobalModel, checkConnection, resumeSession, shareSession } from '../opencode/client.js';
|
|
4
|
+
|
|
5
|
+
export const COMMAND_ALIASES = {
|
|
6
|
+
start: ['start'],
|
|
7
|
+
help: ['help', 'h', '?'],
|
|
8
|
+
status: ['status'],
|
|
9
|
+
reset: ['reset'],
|
|
10
|
+
stop: ['stop'],
|
|
11
|
+
restart: ['restart'],
|
|
12
|
+
sessions: ['sessions', 'sw'],
|
|
13
|
+
delsessions: ['delsessions', 'del'],
|
|
14
|
+
loop: ['loop'],
|
|
15
|
+
edit: ['edit'],
|
|
16
|
+
diagnose: ['diagnose'],
|
|
17
|
+
refresh: ['refresh'],
|
|
18
|
+
copy: ['copy'],
|
|
19
|
+
revert: ['revert'],
|
|
20
|
+
upload: ['upload'],
|
|
21
|
+
delete: ['delete'],
|
|
22
|
+
oc: ['oc'],
|
|
23
|
+
cc: ['cc'],
|
|
24
|
+
cx: ['cx'],
|
|
25
|
+
copilot: ['copilot'],
|
|
26
|
+
agents: ['agents'],
|
|
27
|
+
model: ['model'],
|
|
28
|
+
expert: ['expert', 'z', 'review'],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const COMMAND_MAP = {};
|
|
32
|
+
for (const [cmd, aliases] of Object.entries(COMMAND_ALIASES)) {
|
|
33
|
+
for (const alias of aliases) {
|
|
34
|
+
COMMAND_MAP[alias] = cmd;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function detectCommand(text) {
|
|
39
|
+
const trimmed = text.trim();
|
|
40
|
+
if (trimmed === 'h' || trimmed === '?') {
|
|
41
|
+
return { name: 'help', arg: '' };
|
|
42
|
+
}
|
|
43
|
+
if (/^[.。\/]/.test(trimmed)) {
|
|
44
|
+
const cmd = trimmed.slice(1).trim();
|
|
45
|
+
const parts = cmd.split(/\s+/);
|
|
46
|
+
const name = COMMAND_MAP[parts[0].toLowerCase()];
|
|
47
|
+
if (name) {
|
|
48
|
+
return { name, arg: parts.slice(1).join(' ') };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveAgentFromCommand(commandName) {
|
|
55
|
+
const agent = registry.findAgent(commandName);
|
|
56
|
+
if (agent) return agent.name;
|
|
57
|
+
const fallback = { oc: 'opencode', cc: 'claude-code', cx: 'codex', copilot: 'copilot' };
|
|
58
|
+
return fallback[commandName] || null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function parseMessage(text) {
|
|
62
|
+
const trimmed = text.trim();
|
|
63
|
+
if (!trimmed) return { type: 'default', prompt: '' };
|
|
64
|
+
|
|
65
|
+
const detected = detectCommand(text);
|
|
66
|
+
if (detected) {
|
|
67
|
+
const agentName = resolveAgentFromCommand(detected.name);
|
|
68
|
+
if (agentName) {
|
|
69
|
+
return { type: 'agent', agent: agentName, prompt: detected.arg };
|
|
70
|
+
}
|
|
71
|
+
return { type: 'command', command: detected.name, arg: detected.arg };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { type: 'default', prompt: trimmed };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatTimeAgo(timestamp) {
|
|
78
|
+
const diff = Date.now() - timestamp;
|
|
79
|
+
const seconds = Math.floor(diff / 1000);
|
|
80
|
+
if (seconds < 60) return `${seconds}秒前`;
|
|
81
|
+
const minutes = Math.floor(seconds / 60);
|
|
82
|
+
if (minutes < 60) return `${minutes}分钟前`;
|
|
83
|
+
const hours = Math.floor(minutes / 60);
|
|
84
|
+
if (hours < 24) return `${hours}小时前`;
|
|
85
|
+
return `${Math.floor(hours / 24)}天前`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function getSessionsList() {
|
|
89
|
+
const opencode = await initOpenCode();
|
|
90
|
+
if (!opencode) return null;
|
|
91
|
+
const result = await opencode.client.session.list();
|
|
92
|
+
if (result.error || !result.data) return [];
|
|
93
|
+
return result.data.sort((a, b) => (b.time?.updated || b.updated_at || 0) - (a.time?.updated || a.updated_at || 0));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function getSessionMessages(sessionId) {
|
|
97
|
+
const opencode = await initOpenCode();
|
|
98
|
+
if (!opencode) return null;
|
|
99
|
+
const result = await opencode.client.session.messages({ path: { id: sessionId } });
|
|
100
|
+
if (result.error) return null;
|
|
101
|
+
return result.data || [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function routeMessage(parsed, ctx) {
|
|
105
|
+
switch (parsed.type) {
|
|
106
|
+
case 'command': {
|
|
107
|
+
switch (parsed.command) {
|
|
108
|
+
case 'help':
|
|
109
|
+
return `📖 指令
|
|
110
|
+
|
|
111
|
+
🟢 常用:
|
|
112
|
+
/start — 首次认证
|
|
113
|
+
/help — 帮助
|
|
114
|
+
/status — 连接状态
|
|
115
|
+
/reset — 清空会话
|
|
116
|
+
/copy — 复制回复
|
|
117
|
+
/revert — 撤销消息
|
|
118
|
+
|
|
119
|
+
🔄 任务:
|
|
120
|
+
/loop — 循环执行
|
|
121
|
+
/refresh — 刷新上下文
|
|
122
|
+
/restart — 重启 bot
|
|
123
|
+
/stop — 停止 bot
|
|
124
|
+
|
|
125
|
+
📂 会话:
|
|
126
|
+
/sessions — 浏览会话
|
|
127
|
+
/delsessions — 删除会话
|
|
128
|
+
|
|
129
|
+
🤖 AI 模型:
|
|
130
|
+
/model — 切换模型
|
|
131
|
+
/agents — 查看可用 Agent
|
|
132
|
+
/oc — 使用 OpenCode
|
|
133
|
+
/cc — 使用 Claude Code
|
|
134
|
+
|
|
135
|
+
💬 直接发消息给 AI!`;
|
|
136
|
+
|
|
137
|
+
case 'agents': {
|
|
138
|
+
const agents = registry.listAgents();
|
|
139
|
+
const lines = ['🤖 可用 AI Agent:'];
|
|
140
|
+
for (const name of agents) {
|
|
141
|
+
const agent = registry.findAgent(name);
|
|
142
|
+
const aliases = agent?.aliases || [];
|
|
143
|
+
const available = await agent?.isAvailable().catch(() => false);
|
|
144
|
+
const status = available ? '✅' : '❌';
|
|
145
|
+
const aliasStr = aliases.length > 0 ? ` (${aliases.join(', ')})` : '';
|
|
146
|
+
lines.push(`${status} ${name}${aliasStr}`);
|
|
147
|
+
}
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push('切换: /oc /cc /cx /copilot');
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'status': {
|
|
154
|
+
const connected = await checkConnection();
|
|
155
|
+
return `${connected ? '✅' : '❌'} OpenCode ${connected ? '在线' : '离线'}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'start':
|
|
159
|
+
return '🚀 准备就绪,发送消息给 OpenCode 开始工作';
|
|
160
|
+
|
|
161
|
+
case 'reset':
|
|
162
|
+
case 'new':
|
|
163
|
+
return '🔄 会话已重置';
|
|
164
|
+
|
|
165
|
+
case 'restart':
|
|
166
|
+
return '🔄 重启信号已发送,bot 即将重启...';
|
|
167
|
+
|
|
168
|
+
case 'stop':
|
|
169
|
+
return '🛑 停止信号已发送';
|
|
170
|
+
|
|
171
|
+
case 'sessions': {
|
|
172
|
+
const sessions = await getSessionsList();
|
|
173
|
+
if (!sessions || sessions.length === 0) return '📭 暂无会话';
|
|
174
|
+
let msg = '📂 最近会话:\n\n';
|
|
175
|
+
sessions.slice(0, 10).forEach((s, i) => {
|
|
176
|
+
const title = s.title || '无标题';
|
|
177
|
+
const time = s.updated_at ? formatTimeAgo(s.updated_at * 1000) : '';
|
|
178
|
+
msg += `${i + 1}. ${title} (${time})\n`;
|
|
179
|
+
});
|
|
180
|
+
return msg;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case 'delsessions': {
|
|
184
|
+
const sessions = await getSessionsList();
|
|
185
|
+
if (!sessions || sessions.length === 0) return '📭 暂无会话可删除';
|
|
186
|
+
let msg = '🗑️ 选择要删除的会话:\n\n';
|
|
187
|
+
sessions.slice(0, 10).forEach((s, i) => {
|
|
188
|
+
msg += `${i + 1}. ${s.title || '无标题'}\n`;
|
|
189
|
+
});
|
|
190
|
+
msg += '\n(在当前平台无法交互式选择,请使用 WeChat)';
|
|
191
|
+
return msg;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
case 'loop':
|
|
195
|
+
if (parsed.arg === 'off' || parsed.arg === 'stop') return '⏹️ 循环任务已停止';
|
|
196
|
+
if (parsed.arg === 'status') return '🔄 循环任务状态(在微信中查看详情)';
|
|
197
|
+
return '🔄 循环任务已启动(完整控制请使用 WeChat)';
|
|
198
|
+
|
|
199
|
+
case 'refresh': {
|
|
200
|
+
if (!ctx.opencodeSessionId) return '❌ 没有活跃的会话';
|
|
201
|
+
const opencode = await initOpenCode();
|
|
202
|
+
if (!opencode) return '❌ 无法连接 OpenCode';
|
|
203
|
+
try {
|
|
204
|
+
await opencode.client.session.compact({ path: { id: ctx.opencodeSessionId } });
|
|
205
|
+
const result = await opencode.client.session.summarize({ path: { id: ctx.opencodeSessionId } });
|
|
206
|
+
return result.error ? '⚠️ 压缩完成,但摘要生成失败' : '✅ 会话已刷新';
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error('[refresh] Error:', e.message);
|
|
209
|
+
return `❌ 刷新失败: ${e.message}`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case 'copy': {
|
|
214
|
+
if (!ctx.opencodeSessionId) return '❌ 没有活跃的会话';
|
|
215
|
+
const msgs = await getSessionMessages(ctx.opencodeSessionId);
|
|
216
|
+
if (!msgs || msgs.length === 0) return '❌ 无法获取消息';
|
|
217
|
+
const aiMsg = msgs.filter(m => m.info?.role === 'assistant').slice(-1)[0];
|
|
218
|
+
if (!aiMsg) return '❌ 未找到 AI 回复';
|
|
219
|
+
let content = '';
|
|
220
|
+
if (aiMsg.parts) {
|
|
221
|
+
for (const part of aiMsg.parts) {
|
|
222
|
+
if (part.type === 'text') content += part.text + '\n';
|
|
223
|
+
if (part.type === 'code') content += '```' + (part.language || '') + '\n' + part.code + '\n```\n';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return content ? `📋 最新回复:\n\n${content.substring(0, 2000)}` : '❌ 没有可复制的内容';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
case 'revert': {
|
|
230
|
+
if (!ctx.opencodeSessionId) return '❌ 没有活跃的会话';
|
|
231
|
+
const opencode = await initOpenCode();
|
|
232
|
+
if (!opencode) return '❌ 无法连接 OpenCode';
|
|
233
|
+
if (parsed.arg === 'undo') {
|
|
234
|
+
const ok = await opencode.client.session.unrevert?.({ path: { id: ctx.opencodeSessionId } });
|
|
235
|
+
return ok ? '↩️ 已恢复撤销的内容' : '❌ 恢复失败';
|
|
236
|
+
}
|
|
237
|
+
const msgs = await getSessionMessages(ctx.opencodeSessionId);
|
|
238
|
+
if (!msgs) return '❌ 无法获取消息';
|
|
239
|
+
const lastAssistant = msgs.filter(m => m.info?.role === 'assistant' && m.time?.created).slice(-1)[0];
|
|
240
|
+
if (!lastAssistant) return '📭 没有可撤销的消息';
|
|
241
|
+
const ok = await opencode.client.session.revert({ path: { id: ctx.opencodeSessionId }, body: { messageID: lastAssistant.id } });
|
|
242
|
+
return ok ? '↩️ 已撤销最近的消息' : '❌ 撤销失败';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case 'model': {
|
|
246
|
+
try {
|
|
247
|
+
if (parsed.arg) {
|
|
248
|
+
const modelStr = parsed.arg.trim();
|
|
249
|
+
const ok = await updateGlobalModel(modelStr);
|
|
250
|
+
return ok ? `✅ 已切换模型至: ${modelStr}` : '❌ 切换失败';
|
|
251
|
+
}
|
|
252
|
+
const providers = await listProviders();
|
|
253
|
+
if (!providers || providers.length === 0) return '❌ 无法获取模型列表';
|
|
254
|
+
let msg = '🧠 可用模型:\n\n';
|
|
255
|
+
for (const p of providers) {
|
|
256
|
+
const modelIds = Object.keys(p.models || {});
|
|
257
|
+
if (modelIds.length === 0) continue;
|
|
258
|
+
msg += `${p.name} (${p.id}):\n`;
|
|
259
|
+
for (const mid of modelIds.slice(0, 5)) {
|
|
260
|
+
msg += ` ${p.id}/${mid}\n`;
|
|
261
|
+
}
|
|
262
|
+
if (modelIds.length > 5) msg += ` ...还有 ${modelIds.length - 5} 个\n`;
|
|
263
|
+
}
|
|
264
|
+
msg += '\n用法: /model <provider/model>';
|
|
265
|
+
return msg;
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return `❌ 模型操作失败: ${e.message}`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case 'diagnose': {
|
|
272
|
+
const diag = ['🔍 诊断报告\n'];
|
|
273
|
+
const qiniuOk = !!process.env.QINIU_ACCESS_KEY;
|
|
274
|
+
const teleOk = !!process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_BOT_TOKEN !== 'your_bot_token_here';
|
|
275
|
+
const feiOk = !!process.env.FEISHU_APP_ID && !!process.env.FEISHU_APP_SECRET;
|
|
276
|
+
diag.push(`OpenCode: ${await checkConnection().then(() => '✅').catch(() => '❌')}`);
|
|
277
|
+
diag.push(`七牛云: ${qiniuOk ? '✅' : '❌'}`);
|
|
278
|
+
diag.push(`Telegram: ${teleOk ? '✅' : '❌'}`);
|
|
279
|
+
diag.push(`飞书: ${feiOk ? '✅' : '❌'}`);
|
|
280
|
+
diag.push(`会话: ${ctx.opencodeSessionId ? '✅' : '❌'}`);
|
|
281
|
+
return diag.join('\n');
|
|
282
|
+
}
|
|
283
|
+
case 'upload':
|
|
284
|
+
return process.env.QINIU_ACCESS_KEY ? '⬆️ 发送文件路径: /upload <path>' : '⬆️ 上传需要配置 QINIU_ACCESS_KEY 等环境变量';
|
|
285
|
+
case 'delete':
|
|
286
|
+
return '🗑️ 用法: /delete <key>';
|
|
287
|
+
case 'edit':
|
|
288
|
+
return ctx.opencodeSessionId ? '✏️ 用法: /edit <消息编号>' : '❌ 没有活跃的会话';
|
|
289
|
+
|
|
290
|
+
case 'expert': {
|
|
291
|
+
const { execSync } = await import('child_process');
|
|
292
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
293
|
+
const { homedir } = await import('os');
|
|
294
|
+
const { join } = await import('path');
|
|
295
|
+
const projectRoot = process.cwd();
|
|
296
|
+
let gitStatus = '', recentCommits = '', dirTree = '';
|
|
297
|
+
try { gitStatus = execSync('git status --short', { cwd: projectRoot, timeout: 5000, encoding: 'utf-8' }); } catch { gitStatus = '(not a git repo)'; }
|
|
298
|
+
try { recentCommits = execSync('git log --oneline -5', { cwd: projectRoot, timeout: 5000, encoding: 'utf-8' }); } catch { recentCommits = '(no commits)'; }
|
|
299
|
+
try { dirTree = execSync('cmd /c "tree /F /A"', { cwd: projectRoot, timeout: 5000, encoding: 'utf-8' }); } catch { dirTree = '(failed to get tree)'; }
|
|
300
|
+
const customPromptPath = join(homedir(), '.opencode-remote', 'expert-prompt.md');
|
|
301
|
+
let promptTemplate = '';
|
|
302
|
+
if (existsSync(customPromptPath)) {
|
|
303
|
+
promptTemplate = readFileSync(customPromptPath, 'utf-8');
|
|
304
|
+
} else {
|
|
305
|
+
promptTemplate = `你是一个软件工程专家团队。请按以下流程执行:
|
|
306
|
+
|
|
307
|
+
## 项目上下文
|
|
308
|
+
{git_status}
|
|
309
|
+
{recent_commits}
|
|
310
|
+
{directory_tree}
|
|
311
|
+
|
|
312
|
+
## 要求
|
|
313
|
+
1. 分析项目当前状态
|
|
314
|
+
2. 找出问题
|
|
315
|
+
3. 给出改进建议`;
|
|
316
|
+
}
|
|
317
|
+
const prompt = promptTemplate.replace('{git_status}', gitStatus.trim()).replace('{recent_commits}', recentCommits.trim()).replace('{directory_tree}', dirTree.trim());
|
|
318
|
+
const agent = registry.findAgent('opencode');
|
|
319
|
+
if (!agent) return '❌ OpenCode agent not found';
|
|
320
|
+
const available = await agent.isAvailable().catch(() => false);
|
|
321
|
+
if (!available) return '❌ OpenCode 不可用';
|
|
322
|
+
const response = await agent.sendPrompt(ctx.threadId || 'expert-review', prompt, []);
|
|
323
|
+
return response || '无响应';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
default:
|
|
327
|
+
return '❓ 未知指令';
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
case 'agent': {
|
|
331
|
+
const agent = registry.findAgent(parsed.agent);
|
|
332
|
+
if (!agent) return `❌ Agent "${parsed.agent}" 未找到`;
|
|
333
|
+
const available = await agent.isAvailable().catch(() => false);
|
|
334
|
+
if (!available) return `❌ ${parsed.agent} 不可用`;
|
|
335
|
+
if (parsed.prompt) {
|
|
336
|
+
const response = await agent.sendPrompt(ctx.threadId, parsed.prompt, []);
|
|
337
|
+
return response || '无响应';
|
|
338
|
+
}
|
|
339
|
+
return `✅ 已切换到 ${parsed.agent}`;
|
|
340
|
+
}
|
|
341
|
+
default:
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|