opencode-bridge 2.9.0-beta
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/.env.example +131 -0
- package/LICENSE +674 -0
- package/README.md +1195 -0
- package/bin/opencode-bridge.js +31 -0
- package/dist/commands/effort.d.ts +9 -0
- package/dist/commands/effort.d.ts.map +1 -0
- package/dist/commands/effort.js +56 -0
- package/dist/commands/effort.js.map +1 -0
- package/dist/commands/parser.d.ts +37 -0
- package/dist/commands/parser.d.ts.map +1 -0
- package/dist/commands/parser.js +355 -0
- package/dist/commands/parser.js.map +1 -0
- package/dist/config.d.ts +91 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +340 -0
- package/dist/config.js.map +1 -0
- package/dist/feishu/cards-stream.d.ts +65 -0
- package/dist/feishu/cards-stream.d.ts.map +1 -0
- package/dist/feishu/cards-stream.js +448 -0
- package/dist/feishu/cards-stream.js.map +1 -0
- package/dist/feishu/cards.d.ts +81 -0
- package/dist/feishu/cards.d.ts.map +1 -0
- package/dist/feishu/cards.js +560 -0
- package/dist/feishu/cards.js.map +1 -0
- package/dist/feishu/client.d.ts +132 -0
- package/dist/feishu/client.d.ts.map +1 -0
- package/dist/feishu/client.js +952 -0
- package/dist/feishu/client.js.map +1 -0
- package/dist/feishu/streamer.d.ts +30 -0
- package/dist/feishu/streamer.d.ts.map +1 -0
- package/dist/feishu/streamer.js +95 -0
- package/dist/feishu/streamer.js.map +1 -0
- package/dist/handlers/card-action.d.ts +12 -0
- package/dist/handlers/card-action.d.ts.map +1 -0
- package/dist/handlers/card-action.js +154 -0
- package/dist/handlers/card-action.js.map +1 -0
- package/dist/handlers/command.d.ts +76 -0
- package/dist/handlers/command.d.ts.map +1 -0
- package/dist/handlers/command.js +1773 -0
- package/dist/handlers/command.js.map +1 -0
- package/dist/handlers/discord.d.ts +78 -0
- package/dist/handlers/discord.d.ts.map +1 -0
- package/dist/handlers/discord.js +1832 -0
- package/dist/handlers/discord.js.map +1 -0
- package/dist/handlers/file-sender.d.ts +22 -0
- package/dist/handlers/file-sender.d.ts.map +1 -0
- package/dist/handlers/file-sender.js +183 -0
- package/dist/handlers/file-sender.js.map +1 -0
- package/dist/handlers/group.d.ts +21 -0
- package/dist/handlers/group.d.ts.map +1 -0
- package/dist/handlers/group.js +414 -0
- package/dist/handlers/group.js.map +1 -0
- package/dist/handlers/lifecycle.d.ts +17 -0
- package/dist/handlers/lifecycle.d.ts.map +1 -0
- package/dist/handlers/lifecycle.js +129 -0
- package/dist/handlers/lifecycle.js.map +1 -0
- package/dist/handlers/p2p.d.ts +44 -0
- package/dist/handlers/p2p.d.ts.map +1 -0
- package/dist/handlers/p2p.js +625 -0
- package/dist/handlers/p2p.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1562 -0
- package/dist/index.js.map +1 -0
- package/dist/opencode/client.d.ts +176 -0
- package/dist/opencode/client.d.ts.map +1 -0
- package/dist/opencode/client.js +1126 -0
- package/dist/opencode/client.js.map +1 -0
- package/dist/opencode/delayed-handler.d.ts +33 -0
- package/dist/opencode/delayed-handler.d.ts.map +1 -0
- package/dist/opencode/delayed-handler.js +74 -0
- package/dist/opencode/delayed-handler.js.map +1 -0
- package/dist/opencode/output-buffer.d.ts +56 -0
- package/dist/opencode/output-buffer.d.ts.map +1 -0
- package/dist/opencode/output-buffer.js +202 -0
- package/dist/opencode/output-buffer.js.map +1 -0
- package/dist/opencode/question-handler.d.ts +61 -0
- package/dist/opencode/question-handler.d.ts.map +1 -0
- package/dist/opencode/question-handler.js +183 -0
- package/dist/opencode/question-handler.js.map +1 -0
- package/dist/opencode/question-parser.d.ts +9 -0
- package/dist/opencode/question-parser.d.ts.map +1 -0
- package/dist/opencode/question-parser.js +69 -0
- package/dist/opencode/question-parser.js.map +1 -0
- package/dist/opencode/session-queue.d.ts +16 -0
- package/dist/opencode/session-queue.d.ts.map +1 -0
- package/dist/opencode/session-queue.js +41 -0
- package/dist/opencode/session-queue.js.map +1 -0
- package/dist/permissions/handler.d.ts +36 -0
- package/dist/permissions/handler.d.ts.map +1 -0
- package/dist/permissions/handler.js +141 -0
- package/dist/permissions/handler.js.map +1 -0
- package/dist/platform/adapters/discord-adapter.d.ts +45 -0
- package/dist/platform/adapters/discord-adapter.d.ts.map +1 -0
- package/dist/platform/adapters/discord-adapter.js +497 -0
- package/dist/platform/adapters/discord-adapter.js.map +1 -0
- package/dist/platform/adapters/feishu-adapter.d.ts +29 -0
- package/dist/platform/adapters/feishu-adapter.d.ts.map +1 -0
- package/dist/platform/adapters/feishu-adapter.js +150 -0
- package/dist/platform/adapters/feishu-adapter.js.map +1 -0
- package/dist/platform/registry.d.ts +41 -0
- package/dist/platform/registry.d.ts.map +1 -0
- package/dist/platform/registry.js +87 -0
- package/dist/platform/registry.js.map +1 -0
- package/dist/platform/types.d.ts +92 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/types.js +4 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/reliability/audit-log.d.ts +93 -0
- package/dist/reliability/audit-log.d.ts.map +1 -0
- package/dist/reliability/audit-log.js +248 -0
- package/dist/reliability/audit-log.js.map +1 -0
- package/dist/reliability/config-guard.d.ts +42 -0
- package/dist/reliability/config-guard.d.ts.map +1 -0
- package/dist/reliability/config-guard.js +264 -0
- package/dist/reliability/config-guard.js.map +1 -0
- package/dist/reliability/conversation-heartbeat.d.ts +37 -0
- package/dist/reliability/conversation-heartbeat.d.ts.map +1 -0
- package/dist/reliability/conversation-heartbeat.js +179 -0
- package/dist/reliability/conversation-heartbeat.js.map +1 -0
- package/dist/reliability/cron-api-server.d.ts +13 -0
- package/dist/reliability/cron-api-server.d.ts.map +1 -0
- package/dist/reliability/cron-api-server.js +247 -0
- package/dist/reliability/cron-api-server.js.map +1 -0
- package/dist/reliability/cron-control.d.ts +34 -0
- package/dist/reliability/cron-control.d.ts.map +1 -0
- package/dist/reliability/cron-control.js +864 -0
- package/dist/reliability/cron-control.js.map +1 -0
- package/dist/reliability/cron-semantic.d.ts +9 -0
- package/dist/reliability/cron-semantic.d.ts.map +1 -0
- package/dist/reliability/cron-semantic.js +208 -0
- package/dist/reliability/cron-semantic.js.map +1 -0
- package/dist/reliability/environment-doctor.d.ts +56 -0
- package/dist/reliability/environment-doctor.d.ts.map +1 -0
- package/dist/reliability/environment-doctor.js +213 -0
- package/dist/reliability/environment-doctor.js.map +1 -0
- package/dist/reliability/job-registry.d.ts +26 -0
- package/dist/reliability/job-registry.d.ts.map +1 -0
- package/dist/reliability/job-registry.js +77 -0
- package/dist/reliability/job-registry.js.map +1 -0
- package/dist/reliability/opencode-probe.d.ts +37 -0
- package/dist/reliability/opencode-probe.d.ts.map +1 -0
- package/dist/reliability/opencode-probe.js +195 -0
- package/dist/reliability/opencode-probe.js.map +1 -0
- package/dist/reliability/opencode-restart.d.ts +42 -0
- package/dist/reliability/opencode-restart.d.ts.map +1 -0
- package/dist/reliability/opencode-restart.js +155 -0
- package/dist/reliability/opencode-restart.js.map +1 -0
- package/dist/reliability/proactive-heartbeat.d.ts +39 -0
- package/dist/reliability/proactive-heartbeat.d.ts.map +1 -0
- package/dist/reliability/proactive-heartbeat.js +147 -0
- package/dist/reliability/proactive-heartbeat.js.map +1 -0
- package/dist/reliability/process-check-job.d.ts +73 -0
- package/dist/reliability/process-check-job.d.ts.map +1 -0
- package/dist/reliability/process-check-job.js +254 -0
- package/dist/reliability/process-check-job.js.map +1 -0
- package/dist/reliability/process-guard.d.ts +53 -0
- package/dist/reliability/process-guard.d.ts.map +1 -0
- package/dist/reliability/process-guard.js +344 -0
- package/dist/reliability/process-guard.js.map +1 -0
- package/dist/reliability/recovery-reporter.d.ts +37 -0
- package/dist/reliability/recovery-reporter.d.ts.map +1 -0
- package/dist/reliability/recovery-reporter.js +161 -0
- package/dist/reliability/recovery-reporter.js.map +1 -0
- package/dist/reliability/rescue-executor.d.ts +52 -0
- package/dist/reliability/rescue-executor.d.ts.map +1 -0
- package/dist/reliability/rescue-executor.js +244 -0
- package/dist/reliability/rescue-executor.js.map +1 -0
- package/dist/reliability/rescue-policy.d.ts +39 -0
- package/dist/reliability/rescue-policy.d.ts.map +1 -0
- package/dist/reliability/rescue-policy.js +85 -0
- package/dist/reliability/rescue-policy.js.map +1 -0
- package/dist/reliability/runtime-cron-dispatcher.d.ts +30 -0
- package/dist/reliability/runtime-cron-dispatcher.d.ts.map +1 -0
- package/dist/reliability/runtime-cron-dispatcher.js +100 -0
- package/dist/reliability/runtime-cron-dispatcher.js.map +1 -0
- package/dist/reliability/runtime-cron-orphan.d.ts +18 -0
- package/dist/reliability/runtime-cron-orphan.d.ts.map +1 -0
- package/dist/reliability/runtime-cron-orphan.js +87 -0
- package/dist/reliability/runtime-cron-orphan.js.map +1 -0
- package/dist/reliability/runtime-cron-registry.d.ts +4 -0
- package/dist/reliability/runtime-cron-registry.d.ts.map +1 -0
- package/dist/reliability/runtime-cron-registry.js +8 -0
- package/dist/reliability/runtime-cron-registry.js.map +1 -0
- package/dist/reliability/runtime-cron.d.ts +75 -0
- package/dist/reliability/runtime-cron.d.ts.map +1 -0
- package/dist/reliability/runtime-cron.js +309 -0
- package/dist/reliability/runtime-cron.js.map +1 -0
- package/dist/reliability/scheduler.d.ts +38 -0
- package/dist/reliability/scheduler.d.ts.map +1 -0
- package/dist/reliability/scheduler.js +174 -0
- package/dist/reliability/scheduler.js.map +1 -0
- package/dist/reliability/types.d.ts +151 -0
- package/dist/reliability/types.d.ts.map +1 -0
- package/dist/reliability/types.js +178 -0
- package/dist/reliability/types.js.map +1 -0
- package/dist/router/action-handlers.d.ts +27 -0
- package/dist/router/action-handlers.d.ts.map +1 -0
- package/dist/router/action-handlers.js +226 -0
- package/dist/router/action-handlers.js.map +1 -0
- package/dist/router/opencode-event-hub.d.ts +159 -0
- package/dist/router/opencode-event-hub.d.ts.map +1 -0
- package/dist/router/opencode-event-hub.js +589 -0
- package/dist/router/opencode-event-hub.js.map +1 -0
- package/dist/router/root-router.d.ts +94 -0
- package/dist/router/root-router.d.ts.map +1 -0
- package/dist/router/root-router.js +214 -0
- package/dist/router/root-router.js.map +1 -0
- package/dist/store/chat-session.d.ts +150 -0
- package/dist/store/chat-session.d.ts.map +1 -0
- package/dist/store/chat-session.js +640 -0
- package/dist/store/chat-session.js.map +1 -0
- package/dist/store/session-directory.d.ts +12 -0
- package/dist/store/session-directory.d.ts.map +1 -0
- package/dist/store/session-directory.js +47 -0
- package/dist/store/session-directory.js.map +1 -0
- package/dist/store/session-group.d.ts +19 -0
- package/dist/store/session-group.d.ts.map +1 -0
- package/dist/store/session-group.js +92 -0
- package/dist/store/session-group.js.map +1 -0
- package/dist/store/user-session.d.ts +19 -0
- package/dist/store/user-session.d.ts.map +1 -0
- package/dist/store/user-session.js +112 -0
- package/dist/store/user-session.js.map +1 -0
- package/dist/utils/async-queue.d.ts +12 -0
- package/dist/utils/async-queue.d.ts.map +1 -0
- package/dist/utils/async-queue.js +51 -0
- package/dist/utils/async-queue.js.map +1 -0
- package/dist/utils/directory-policy.d.ts +50 -0
- package/dist/utils/directory-policy.d.ts.map +1 -0
- package/dist/utils/directory-policy.js +379 -0
- package/dist/utils/directory-policy.js.map +1 -0
- package/dist/utils/session-title.d.ts +2 -0
- package/dist/utils/session-title.d.ts.map +1 -0
- package/dist/utils/session-title.js +10 -0
- package/dist/utils/session-title.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
import { reliabilityConfig } from '../config.js';
|
|
2
|
+
import { chatSessionStore } from '../store/chat-session.js';
|
|
3
|
+
const WEEKDAY_MAP = {
|
|
4
|
+
一: 1,
|
|
5
|
+
二: 2,
|
|
6
|
+
三: 3,
|
|
7
|
+
四: 4,
|
|
8
|
+
五: 5,
|
|
9
|
+
六: 6,
|
|
10
|
+
日: 0,
|
|
11
|
+
天: 0,
|
|
12
|
+
};
|
|
13
|
+
export function parseCronSlashIntent(rawArgs) {
|
|
14
|
+
const trimmed = rawArgs.trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return {
|
|
17
|
+
action: 'list',
|
|
18
|
+
source: 'slash',
|
|
19
|
+
argsText: '',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const [first, ...rest] = trimmed.split(/\s+/);
|
|
23
|
+
const action = normalizeAction(first);
|
|
24
|
+
if (!action) {
|
|
25
|
+
return {
|
|
26
|
+
action: 'help',
|
|
27
|
+
source: 'slash',
|
|
28
|
+
argsText: trimmed,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
action,
|
|
33
|
+
source: 'slash',
|
|
34
|
+
argsText: rest.join(' '),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export async function resolveCronIntentForExecution(options) {
|
|
38
|
+
const source = options.source;
|
|
39
|
+
const argsText = options.argsText.trim();
|
|
40
|
+
if (source === 'natural') {
|
|
41
|
+
if (options.semanticParser) {
|
|
42
|
+
const semantic = await options.semanticParser(argsText, source, options.action).catch(() => null);
|
|
43
|
+
if (semantic) {
|
|
44
|
+
return semantic;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return parseCronBodyIntent(argsText, 'natural');
|
|
48
|
+
}
|
|
49
|
+
const shouldUseSemantic = shouldUseSemanticParserForSlash(options.action, argsText);
|
|
50
|
+
if (shouldUseSemantic && options.semanticParser) {
|
|
51
|
+
const semantic = await options.semanticParser(argsText, source, options.action).catch(() => null);
|
|
52
|
+
if (semantic) {
|
|
53
|
+
return semantic;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (shouldUseSemantic) {
|
|
57
|
+
const fallbackText = buildFallbackBodyForLocalParser(options.action, argsText);
|
|
58
|
+
const fallback = parseCronBodyIntent(fallbackText, 'slash');
|
|
59
|
+
if (fallback.action !== 'help') {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (options.action) {
|
|
64
|
+
return {
|
|
65
|
+
action: options.action,
|
|
66
|
+
source,
|
|
67
|
+
argsText,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return parseCronSlashIntent(argsText);
|
|
71
|
+
}
|
|
72
|
+
export function buildCronHelpText(platform) {
|
|
73
|
+
const slashPrefix = platform === 'discord' ? '///cron' : '/cron';
|
|
74
|
+
const lines = [
|
|
75
|
+
'🕒 Cron 调度命令(支持语义解析)',
|
|
76
|
+
'',
|
|
77
|
+
'【语义命令(推荐)】',
|
|
78
|
+
`- \`${slashPrefix} 添加个定时任务,每天早上8点向我发送一份AI简报\``,
|
|
79
|
+
`- \`${slashPrefix} 生产AI简报,工作日记得发我\``,
|
|
80
|
+
`- \`${slashPrefix} 列出现在的定时任务\``,
|
|
81
|
+
`- \`${slashPrefix} 暂停任务 <jobId>\``,
|
|
82
|
+
`- \`${slashPrefix} 恢复任务 <jobId>\``,
|
|
83
|
+
`- \`${slashPrefix} 删除任务 <jobId>\``,
|
|
84
|
+
'',
|
|
85
|
+
'【结构化命令(高级)】',
|
|
86
|
+
`- \`${slashPrefix} list\``,
|
|
87
|
+
`- \`${slashPrefix} add --name <名称> --expr "<cron表达式>" --text "<执行内容>" [--session current|<id>] [--chat current|<会话ID>] [--dir current|<路径>] [--agent <名称>]\``,
|
|
88
|
+
`- \`${slashPrefix} update --id <jobId> [--name <名称>] [--expr "<cron表达式>"] [--text "<执行内容>"] [--chat current|<会话ID>] [--enabled true|false]\``,
|
|
89
|
+
`- \`${slashPrefix} remove --id <jobId>\``,
|
|
90
|
+
`- \`${slashPrefix} pause --id <jobId>\``,
|
|
91
|
+
`- \`${slashPrefix} resume --id <jobId>\``,
|
|
92
|
+
`- 持久化路径: \`${reliabilityConfig.cronJobsFile || '~/cron/jobs.json'}\``,
|
|
93
|
+
];
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
export function executeCronIntent(options) {
|
|
97
|
+
const { manager, intent, currentSessionId, currentDirectory, currentConversationId, creatorId, platform } = options;
|
|
98
|
+
if (!manager) {
|
|
99
|
+
return [
|
|
100
|
+
'❌ Runtime Cron 未启用。',
|
|
101
|
+
'请检查配置:',
|
|
102
|
+
'- RELIABILITY_CRON_ENABLED=true',
|
|
103
|
+
'- RELIABILITY_CRON_API_ENABLED=true(可选,开启 HTTP 管理)',
|
|
104
|
+
].join('\n');
|
|
105
|
+
}
|
|
106
|
+
if (intent.action === 'help') {
|
|
107
|
+
return buildCronHelpText(platform);
|
|
108
|
+
}
|
|
109
|
+
if (intent.action === 'list') {
|
|
110
|
+
const jobs = manager.listJobs();
|
|
111
|
+
if (jobs.length === 0) {
|
|
112
|
+
return [
|
|
113
|
+
'当前没有运行时 Cron 任务。',
|
|
114
|
+
`使用 \`${platform === 'discord' ? '///cron' : '/cron'} add ...\` 创建任务。`,
|
|
115
|
+
].join('\n');
|
|
116
|
+
}
|
|
117
|
+
const lines = ['🕒 运行时 Cron 任务列表', '(状态基于本地绑定表;fallback 为候选目标)'];
|
|
118
|
+
for (const job of jobs) {
|
|
119
|
+
const status = job.enabled ? '启用' : '暂停';
|
|
120
|
+
const briefText = brief(job.payload.text, 60);
|
|
121
|
+
const displayStatus = buildRuntimeCronDisplayStatus(job);
|
|
122
|
+
lines.push(`- [${status}] ${job.id} | ${job.name} | ${job.schedule.expr}`);
|
|
123
|
+
lines.push(` text: ${briefText}`);
|
|
124
|
+
lines.push(` target: ${displayStatus.targetWindow} | session: ${job.payload.sessionId || '未绑定'}`);
|
|
125
|
+
lines.push(` orphan: ${displayStatus.orphanStatus}`);
|
|
126
|
+
lines.push(` fallback: ${displayStatus.fallbackStatus}`);
|
|
127
|
+
}
|
|
128
|
+
return lines.join('\n');
|
|
129
|
+
}
|
|
130
|
+
const parsedArgs = parseOptionArgs(intent.argsText);
|
|
131
|
+
if (intent.action === 'remove') {
|
|
132
|
+
const id = resolveJobId(intent, parsedArgs);
|
|
133
|
+
if (!id) {
|
|
134
|
+
return '❌ 缺少任务 ID。用法:remove --id <jobId>';
|
|
135
|
+
}
|
|
136
|
+
const removed = manager.removeJob(id);
|
|
137
|
+
if (!removed) {
|
|
138
|
+
return `❌ 未找到任务: ${id}`;
|
|
139
|
+
}
|
|
140
|
+
return `✅ 已删除任务: ${id}`;
|
|
141
|
+
}
|
|
142
|
+
if (intent.action === 'pause' || intent.action === 'resume') {
|
|
143
|
+
const id = resolveJobId(intent, parsedArgs);
|
|
144
|
+
if (!id) {
|
|
145
|
+
return '❌ 缺少任务 ID。用法:pause/resume --id <jobId>';
|
|
146
|
+
}
|
|
147
|
+
const enabled = intent.action === 'resume';
|
|
148
|
+
try {
|
|
149
|
+
manager.updateJob({
|
|
150
|
+
id,
|
|
151
|
+
enabled,
|
|
152
|
+
});
|
|
153
|
+
return `✅ 已${enabled ? '恢复' : '暂停'}任务: ${id}`;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
return `❌ ${enabled ? '恢复' : '暂停'}任务失败: ${formatError(error)}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (intent.action === 'add') {
|
|
160
|
+
const expr = resolveExpr(intent, parsedArgs);
|
|
161
|
+
const text = resolveText(intent, parsedArgs);
|
|
162
|
+
if (!expr || !text) {
|
|
163
|
+
return '❌ 新增任务缺少参数:需要 expr 与 text。用法:add --name <名称> --expr "*/5 * * * * *" --text "执行内容"';
|
|
164
|
+
}
|
|
165
|
+
if (!currentSessionId && !parsedArgs.options.session) {
|
|
166
|
+
return '❌ 当前聊天尚未绑定 OpenCode 会话;请先在当前窗口建立/绑定会话,或显式使用 --session <id>。';
|
|
167
|
+
}
|
|
168
|
+
if (!currentConversationId) {
|
|
169
|
+
return '❌ 当前聊天上下文缺失,无法为 Cron 任务绑定回推窗口。';
|
|
170
|
+
}
|
|
171
|
+
const name = resolveName(intent, parsedArgs, text);
|
|
172
|
+
const payload = buildPayload(intent, parsedArgs, text, {
|
|
173
|
+
currentSessionId,
|
|
174
|
+
currentDirectory,
|
|
175
|
+
currentConversationId,
|
|
176
|
+
creatorId,
|
|
177
|
+
platform,
|
|
178
|
+
});
|
|
179
|
+
const enabled = resolveEnabled(parsedArgs, true);
|
|
180
|
+
try {
|
|
181
|
+
const created = manager.addJob({
|
|
182
|
+
name,
|
|
183
|
+
schedule: { kind: 'cron', expr },
|
|
184
|
+
payload,
|
|
185
|
+
enabled,
|
|
186
|
+
});
|
|
187
|
+
return [
|
|
188
|
+
'✅ Cron 任务创建成功',
|
|
189
|
+
`- id: ${created.id}`,
|
|
190
|
+
`- name: ${created.name}`,
|
|
191
|
+
`- expr: ${created.schedule.expr}`,
|
|
192
|
+
`- enabled: ${created.enabled}`,
|
|
193
|
+
`- store: ${reliabilityConfig.cronJobsFile || '~/cron/jobs.json'}`,
|
|
194
|
+
].join('\n');
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return `❌ 创建任务失败: ${formatError(error)}`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (intent.action === 'update') {
|
|
201
|
+
const id = resolveJobId(intent, parsedArgs);
|
|
202
|
+
if (!id) {
|
|
203
|
+
return '❌ 更新任务缺少 ID。用法:update --id <jobId> [--expr ...] [--text ...] [--enabled true|false]';
|
|
204
|
+
}
|
|
205
|
+
const existing = manager.listJobs().find(item => item.id === id);
|
|
206
|
+
if (!existing) {
|
|
207
|
+
return `❌ 未找到任务: ${id}`;
|
|
208
|
+
}
|
|
209
|
+
const nextSchedule = resolveExpr(intent, parsedArgs);
|
|
210
|
+
const nextText = resolveText(intent, parsedArgs);
|
|
211
|
+
const nextName = resolveName(intent, parsedArgs, existing.payload.text, true);
|
|
212
|
+
const nextEnabled = resolveEnabled(parsedArgs, existing.enabled);
|
|
213
|
+
const hasPayloadChange = Boolean(nextText
|
|
214
|
+
|| parsedArgs.options.session
|
|
215
|
+
|| parsedArgs.options.dir
|
|
216
|
+
|| parsedArgs.options.agent
|
|
217
|
+
|| parsedArgs.options.chat);
|
|
218
|
+
const nextConversationId = resolveOptionalValue(parsedArgs.options.chat, currentConversationId);
|
|
219
|
+
const updatePayload = {
|
|
220
|
+
...(nextText ? { text: nextText } : {}),
|
|
221
|
+
...(resolveOptionalValue(parsedArgs.options.session, currentSessionId) !== undefined
|
|
222
|
+
? { sessionId: resolveOptionalValue(parsedArgs.options.session, currentSessionId) }
|
|
223
|
+
: {}),
|
|
224
|
+
...(resolveOptionalValue(parsedArgs.options.dir, currentDirectory) !== undefined
|
|
225
|
+
? { directory: resolveOptionalValue(parsedArgs.options.dir, currentDirectory) }
|
|
226
|
+
: {}),
|
|
227
|
+
...(resolveOptionalValue(parsedArgs.options.agent, undefined) !== undefined
|
|
228
|
+
? { agent: resolveOptionalValue(parsedArgs.options.agent, undefined) }
|
|
229
|
+
: {}),
|
|
230
|
+
};
|
|
231
|
+
try {
|
|
232
|
+
manager.updateJob({
|
|
233
|
+
id,
|
|
234
|
+
...(nextName ? { name: nextName } : {}),
|
|
235
|
+
...(nextSchedule ? { schedule: { kind: 'cron', expr: nextSchedule } } : {}),
|
|
236
|
+
...(hasPayloadChange
|
|
237
|
+
? {
|
|
238
|
+
payload: {
|
|
239
|
+
kind: 'systemEvent',
|
|
240
|
+
text: updatePayload.text || existing.payload.text,
|
|
241
|
+
...(updatePayload.sessionId !== undefined
|
|
242
|
+
? (updatePayload.sessionId ? { sessionId: updatePayload.sessionId } : {})
|
|
243
|
+
: (existing.payload.sessionId ? { sessionId: existing.payload.sessionId } : {})),
|
|
244
|
+
...(updatePayload.directory !== undefined
|
|
245
|
+
? (updatePayload.directory ? { directory: updatePayload.directory } : {})
|
|
246
|
+
: (existing.payload.directory ? { directory: existing.payload.directory } : {})),
|
|
247
|
+
...(updatePayload.agent !== undefined
|
|
248
|
+
? (updatePayload.agent ? { agent: updatePayload.agent } : {})
|
|
249
|
+
: (existing.payload.agent ? { agent: existing.payload.agent } : {})),
|
|
250
|
+
...(nextConversationId !== undefined
|
|
251
|
+
? {
|
|
252
|
+
delivery: {
|
|
253
|
+
platform,
|
|
254
|
+
conversationId: nextConversationId || currentConversationId || existing.payload.delivery?.conversationId || '',
|
|
255
|
+
...(creatorId || existing.payload.delivery?.creatorId
|
|
256
|
+
? { creatorId: creatorId || existing.payload.delivery?.creatorId }
|
|
257
|
+
: {}),
|
|
258
|
+
...(existing.payload.delivery?.fallbackConversationId
|
|
259
|
+
? { fallbackConversationId: existing.payload.delivery.fallbackConversationId }
|
|
260
|
+
: {}),
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
: (existing.payload.delivery ? { delivery: existing.payload.delivery } : {})),
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
: {}),
|
|
267
|
+
...(nextEnabled !== existing.enabled ? { enabled: nextEnabled } : {}),
|
|
268
|
+
});
|
|
269
|
+
return `✅ 已更新任务: ${id}`;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
return `❌ 更新任务失败: ${formatError(error)}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return buildCronHelpText(platform);
|
|
276
|
+
}
|
|
277
|
+
function normalizeAction(rawAction) {
|
|
278
|
+
const normalized = rawAction.trim().toLowerCase();
|
|
279
|
+
if (!normalized) {
|
|
280
|
+
return 'list';
|
|
281
|
+
}
|
|
282
|
+
if (normalized === 'list' || normalized === 'ls' || normalized === '列表') {
|
|
283
|
+
return 'list';
|
|
284
|
+
}
|
|
285
|
+
if (normalized === 'help' || normalized === 'h' || normalized === '帮助') {
|
|
286
|
+
return 'help';
|
|
287
|
+
}
|
|
288
|
+
if (normalized === 'add'
|
|
289
|
+
|| normalized === 'create'
|
|
290
|
+
|| normalized === '新增'
|
|
291
|
+
|| normalized === '添加'
|
|
292
|
+
|| normalized === '添加任务'
|
|
293
|
+
|| normalized === '添加定时任务'
|
|
294
|
+
|| normalized === '创建定时任务') {
|
|
295
|
+
return 'add';
|
|
296
|
+
}
|
|
297
|
+
if (normalized === 'update'
|
|
298
|
+
|| normalized === 'edit'
|
|
299
|
+
|| normalized === '修改'
|
|
300
|
+
|| normalized === '修改任务'
|
|
301
|
+
|| normalized === '更新'
|
|
302
|
+
|| normalized === '更新任务') {
|
|
303
|
+
return 'update';
|
|
304
|
+
}
|
|
305
|
+
if (normalized === 'remove' || normalized === 'delete' || normalized === '删除' || normalized === '删除任务') {
|
|
306
|
+
return 'remove';
|
|
307
|
+
}
|
|
308
|
+
if (normalized === 'pause' || normalized === 'disable' || normalized === '暂停' || normalized === '暂停任务') {
|
|
309
|
+
return 'pause';
|
|
310
|
+
}
|
|
311
|
+
if (normalized === 'resume' || normalized === 'enable' || normalized === '恢复' || normalized === '恢复任务') {
|
|
312
|
+
return 'resume';
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
function shouldUseSemanticParserForSlash(action, argsText) {
|
|
317
|
+
const trimmed = argsText.trim();
|
|
318
|
+
if (!trimmed) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
if (action === 'list') {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
if (action === 'add' || action === 'update') {
|
|
325
|
+
return !/^--/u.test(trimmed);
|
|
326
|
+
}
|
|
327
|
+
if (action === 'remove' || action === 'pause' || action === 'resume') {
|
|
328
|
+
if (/^--id\b/iu.test(trimmed)) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return /\s/u.test(trimmed);
|
|
332
|
+
}
|
|
333
|
+
// 显式参数命令(低歧义)直接走本地解析
|
|
334
|
+
if (/^add\s+--/i.test(trimmed) || /^update\s+--/i.test(trimmed)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
// list/help/remove/pause/resume 显式子命令优先本地解析
|
|
338
|
+
if (/^(?:list|ls|help|h|remove|delete|pause|resume|列表|查看|帮助|删除|删除任务|暂停|暂停任务|恢复|恢复任务)(?:\s|$)/iu.test(trimmed)) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
// 其余 slash 文本(例如“添加个定时任务...”)走语义解析
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
function buildFallbackBodyForLocalParser(action, argsText) {
|
|
345
|
+
const trimmed = argsText.trim();
|
|
346
|
+
if (!trimmed) {
|
|
347
|
+
if (action === 'list') {
|
|
348
|
+
return 'list';
|
|
349
|
+
}
|
|
350
|
+
return '';
|
|
351
|
+
}
|
|
352
|
+
if (action === 'remove' || action === 'pause' || action === 'resume') {
|
|
353
|
+
return `${action} ${trimmed}`;
|
|
354
|
+
}
|
|
355
|
+
return trimmed;
|
|
356
|
+
}
|
|
357
|
+
function parseCronBodyIntent(bodyText, source) {
|
|
358
|
+
const body = bodyText.trim();
|
|
359
|
+
if (!body) {
|
|
360
|
+
return {
|
|
361
|
+
action: 'help',
|
|
362
|
+
source,
|
|
363
|
+
argsText: '',
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (/^(help|帮助|说明)$/iu.test(body)) {
|
|
367
|
+
return {
|
|
368
|
+
action: 'help',
|
|
369
|
+
source,
|
|
370
|
+
argsText: '',
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (/^(list|列表|查看)$/iu.test(body)) {
|
|
374
|
+
return {
|
|
375
|
+
action: 'list',
|
|
376
|
+
source,
|
|
377
|
+
argsText: '',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const removeMatch = body.match(/^(?:删除任务|删除|移除|remove)\s+(.+)$/iu);
|
|
381
|
+
if (removeMatch) {
|
|
382
|
+
return {
|
|
383
|
+
action: 'remove',
|
|
384
|
+
source,
|
|
385
|
+
argsText: '',
|
|
386
|
+
preset: { id: removeMatch[1].trim() },
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const pauseMatch = body.match(/^(?:暂停任务|暂停|停用|pause)\s+(.+)$/iu);
|
|
390
|
+
if (pauseMatch) {
|
|
391
|
+
return {
|
|
392
|
+
action: 'pause',
|
|
393
|
+
source,
|
|
394
|
+
argsText: '',
|
|
395
|
+
preset: { id: pauseMatch[1].trim() },
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const resumeMatch = body.match(/^(?:恢复任务|恢复|启用|resume)\s+(.+)$/iu);
|
|
399
|
+
if (resumeMatch) {
|
|
400
|
+
return {
|
|
401
|
+
action: 'resume',
|
|
402
|
+
source,
|
|
403
|
+
argsText: '',
|
|
404
|
+
preset: { id: resumeMatch[1].trim() },
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const normalizedBody = stripCreateTaskPrefix(body);
|
|
408
|
+
const everyMinutesMatch = normalizedBody.match(/^(?:每|每隔)\s*(\d{1,2})\s*分钟\s*(?:执行|做|处理)?\s*(.+)$/u);
|
|
409
|
+
if (everyMinutesMatch) {
|
|
410
|
+
const minutes = Number.parseInt(everyMinutesMatch[1], 10);
|
|
411
|
+
if (minutes < 1 || minutes > 59) {
|
|
412
|
+
return {
|
|
413
|
+
action: 'help',
|
|
414
|
+
source,
|
|
415
|
+
argsText: '',
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const textValue = everyMinutesMatch[2].trim();
|
|
419
|
+
if (!textValue) {
|
|
420
|
+
return {
|
|
421
|
+
action: 'help',
|
|
422
|
+
source,
|
|
423
|
+
argsText: body,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
action: 'add',
|
|
428
|
+
source,
|
|
429
|
+
argsText: '',
|
|
430
|
+
preset: {
|
|
431
|
+
expr: `0 */${minutes} * * * *`,
|
|
432
|
+
text: textValue,
|
|
433
|
+
name: buildNaturalJobName(textValue),
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const everyHoursMatch = normalizedBody.match(/^(?:每|每隔)\s*(\d{1,2})\s*小时\s*(?:执行|做|处理)?\s*(.+)$/u);
|
|
438
|
+
if (everyHoursMatch) {
|
|
439
|
+
const hours = Number.parseInt(everyHoursMatch[1], 10);
|
|
440
|
+
if (hours < 1 || hours > 23) {
|
|
441
|
+
return {
|
|
442
|
+
action: 'help',
|
|
443
|
+
source,
|
|
444
|
+
argsText: '',
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const textValue = everyHoursMatch[2].trim();
|
|
448
|
+
if (!textValue) {
|
|
449
|
+
return {
|
|
450
|
+
action: 'help',
|
|
451
|
+
source,
|
|
452
|
+
argsText: body,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
action: 'add',
|
|
457
|
+
source,
|
|
458
|
+
argsText: '',
|
|
459
|
+
preset: {
|
|
460
|
+
expr: `0 0 */${hours} * * *`,
|
|
461
|
+
text: textValue,
|
|
462
|
+
name: buildNaturalJobName(textValue),
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
const dailyMatch = normalizedBody.match(/^每天\s*(\d{1,2})[::](\d{2})\s*(?:执行|做|处理)?\s*(.+)$/u);
|
|
467
|
+
if (dailyMatch) {
|
|
468
|
+
const hour = Number.parseInt(dailyMatch[1], 10);
|
|
469
|
+
const minute = Number.parseInt(dailyMatch[2], 10);
|
|
470
|
+
if (!isValidHourMinute(hour, minute)) {
|
|
471
|
+
return {
|
|
472
|
+
action: 'help',
|
|
473
|
+
source,
|
|
474
|
+
argsText: '',
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const textValue = dailyMatch[3].trim();
|
|
478
|
+
if (!textValue) {
|
|
479
|
+
return {
|
|
480
|
+
action: 'help',
|
|
481
|
+
source,
|
|
482
|
+
argsText: body,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
action: 'add',
|
|
487
|
+
source,
|
|
488
|
+
argsText: '',
|
|
489
|
+
preset: {
|
|
490
|
+
expr: `0 ${minute} ${hour} * * *`,
|
|
491
|
+
text: textValue,
|
|
492
|
+
name: buildNaturalJobName(textValue),
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const dailyChineseMatch = normalizedBody.match(/^每天\s*(早上|上午|中午|下午|晚上)?\s*(\d{1,2})(?:[::](\d{1,2})|点(?:(\d{1,2})分?)?)?\s*(?:执行|做|处理)?\s*(.+)$/u);
|
|
497
|
+
if (dailyChineseMatch) {
|
|
498
|
+
const period = dailyChineseMatch[1]?.trim();
|
|
499
|
+
const baseHour = Number.parseInt(dailyChineseMatch[2], 10);
|
|
500
|
+
const minuteRaw = dailyChineseMatch[3] ?? dailyChineseMatch[4] ?? '0';
|
|
501
|
+
const minute = Number.parseInt(minuteRaw, 10);
|
|
502
|
+
const hour = normalizeHourByPeriod(baseHour, period);
|
|
503
|
+
if (!isValidHourMinute(hour, minute)) {
|
|
504
|
+
return {
|
|
505
|
+
action: 'help',
|
|
506
|
+
source,
|
|
507
|
+
argsText: '',
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
const rawTextValue = dailyChineseMatch[5].trim();
|
|
511
|
+
const textValue = stripActionPrefix(rawTextValue);
|
|
512
|
+
if (!textValue) {
|
|
513
|
+
return {
|
|
514
|
+
action: 'help',
|
|
515
|
+
source,
|
|
516
|
+
argsText: body,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
action: 'add',
|
|
521
|
+
source,
|
|
522
|
+
argsText: '',
|
|
523
|
+
preset: {
|
|
524
|
+
expr: `0 ${minute} ${hour} * * *`,
|
|
525
|
+
text: textValue,
|
|
526
|
+
name: buildNaturalJobName(textValue),
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
const weeklyMatch = normalizedBody.match(/^每周([一二三四五六日天])\s*(\d{1,2})[::](\d{2})\s*(?:执行|做|处理)?\s*(.+)$/u);
|
|
531
|
+
if (weeklyMatch) {
|
|
532
|
+
const weekday = WEEKDAY_MAP[weeklyMatch[1]];
|
|
533
|
+
const hour = Number.parseInt(weeklyMatch[2], 10);
|
|
534
|
+
const minute = Number.parseInt(weeklyMatch[3], 10);
|
|
535
|
+
if (typeof weekday !== 'number' || !isValidHourMinute(hour, minute)) {
|
|
536
|
+
return {
|
|
537
|
+
action: 'help',
|
|
538
|
+
source,
|
|
539
|
+
argsText: '',
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const textValue = weeklyMatch[4].trim();
|
|
543
|
+
if (!textValue) {
|
|
544
|
+
return {
|
|
545
|
+
action: 'help',
|
|
546
|
+
source,
|
|
547
|
+
argsText: body,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
action: 'add',
|
|
552
|
+
source,
|
|
553
|
+
argsText: '',
|
|
554
|
+
preset: {
|
|
555
|
+
expr: `0 ${minute} ${hour} * * ${weekday}`,
|
|
556
|
+
text: textValue,
|
|
557
|
+
name: buildNaturalJobName(textValue),
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
action: 'help',
|
|
563
|
+
source,
|
|
564
|
+
argsText: body,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function buildNaturalJobName(text) {
|
|
568
|
+
const candidate = text.trim().slice(0, 24);
|
|
569
|
+
return candidate || `cron-${Date.now()}`;
|
|
570
|
+
}
|
|
571
|
+
function stripCreateTaskPrefix(text) {
|
|
572
|
+
return text
|
|
573
|
+
.replace(/^(?:请|请你|帮我)?\s*(?:添加|新增|创建)(?:一个|个)?\s*定时任务[,,:\s]*/u, '')
|
|
574
|
+
.trim();
|
|
575
|
+
}
|
|
576
|
+
function stripActionPrefix(text) {
|
|
577
|
+
return text
|
|
578
|
+
.replace(/^(?:执行|做|处理)\s*/u, '')
|
|
579
|
+
.trim();
|
|
580
|
+
}
|
|
581
|
+
function normalizeHourByPeriod(hour, period) {
|
|
582
|
+
if (!Number.isFinite(hour)) {
|
|
583
|
+
return hour;
|
|
584
|
+
}
|
|
585
|
+
const normalizedPeriod = (period || '').trim();
|
|
586
|
+
if (!normalizedPeriod) {
|
|
587
|
+
return hour;
|
|
588
|
+
}
|
|
589
|
+
if (normalizedPeriod === '早上' || normalizedPeriod === '上午') {
|
|
590
|
+
return hour === 12 ? 0 : hour;
|
|
591
|
+
}
|
|
592
|
+
if (normalizedPeriod === '中午') {
|
|
593
|
+
if (hour >= 1 && hour <= 10) {
|
|
594
|
+
return hour + 12;
|
|
595
|
+
}
|
|
596
|
+
return hour;
|
|
597
|
+
}
|
|
598
|
+
if (normalizedPeriod === '下午' || normalizedPeriod === '晚上') {
|
|
599
|
+
if (hour >= 1 && hour <= 11) {
|
|
600
|
+
return hour + 12;
|
|
601
|
+
}
|
|
602
|
+
return hour;
|
|
603
|
+
}
|
|
604
|
+
return hour;
|
|
605
|
+
}
|
|
606
|
+
function isValidHourMinute(hour, minute) {
|
|
607
|
+
return Number.isInteger(hour)
|
|
608
|
+
&& Number.isInteger(minute)
|
|
609
|
+
&& hour >= 0
|
|
610
|
+
&& hour <= 23
|
|
611
|
+
&& minute >= 0
|
|
612
|
+
&& minute <= 59;
|
|
613
|
+
}
|
|
614
|
+
function parseOptionArgs(input) {
|
|
615
|
+
const tokens = tokenize(input);
|
|
616
|
+
const options = {};
|
|
617
|
+
const flags = new Set();
|
|
618
|
+
const positionals = [];
|
|
619
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
620
|
+
const token = tokens[index];
|
|
621
|
+
if (!token.startsWith('--')) {
|
|
622
|
+
positionals.push(token);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
const item = token.slice(2);
|
|
626
|
+
if (!item) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
const equalIndex = item.indexOf('=');
|
|
630
|
+
if (equalIndex >= 0) {
|
|
631
|
+
const key = item.slice(0, equalIndex).trim().toLowerCase();
|
|
632
|
+
const value = item.slice(equalIndex + 1).trim();
|
|
633
|
+
if (key) {
|
|
634
|
+
options[key] = value;
|
|
635
|
+
}
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const key = item.trim().toLowerCase();
|
|
639
|
+
const nextToken = tokens[index + 1];
|
|
640
|
+
if (nextToken && !nextToken.startsWith('--')) {
|
|
641
|
+
options[key] = nextToken;
|
|
642
|
+
index += 1;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
flags.add(key);
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
options,
|
|
649
|
+
flags,
|
|
650
|
+
positionals,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function tokenize(input) {
|
|
654
|
+
const tokens = [];
|
|
655
|
+
const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|`([^`\\]*(?:\\.[^`\\]*)*)`|(\S+)/g;
|
|
656
|
+
let matched = regex.exec(input);
|
|
657
|
+
while (matched !== null) {
|
|
658
|
+
const value = matched[1] ?? matched[2] ?? matched[3] ?? matched[4] ?? '';
|
|
659
|
+
if (value) {
|
|
660
|
+
tokens.push(unescapeToken(value));
|
|
661
|
+
}
|
|
662
|
+
matched = regex.exec(input);
|
|
663
|
+
}
|
|
664
|
+
return tokens;
|
|
665
|
+
}
|
|
666
|
+
function unescapeToken(value) {
|
|
667
|
+
return value
|
|
668
|
+
.replace(/\\"/g, '"')
|
|
669
|
+
.replace(/\\'/g, "'")
|
|
670
|
+
.replace(/\\`/g, '`')
|
|
671
|
+
.replace(/\\\\/g, '\\');
|
|
672
|
+
}
|
|
673
|
+
function resolveJobId(intent, args) {
|
|
674
|
+
const fromPreset = intent.preset?.id?.trim();
|
|
675
|
+
if (fromPreset) {
|
|
676
|
+
return fromPreset;
|
|
677
|
+
}
|
|
678
|
+
const fromOption = args.options.id?.trim();
|
|
679
|
+
if (fromOption) {
|
|
680
|
+
return fromOption;
|
|
681
|
+
}
|
|
682
|
+
return args.positionals[0]?.trim() || '';
|
|
683
|
+
}
|
|
684
|
+
function resolveExpr(intent, args) {
|
|
685
|
+
const fromPreset = intent.preset?.expr?.trim();
|
|
686
|
+
if (fromPreset) {
|
|
687
|
+
return fromPreset;
|
|
688
|
+
}
|
|
689
|
+
const fromOption = args.options.expr?.trim();
|
|
690
|
+
if (fromOption) {
|
|
691
|
+
return fromOption;
|
|
692
|
+
}
|
|
693
|
+
return '';
|
|
694
|
+
}
|
|
695
|
+
function resolveText(intent, args) {
|
|
696
|
+
const fromPreset = intent.preset?.text?.trim();
|
|
697
|
+
if (fromPreset) {
|
|
698
|
+
return fromPreset;
|
|
699
|
+
}
|
|
700
|
+
const fromOption = args.options.text?.trim();
|
|
701
|
+
if (fromOption) {
|
|
702
|
+
return fromOption;
|
|
703
|
+
}
|
|
704
|
+
return '';
|
|
705
|
+
}
|
|
706
|
+
function resolveName(intent, args, fallbackText, keepEmpty = false) {
|
|
707
|
+
const fromPreset = intent.preset?.name?.trim();
|
|
708
|
+
if (fromPreset) {
|
|
709
|
+
return fromPreset;
|
|
710
|
+
}
|
|
711
|
+
const fromOption = args.options.name?.trim();
|
|
712
|
+
if (fromOption) {
|
|
713
|
+
return fromOption;
|
|
714
|
+
}
|
|
715
|
+
if (keepEmpty) {
|
|
716
|
+
return '';
|
|
717
|
+
}
|
|
718
|
+
return buildNaturalJobName(fallbackText);
|
|
719
|
+
}
|
|
720
|
+
function resolveEnabled(args, fallback) {
|
|
721
|
+
if ('enabled' in args.options) {
|
|
722
|
+
return parseBooleanLike(args.options.enabled, fallback);
|
|
723
|
+
}
|
|
724
|
+
if (args.flags.has('enabled')) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
if (args.flags.has('disabled')) {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
return fallback;
|
|
731
|
+
}
|
|
732
|
+
function parseBooleanLike(value, fallback) {
|
|
733
|
+
const normalized = value.trim().toLowerCase();
|
|
734
|
+
if (['1', 'true', 'yes', 'y', 'on', 'enable', 'enabled'].includes(normalized)) {
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
if (['0', 'false', 'no', 'n', 'off', 'disable', 'disabled'].includes(normalized)) {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
return fallback;
|
|
741
|
+
}
|
|
742
|
+
function resolveOptionalValue(raw, currentValue) {
|
|
743
|
+
if (raw === undefined) {
|
|
744
|
+
return undefined;
|
|
745
|
+
}
|
|
746
|
+
const trimmed = raw.trim();
|
|
747
|
+
if (!trimmed) {
|
|
748
|
+
return '';
|
|
749
|
+
}
|
|
750
|
+
if (trimmed.toLowerCase() === 'current') {
|
|
751
|
+
return currentValue || '';
|
|
752
|
+
}
|
|
753
|
+
return trimmed;
|
|
754
|
+
}
|
|
755
|
+
function buildPayload(intent, args, text, context) {
|
|
756
|
+
const optionSession = resolveOptionalValue(args.options.session, context.currentSessionId);
|
|
757
|
+
const optionDirectory = resolveOptionalValue(args.options.dir, context.currentDirectory);
|
|
758
|
+
const optionAgent = resolveOptionalValue(args.options.agent, undefined);
|
|
759
|
+
const optionConversationId = args.options.chat !== undefined
|
|
760
|
+
? resolveOptionalValue(args.options.chat, context.currentConversationId)
|
|
761
|
+
: (context.currentConversationId || '');
|
|
762
|
+
const presetSession = intent.preset?.id;
|
|
763
|
+
const sessionId = optionSession !== undefined ? optionSession : presetSession;
|
|
764
|
+
return {
|
|
765
|
+
kind: 'systemEvent',
|
|
766
|
+
text,
|
|
767
|
+
...(sessionId ? { sessionId } : {}),
|
|
768
|
+
...(optionDirectory ? { directory: optionDirectory } : {}),
|
|
769
|
+
...(optionAgent ? { agent: optionAgent } : {}),
|
|
770
|
+
...(optionConversationId
|
|
771
|
+
? {
|
|
772
|
+
delivery: {
|
|
773
|
+
platform: context.platform,
|
|
774
|
+
conversationId: optionConversationId,
|
|
775
|
+
...(context.creatorId ? { creatorId: context.creatorId } : {}),
|
|
776
|
+
},
|
|
777
|
+
}
|
|
778
|
+
: {}),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function formatError(error) {
|
|
782
|
+
return error instanceof Error ? error.message : String(error);
|
|
783
|
+
}
|
|
784
|
+
function brief(text, maxLength) {
|
|
785
|
+
if (text.length <= maxLength) {
|
|
786
|
+
return text;
|
|
787
|
+
}
|
|
788
|
+
return `${text.slice(0, maxLength - 1)}…`;
|
|
789
|
+
}
|
|
790
|
+
function buildRuntimeCronDisplayStatus(job) {
|
|
791
|
+
const sessionId = job.payload.sessionId?.trim();
|
|
792
|
+
const delivery = job.payload.delivery;
|
|
793
|
+
if (!delivery) {
|
|
794
|
+
return {
|
|
795
|
+
targetWindow: '未绑定窗口',
|
|
796
|
+
orphanStatus: '是(未绑定目标窗口)',
|
|
797
|
+
fallbackStatus: resolveRuntimeCronFallbackStatus(job),
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const targetLabel = `${delivery.platform}:${delivery.conversationId}`;
|
|
801
|
+
if (!sessionId) {
|
|
802
|
+
return {
|
|
803
|
+
targetWindow: `${targetLabel}(未绑定会话)`,
|
|
804
|
+
orphanStatus: '是(未绑定 OpenCode 会话)',
|
|
805
|
+
fallbackStatus: resolveRuntimeCronFallbackStatus(job),
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const currentBinding = chatSessionStore.getSessionByConversation(delivery.platform, delivery.conversationId);
|
|
809
|
+
const boundConversation = chatSessionStore.getConversationBySessionId(sessionId);
|
|
810
|
+
if (boundConversation
|
|
811
|
+
&& (boundConversation.platform !== delivery.platform || boundConversation.conversationId !== delivery.conversationId)) {
|
|
812
|
+
return {
|
|
813
|
+
targetWindow: `${targetLabel}(原会话已迁移到 ${boundConversation.platform}:${boundConversation.conversationId})`,
|
|
814
|
+
orphanStatus: '是(原会话已迁移到其他窗口)',
|
|
815
|
+
fallbackStatus: `${resolveRuntimeCronFallbackStatus(job)};原会话已迁移,运行时不会直接回退`,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (!currentBinding) {
|
|
819
|
+
return {
|
|
820
|
+
targetWindow: `${targetLabel}(本地未绑定/已失联)`,
|
|
821
|
+
orphanStatus: '是(目标窗口未绑定或已失联)',
|
|
822
|
+
fallbackStatus: resolveRuntimeCronFallbackStatus(job),
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
if (currentBinding.sessionId !== sessionId) {
|
|
826
|
+
return {
|
|
827
|
+
targetWindow: `${targetLabel}(已改绑到 ${currentBinding.sessionId})`,
|
|
828
|
+
orphanStatus: '是(目标窗口已改绑到其他会话)',
|
|
829
|
+
fallbackStatus: resolveRuntimeCronFallbackStatus(job),
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
targetWindow: `${targetLabel}(本地绑定有效)`,
|
|
834
|
+
orphanStatus: '否',
|
|
835
|
+
fallbackStatus: resolveRuntimeCronFallbackStatus(job),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function resolveRuntimeCronFallbackStatus(job) {
|
|
839
|
+
if (!reliabilityConfig.cronForwardToPrivateChat) {
|
|
840
|
+
return '关闭';
|
|
841
|
+
}
|
|
842
|
+
const delivery = job.payload.delivery;
|
|
843
|
+
if (!delivery) {
|
|
844
|
+
return '已启用,但任务未绑定目标窗口';
|
|
845
|
+
}
|
|
846
|
+
if (delivery.fallbackConversationId) {
|
|
847
|
+
return `候选 ${delivery.platform}:${delivery.fallbackConversationId}(任务显式)`;
|
|
848
|
+
}
|
|
849
|
+
if (delivery.platform === 'feishu' && reliabilityConfig.cronFallbackFeishuChatId) {
|
|
850
|
+
return `候选 feishu:${reliabilityConfig.cronFallbackFeishuChatId}(env)`;
|
|
851
|
+
}
|
|
852
|
+
if (delivery.platform === 'discord' && reliabilityConfig.cronFallbackDiscordConversationId) {
|
|
853
|
+
return `候选 discord:${reliabilityConfig.cronFallbackDiscordConversationId}(env)`;
|
|
854
|
+
}
|
|
855
|
+
if (!delivery.creatorId) {
|
|
856
|
+
return '已启用,但未找到可用回退目标';
|
|
857
|
+
}
|
|
858
|
+
const privateBinding = chatSessionStore.findPrivateConversationByCreator(delivery.creatorId, delivery.platform);
|
|
859
|
+
if (!privateBinding) {
|
|
860
|
+
return '已启用,但未找到创建者私聊';
|
|
861
|
+
}
|
|
862
|
+
return `候选 ${privateBinding.platform}:${privateBinding.conversationId}(创建者私聊)`;
|
|
863
|
+
}
|
|
864
|
+
//# sourceMappingURL=cron-control.js.map
|