@goodandready/dsh-cron 0.1.5 → 0.1.7
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/lib/chat-start.js +62 -0
- package/lib/client.js +262 -101
- package/lib/index.js +35 -13
- package/lib/models-handler.js +32 -0
- package/lib/prompt.js +17 -0
- package/lib/runner.js +46 -2
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export async function chatStartHandler(ctx, req, res, parseJsonBody, sendJson, randomUUID) {
|
|
2
|
+
try {
|
|
3
|
+
if (req.method !== 'POST') {
|
|
4
|
+
sendJson(res, 405, { ok: false, error: 'Method not allowed' });
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const body = await parseJsonBody(req);
|
|
9
|
+
const userPrompt = (body.prompt || '').trim();
|
|
10
|
+
if (!userPrompt) {
|
|
11
|
+
sendJson(res, 400, { ok: false, error: 'Текст задачи не может быть пустым' });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const agents = ctx.agents;
|
|
16
|
+
if (!agents || typeof agents.create !== 'function') {
|
|
17
|
+
sendJson(res, 500, { ok: false, error: 'Служба agents недоступна в DSH' });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let createUserMessage = (m) => m;
|
|
22
|
+
try {
|
|
23
|
+
const llmModule = await import('@deepseek-ai/dsh-llm');
|
|
24
|
+
if (llmModule && typeof llmModule.createUserMessage === 'function') {
|
|
25
|
+
createUserMessage = llmModule.createUserMessage;
|
|
26
|
+
}
|
|
27
|
+
} catch {}
|
|
28
|
+
|
|
29
|
+
let mintSessionId = () => `cron-setup-${randomUUID()}`;
|
|
30
|
+
try {
|
|
31
|
+
const sessionModule = await import('@deepseek-ai/dsh-session');
|
|
32
|
+
if (sessionModule && typeof sessionModule.SessionId === 'function') {
|
|
33
|
+
const sid = mintSessionId();
|
|
34
|
+
mintSessionId = () => sessionModule.SessionId(sid);
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
const defaultSel = ctx.get('agentDefaultModel')?.currentSelection?.();
|
|
39
|
+
const provider = body.provider || defaultSel?.provider;
|
|
40
|
+
const model = body.model || defaultSel?.model;
|
|
41
|
+
|
|
42
|
+
const handle = await agents.create({
|
|
43
|
+
sessionId: mintSessionId(),
|
|
44
|
+
meta: {},
|
|
45
|
+
agentOptions: provider && model ? { provider, model } : undefined,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const { buildAgentCronPrompt } = await import('./prompt.js');
|
|
49
|
+
const fullPrompt = buildAgentCronPrompt(userPrompt);
|
|
50
|
+
|
|
51
|
+
await handle.agent.whenIdle();
|
|
52
|
+
handle.agent.followup(createUserMessage({
|
|
53
|
+
content: [{ type: 'text', text: fullPrompt }],
|
|
54
|
+
source: { kind: 'plugin', plugin: 'dsh-cron', form: 'cron-setup' },
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
sendJson(res, 200, { ok: true, sessionId: handle.agent.session.id });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
ctx.logger?.warn?.(`[dsh-cron] chat/start error: ${err.message}`);
|
|
60
|
+
sendJson(res, 500, { ok: false, error: err.message });
|
|
61
|
+
}
|
|
62
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({
|
|
2
2
|
id: '@goodandready/dsh-cron',
|
|
3
3
|
factory: (require) => {
|
|
4
|
-
var module = { exports: {} }
|
|
5
|
-
var exports = module.exports
|
|
6
|
-
|
|
7
|
-
const ReactDOM = require('react-dom/client');
|
|
4
|
+
var module = { exports: {} }
|
|
5
|
+
var exports = module.exports
|
|
6
|
+
const React = window.React || require('react');
|
|
7
|
+
const ReactDOM = window.ReactDOM || require('react-dom/client');
|
|
8
8
|
|
|
9
|
-
const NS = '
|
|
9
|
+
const NS = 'cron';
|
|
10
10
|
const PANEL = 'cron';
|
|
11
11
|
const ACTIVATE_EVENT = 'dsh-panel-activate';
|
|
12
12
|
const ACTIVE_ATTR = 'data-dsh-cron-active';
|
|
@@ -19,22 +19,35 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
const ICON_TIMER = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>';
|
|
20
20
|
const ICON_PLAY = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 21"></polygon></svg>';
|
|
21
21
|
const ICON_PAUSE = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>';
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
22
|
+
const ICON_TRASH = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>';
|
|
23
|
+
const ICON_SEARCH = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
|
|
24
|
+
const ICON_CHEVRON_DOWN = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
25
|
+
const ICON_BELL = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>';
|
|
26
|
+
const ICON_CLIPBOARD = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>';
|
|
27
|
+
const ICON_ACTIVITY = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>';
|
|
28
|
+
const ICON_SEND = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>';
|
|
27
29
|
|
|
28
30
|
const STYLES = `
|
|
29
|
-
[data-pane=conversation],[class*=centerCol]{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
[data-pane=conversation], [class*=centerCol] {
|
|
32
|
+
position: relative !important;
|
|
33
|
+
}
|
|
34
|
+
[${VIEW_ATTR}] {
|
|
35
|
+
z-index: 60;
|
|
36
|
+
background: var(--dsw-alias-bg-base, #171717);
|
|
37
|
+
display: none;
|
|
38
|
+
position: absolute;
|
|
39
|
+
inset: 0;
|
|
40
|
+
}
|
|
41
|
+
html[${ACTIVE_ATTR}] [${VIEW_ATTR}] {
|
|
42
|
+
display: block !important;
|
|
43
|
+
}
|
|
44
|
+
html[${ACTIVE_ATTR}] [data-pane=conversation] > *:not([${VIEW_ATTR}]),
|
|
45
|
+
html[${ACTIVE_ATTR}] [class*=centerCol] > *:not([${VIEW_ATTR}]) {
|
|
46
|
+
display: none !important;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.dsh-cron-entry-clone { display: flex; align-items: center; gap: 8px; }
|
|
50
|
+
.dsh-cron-entry-icon { display: flex; align-items: center; justify-content: center; flex: none; width: 16px; height: 16px; }
|
|
38
51
|
.dsh-cron-entry-label { text-overflow: ellipsis; overflow: hidden; }
|
|
39
52
|
[data-dsh-frame][data-sidebar-collapsed] .dsh-cron-entry-label { display: none; }
|
|
40
53
|
|
|
@@ -67,6 +80,7 @@ window.__ModuleLoader__.load({
|
|
|
67
80
|
.dsh-cron-task-info { flex: 1; min-width: 0; }
|
|
68
81
|
.dsh-cron-task-title { font-size: 15px; font-weight: 500; margin-bottom: 3px; }
|
|
69
82
|
.dsh-cron-task-sched { font-size: 13px; color: #9ca3af; }
|
|
83
|
+
.dsh-cron-task-model-tag { display: inline-block; font-size: 11px; padding: 2px 7px; border-radius: 4px; background: rgba(255,255,255,0.06); color: #aaa; margin-left: 8px; vertical-align: middle; }
|
|
70
84
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
71
85
|
.dsh-cron-icon-btn { background: transparent; border: 1px solid #333; border-radius: 8px; padding: 6px 12px; font-size: 12.5px; color: #aaa; cursor: pointer; }
|
|
72
86
|
.dsh-cron-icon-btn:hover { background: #2a2a2a; color: #fff; }
|
|
@@ -81,15 +95,25 @@ window.__ModuleLoader__.load({
|
|
|
81
95
|
.dsh-cron-rec-desc { font-size: 13px; color: #71717a; }
|
|
82
96
|
|
|
83
97
|
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
|
84
|
-
.dsh-cron-modal { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 16px; width: 100%; max-width:
|
|
98
|
+
.dsh-cron-modal { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 16px; width: 100%; max-width: 540px; padding: 24px; box-shadow: 0 20px 40px rgba(0,0,0,0.6); box-sizing: border-box; }
|
|
85
99
|
.dsh-cron-modal-title { font-size: 18px; font-weight: 600; margin-bottom: 18px; }
|
|
86
100
|
.dsh-cron-form-group { margin-bottom: 16px; }
|
|
87
101
|
.dsh-cron-form-group label { display: block; font-size: 13px; color: #aaa; margin-bottom: 6px; }
|
|
88
|
-
.dsh-cron-form-group input, .dsh-cron-form-group textarea { width: 100%; background: #141414; border: 1px solid #333; border-radius: 8px; padding: 9px 12px; color: #fff; font-size: 13.5px; outline: none; box-sizing: border-box; }
|
|
102
|
+
.dsh-cron-form-group input, .dsh-cron-form-group textarea, .dsh-cron-form-group select { width: 100%; background: #141414; border: 1px solid #333; border-radius: 8px; padding: 9px 12px; color: #fff; font-size: 13.5px; outline: none; box-sizing: border-box; }
|
|
103
|
+
.dsh-cron-form-group select { cursor: pointer; }
|
|
89
104
|
.dsh-cron-form-group textarea { min-height: 80px; resize: vertical; }
|
|
90
|
-
.dsh-cron-
|
|
91
|
-
.dsh-cron-
|
|
105
|
+
.dsh-cron-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
106
|
+
.dsh-cron-modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 20px; }
|
|
107
|
+
.dsh-cron-btn-primary { background: #ededed; color: #111; border: none; border-radius: 8px; padding: 7px 16px; font-size: 13.5px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
108
|
+
.dsh-cron-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
92
109
|
.dsh-cron-btn-secondary { background: transparent; color: #999; border: 1px solid #383838; border-radius: 8px; padding: 7px 16px; font-size: 13.5px; cursor: pointer; }
|
|
110
|
+
.dsh-cron-chat-box { position: relative; background: #141414; border: 1px solid #333; border-radius: 12px; padding: 12px; }
|
|
111
|
+
.dsh-cron-chat-input { width: 100%; background: transparent; border: none; color: #fff; font-size: 14px; min-height: 100px; resize: vertical; outline: none; box-sizing: border-box; line-height: 1.5; }
|
|
112
|
+
.dsh-cron-chat-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding-top: 8px; border-top: 1px solid #222; }
|
|
113
|
+
.dsh-cron-chat-hint { font-size: 12px; color: #666; }
|
|
114
|
+
.dsh-cron-send-btn { width: 34px; height: 34px; border-radius: 8px; background: #ededed; color: #111; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.1s, background 0.15s; }
|
|
115
|
+
.dsh-cron-send-btn:hover:not(:disabled) { background: #fff; transform: translateY(-1px); }
|
|
116
|
+
.dsh-cron-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
93
117
|
|
|
94
118
|
.dsh-cron-card { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 12px; padding: 18px 20px; margin-bottom: 16px; }
|
|
95
119
|
.dsh-cron-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
|
@@ -129,23 +153,16 @@ window.__ModuleLoader__.load({
|
|
|
129
153
|
};
|
|
130
154
|
}
|
|
131
155
|
|
|
132
|
-
function
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
sessions.create({}).then((sessionId) => {
|
|
142
|
-
if (sessions.open) sessions.open(sessionId);
|
|
143
|
-
}).catch(() => {});
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
156
|
+
function openSession(ctx, sessionId) {
|
|
157
|
+
if (!sessionId) return false;
|
|
158
|
+
const sessions = ctx && typeof ctx.get === 'function' ? ctx.get('sessions') : ctx?.sessions;
|
|
159
|
+
if (!sessions || typeof sessions.open !== 'function') return false;
|
|
160
|
+
try {
|
|
161
|
+
sessions.open(sessionId);
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
146
165
|
}
|
|
147
|
-
const btn = nativeButton();
|
|
148
|
-
if (btn) btn.click();
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
function CronScreen(props) {
|
|
@@ -155,21 +172,36 @@ window.__ModuleLoader__.load({
|
|
|
155
172
|
const [tasks, setTasks] = React.useState([]);
|
|
156
173
|
const [recs, setRecs] = React.useState([]);
|
|
157
174
|
const [dropdownOpen, setDropdownOpen] = React.useState(false);
|
|
158
|
-
|
|
175
|
+
|
|
176
|
+
// Manual modal state
|
|
177
|
+
const [manualModalOpen, setManualModalOpen] = React.useState(false);
|
|
159
178
|
const [formTitle, setFormTitle] = React.useState('');
|
|
160
179
|
const [formSchedule, setFormSchedule] = React.useState('');
|
|
161
180
|
const [formPrompt, setFormPrompt] = React.useState('');
|
|
181
|
+
const [formProvider, setFormProvider] = React.useState('');
|
|
182
|
+
const [formModel, setFormModel] = React.useState('');
|
|
183
|
+
|
|
184
|
+
// DSH dialog state
|
|
185
|
+
const [dshModalOpen, setDshModalOpen] = React.useState(false);
|
|
186
|
+
const [dshPrompt, setDshPrompt] = React.useState('');
|
|
187
|
+
const [dshLoading, setDshLoading] = React.useState(false);
|
|
188
|
+
|
|
189
|
+
// Models list state
|
|
190
|
+
const [providers, setProviders] = React.useState([]);
|
|
191
|
+
const [models, setModels] = React.useState([]);
|
|
192
|
+
const [defaultModelInfo, setDefaultModelInfo] = React.useState(null);
|
|
162
193
|
|
|
163
194
|
React.useEffect(() => {
|
|
164
195
|
const onKey = (e) => {
|
|
165
196
|
if (e.key !== 'Escape') return;
|
|
166
|
-
if (
|
|
197
|
+
if (manualModalOpen) { setManualModalOpen(false); return; }
|
|
198
|
+
if (dshModalOpen) { setDshModalOpen(false); return; }
|
|
167
199
|
if (dropdownOpen) { setDropdownOpen(false); return; }
|
|
168
200
|
if (onClose) onClose();
|
|
169
201
|
};
|
|
170
202
|
document.addEventListener('keydown', onKey);
|
|
171
203
|
return () => document.removeEventListener('keydown', onKey);
|
|
172
|
-
}, [
|
|
204
|
+
}, [manualModalOpen, dshModalOpen, dropdownOpen, onClose]);
|
|
173
205
|
|
|
174
206
|
const fetchTasks = async () => {
|
|
175
207
|
try {
|
|
@@ -182,7 +214,21 @@ window.__ModuleLoader__.load({
|
|
|
182
214
|
} catch (err) {}
|
|
183
215
|
};
|
|
184
216
|
|
|
217
|
+
const loadModels = async (providerToFetch) => {
|
|
218
|
+
try {
|
|
219
|
+
const url = '/dsh-cron/models' + (providerToFetch ? '?provider=' + encodeURIComponent(providerToFetch) : '');
|
|
220
|
+
const res = await fetch(url);
|
|
221
|
+
const data = await res.json();
|
|
222
|
+
if (data) {
|
|
223
|
+
if (data.providers) setProviders(data.providers);
|
|
224
|
+
if (data.models) setModels(data.models);
|
|
225
|
+
if (data.current) setDefaultModelInfo(data.current);
|
|
226
|
+
}
|
|
227
|
+
} catch (err) {}
|
|
228
|
+
};
|
|
229
|
+
|
|
185
230
|
React.useEffect(() => { fetchTasks(); }, [tab, query]);
|
|
231
|
+
React.useEffect(() => { loadModels(); }, []);
|
|
186
232
|
|
|
187
233
|
const handleToggleTask = async (id) => {
|
|
188
234
|
try {
|
|
@@ -207,21 +253,34 @@ window.__ModuleLoader__.load({
|
|
|
207
253
|
} catch (err) {}
|
|
208
254
|
};
|
|
209
255
|
|
|
210
|
-
const
|
|
256
|
+
const openManualModal = (preset) => {
|
|
211
257
|
setDropdownOpen(false);
|
|
212
258
|
if (preset) {
|
|
213
259
|
setFormTitle(preset.title || '');
|
|
214
260
|
setFormSchedule(preset.schedule || '');
|
|
215
261
|
setFormPrompt(preset.prompt || '');
|
|
262
|
+
setFormProvider(defaultModelInfo?.provider || '');
|
|
263
|
+
setFormModel(defaultModelInfo?.model || '');
|
|
264
|
+
if (defaultModelInfo?.provider) loadModels(defaultModelInfo.provider);
|
|
216
265
|
} else {
|
|
217
266
|
setFormTitle('');
|
|
218
267
|
setFormSchedule('0 9 * * 1-5');
|
|
219
268
|
setFormPrompt('');
|
|
269
|
+
setFormProvider(defaultModelInfo?.provider || '');
|
|
270
|
+
setFormModel(defaultModelInfo?.model || '');
|
|
271
|
+
if (defaultModelInfo?.provider) loadModels(defaultModelInfo.provider);
|
|
220
272
|
}
|
|
221
|
-
|
|
273
|
+
setManualModalOpen(true);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const handleProviderChange = (e) => {
|
|
277
|
+
const prov = e.target.value;
|
|
278
|
+
setFormProvider(prov);
|
|
279
|
+
setFormModel('');
|
|
280
|
+
loadModels(prov);
|
|
222
281
|
};
|
|
223
282
|
|
|
224
|
-
const
|
|
283
|
+
const handleSaveManual = async (e) => {
|
|
225
284
|
e.preventDefault();
|
|
226
285
|
try {
|
|
227
286
|
const res = await fetch('/dsh-cron/tasks', {
|
|
@@ -230,38 +289,54 @@ window.__ModuleLoader__.load({
|
|
|
230
289
|
body: JSON.stringify({
|
|
231
290
|
title: formTitle,
|
|
232
291
|
schedule: formSchedule,
|
|
233
|
-
prompt: formPrompt
|
|
292
|
+
prompt: formPrompt,
|
|
293
|
+
provider: formProvider || undefined,
|
|
294
|
+
model: formModel || undefined,
|
|
234
295
|
})
|
|
235
296
|
});
|
|
236
297
|
const data = await res.json();
|
|
237
298
|
if (!data.ok) { alert(data.error || 'Ошибка'); return; }
|
|
238
|
-
|
|
299
|
+
setManualModalOpen(false);
|
|
239
300
|
fetchTasks();
|
|
240
301
|
} catch (err) { alert(err.message); }
|
|
241
302
|
};
|
|
242
303
|
|
|
243
|
-
const
|
|
304
|
+
const openDshModal = () => {
|
|
244
305
|
setDropdownOpen(false);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
306
|
+
setDshPrompt('');
|
|
307
|
+
setDshModalOpen(true);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const handleStartWithDSH = async () => {
|
|
311
|
+
const text = dshPrompt.trim();
|
|
312
|
+
if (!text) return;
|
|
313
|
+
|
|
314
|
+
setDshLoading(true);
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch('/dsh-cron/chat/start', {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: { 'Content-Type': 'application/json' },
|
|
319
|
+
body: JSON.stringify({ prompt: text })
|
|
320
|
+
});
|
|
321
|
+
const data = await res.json();
|
|
322
|
+
if (!data.ok || !data.sessionId) {
|
|
323
|
+
alert(data.error || 'Не удалось запустить диалог с агентом');
|
|
324
|
+
setDshLoading(false);
|
|
325
|
+
return;
|
|
262
326
|
}
|
|
263
|
-
|
|
264
|
-
|
|
327
|
+
|
|
328
|
+
setDshModalOpen(false);
|
|
329
|
+
if (onClose) onClose();
|
|
330
|
+
else toggle.set(false);
|
|
331
|
+
|
|
332
|
+
if (!openSession(ctx, data.sessionId)) {
|
|
333
|
+
alert('Сессия создана (' + data.sessionId + '), перейдите к ней в списке чатов');
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
alert('Ошибка запуска: ' + err.message);
|
|
337
|
+
} finally {
|
|
338
|
+
setDshLoading(false);
|
|
339
|
+
}
|
|
265
340
|
};
|
|
266
341
|
|
|
267
342
|
return React.createElement('div', { className: 'dsh-cron-overlay' },
|
|
@@ -291,8 +366,8 @@ window.__ModuleLoader__.load({
|
|
|
291
366
|
React.createElement('span', { dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } })
|
|
292
367
|
),
|
|
293
368
|
dropdownOpen && React.createElement('div', { className: 'dsh-cron-dropdown' },
|
|
294
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick:
|
|
295
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () =>
|
|
369
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: openDshModal }, '💬 Создать с DSH'),
|
|
370
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () => openManualModal(null) }, '✏️ Настроить вручную')
|
|
296
371
|
)
|
|
297
372
|
)
|
|
298
373
|
),
|
|
@@ -312,46 +387,58 @@ window.__ModuleLoader__.load({
|
|
|
312
387
|
className: 'dsh-cron-tab',
|
|
313
388
|
'data-active': tab === t ? 'true' : undefined,
|
|
314
389
|
onClick: () => setTab(t)
|
|
315
|
-
},
|
|
316
|
-
t === 'all' ? 'Все' : t === 'active' ? 'Активные' : t === 'paused' ? 'Приостановленные' : 'Завершено'
|
|
317
|
-
)
|
|
390
|
+
}, t === 'all' ? 'Все' : t === 'active' ? 'Активные' : t === 'paused' ? 'На паузе' : 'Завершенные')
|
|
318
391
|
)
|
|
319
392
|
),
|
|
320
393
|
React.createElement('div', { className: 'dsh-cron-task-list' },
|
|
321
|
-
tasks.length === 0
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
394
|
+
tasks.length === 0
|
|
395
|
+
? React.createElement('div', { style: { color: '#777', padding: '16px 0', fontSize: '14px' } }, 'Нет запланированных задач')
|
|
396
|
+
: tasks.map((task) =>
|
|
397
|
+
React.createElement('div', { key: task.id, className: 'dsh-cron-task-item' },
|
|
398
|
+
React.createElement('button', {
|
|
399
|
+
className: 'dsh-cron-task-status-btn',
|
|
400
|
+
'data-active': task.status === 'active' ? 'true' : undefined,
|
|
401
|
+
title: task.status === 'active' ? 'Приостановить' : 'Возобновить',
|
|
402
|
+
onClick: () => handleToggleTask(task.id)
|
|
403
|
+
}, React.createElement('span', { dangerouslySetInnerHTML: { __html: task.status === 'active' ? ICON_PAUSE : ICON_PLAY } })),
|
|
404
|
+
React.createElement('div', { className: 'dsh-cron-task-info' },
|
|
405
|
+
React.createElement('div', { className: 'dsh-cron-task-title' },
|
|
406
|
+
task.title,
|
|
407
|
+
task.model ? React.createElement('span', { className: 'dsh-cron-task-model-tag' }, task.model.split('/').pop()) : null
|
|
408
|
+
),
|
|
409
|
+
React.createElement('div', { className: 'dsh-cron-task-sched' },
|
|
410
|
+
task.scheduleText || task.schedule,
|
|
411
|
+
task.nextRunAt ? ` · след: ${new Date(task.nextRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : ''
|
|
412
|
+
)
|
|
413
|
+
),
|
|
414
|
+
React.createElement('div', { className: 'dsh-cron-task-actions' },
|
|
415
|
+
React.createElement('button', {
|
|
416
|
+
className: 'dsh-cron-icon-btn',
|
|
417
|
+
title: 'Запустить немедленно',
|
|
418
|
+
onClick: () => handleRunNow(task.id)
|
|
419
|
+
}, 'Запустить'),
|
|
420
|
+
React.createElement('button', {
|
|
421
|
+
className: 'dsh-cron-icon-btn',
|
|
422
|
+
title: 'Удалить задачу',
|
|
423
|
+
onClick: () => handleDelete(task.id)
|
|
424
|
+
}, React.createElement('span', { dangerouslySetInnerHTML: { __html: ICON_TRASH } }))
|
|
425
|
+
)
|
|
426
|
+
)
|
|
337
427
|
)
|
|
338
|
-
)
|
|
339
|
-
)
|
|
340
428
|
),
|
|
341
|
-
React.createElement('div', { className: 'dsh-cron-recs-title' }, '
|
|
429
|
+
React.createElement('div', { className: 'dsh-cron-recs-title' }, 'Рекомендуемые задачи'),
|
|
342
430
|
React.createElement('div', { className: 'dsh-cron-recs-list' },
|
|
343
431
|
recs.map((rec) =>
|
|
344
432
|
React.createElement('div', {
|
|
345
433
|
key: rec.id,
|
|
346
434
|
className: 'dsh-cron-rec-card',
|
|
347
|
-
onClick: () =>
|
|
435
|
+
onClick: () => openManualModal(rec)
|
|
348
436
|
},
|
|
349
|
-
React.createElement('div', {
|
|
350
|
-
|
|
351
|
-
dangerouslySetInnerHTML: {
|
|
437
|
+
React.createElement('div', { className: 'dsh-cron-rec-icon' },
|
|
438
|
+
React.createElement('span', { dangerouslySetInnerHTML: {
|
|
352
439
|
__html: rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY
|
|
353
|
-
}
|
|
354
|
-
|
|
440
|
+
} })
|
|
441
|
+
),
|
|
355
442
|
React.createElement('div', { className: 'dsh-cron-rec-content' },
|
|
356
443
|
React.createElement('div', { className: 'dsh-cron-rec-title' },
|
|
357
444
|
rec.title,
|
|
@@ -363,10 +450,12 @@ window.__ModuleLoader__.load({
|
|
|
363
450
|
)
|
|
364
451
|
)
|
|
365
452
|
),
|
|
366
|
-
|
|
453
|
+
|
|
454
|
+
// 1. Manual creation modal
|
|
455
|
+
manualModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setManualModalOpen(false) },
|
|
367
456
|
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
368
457
|
React.createElement('div', { className: 'dsh-cron-modal-title' }, 'Настройка запланированной задачи'),
|
|
369
|
-
React.createElement('form', { onSubmit:
|
|
458
|
+
React.createElement('form', { onSubmit: handleSaveManual },
|
|
370
459
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
371
460
|
React.createElement('label', null, 'Название задачи'),
|
|
372
461
|
React.createElement('input', {
|
|
@@ -385,6 +474,28 @@ window.__ModuleLoader__.load({
|
|
|
385
474
|
onChange: (e) => setFormSchedule(e.target.value)
|
|
386
475
|
})
|
|
387
476
|
),
|
|
477
|
+
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
478
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
479
|
+
React.createElement('label', null, 'Провайдер LLM'),
|
|
480
|
+
React.createElement('select', {
|
|
481
|
+
value: formProvider,
|
|
482
|
+
onChange: handleProviderChange
|
|
483
|
+
},
|
|
484
|
+
React.createElement('option', { value: '' }, 'По умолчанию (DSH)'),
|
|
485
|
+
providers.map((p) => React.createElement('option', { key: p.id, value: p.id }, p.name || p.id))
|
|
486
|
+
)
|
|
487
|
+
),
|
|
488
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
489
|
+
React.createElement('label', null, 'Модель LLM'),
|
|
490
|
+
React.createElement('select', {
|
|
491
|
+
value: formModel,
|
|
492
|
+
onChange: (e) => setFormModel(e.target.value)
|
|
493
|
+
},
|
|
494
|
+
React.createElement('option', { value: '' }, 'По умолчанию'),
|
|
495
|
+
models.map((m) => React.createElement('option', { key: m.id, value: m.id }, m.name || m.id))
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
),
|
|
388
499
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
389
500
|
React.createElement('label', null, 'Промпт / Инструкция для агента'),
|
|
390
501
|
React.createElement('textarea', {
|
|
@@ -398,7 +509,7 @@ window.__ModuleLoader__.load({
|
|
|
398
509
|
React.createElement('button', {
|
|
399
510
|
type: 'button',
|
|
400
511
|
className: 'dsh-cron-btn-secondary',
|
|
401
|
-
onClick: () =>
|
|
512
|
+
onClick: () => setManualModalOpen(false)
|
|
402
513
|
}, 'Отмена'),
|
|
403
514
|
React.createElement('button', {
|
|
404
515
|
type: 'submit',
|
|
@@ -407,6 +518,56 @@ window.__ModuleLoader__.load({
|
|
|
407
518
|
)
|
|
408
519
|
)
|
|
409
520
|
)
|
|
521
|
+
),
|
|
522
|
+
|
|
523
|
+
// 2. "Создать с DSH" modal
|
|
524
|
+
dshModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => !dshLoading && setDshModalOpen(false) },
|
|
525
|
+
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
526
|
+
React.createElement('div', { className: 'dsh-cron-modal-title' }, 'Запланировать задачу с DSH Агентом'),
|
|
527
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
528
|
+
React.createElement('label', null, 'Опишите задачу и желаемую периодичность:'),
|
|
529
|
+
React.createElement('div', { className: 'dsh-cron-chat-box' },
|
|
530
|
+
React.createElement('textarea', {
|
|
531
|
+
className: 'dsh-cron-chat-input',
|
|
532
|
+
rows: 4,
|
|
533
|
+
autoFocus: true,
|
|
534
|
+
disabled: dshLoading,
|
|
535
|
+
placeholder: 'Например: проверяй каждые 2 часа свободное место на сервере и наличие упавших systemd-сервисов, присылай алерт только если что-то не так...',
|
|
536
|
+
value: dshPrompt,
|
|
537
|
+
onKeyDown: (e) => {
|
|
538
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
539
|
+
e.preventDefault();
|
|
540
|
+
handleStartWithDSH();
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
onChange: (e) => setDshPrompt(e.target.value)
|
|
544
|
+
}),
|
|
545
|
+
React.createElement('div', { className: 'dsh-cron-chat-foot' },
|
|
546
|
+
React.createElement('span', { className: 'dsh-cron-chat-hint' }, 'Ctrl+Enter для отправки'),
|
|
547
|
+
React.createElement('button', {
|
|
548
|
+
type: 'button',
|
|
549
|
+
className: 'dsh-cron-send-btn',
|
|
550
|
+
disabled: dshLoading || !dshPrompt.trim(),
|
|
551
|
+
title: 'Отправить агенту',
|
|
552
|
+
onClick: handleStartWithDSH
|
|
553
|
+
},
|
|
554
|
+
React.createElement('span', {
|
|
555
|
+
style: { display: 'flex', alignItems: 'center' },
|
|
556
|
+
dangerouslySetInnerHTML: { __html: ICON_SEND }
|
|
557
|
+
})
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
),
|
|
562
|
+
React.createElement('div', { className: 'dsh-cron-modal-foot' },
|
|
563
|
+
React.createElement('button', {
|
|
564
|
+
type: 'button',
|
|
565
|
+
className: 'dsh-cron-btn-secondary',
|
|
566
|
+
disabled: dshLoading,
|
|
567
|
+
onClick: () => setDshModalOpen(false)
|
|
568
|
+
}, 'Отмена')
|
|
569
|
+
)
|
|
570
|
+
)
|
|
410
571
|
)
|
|
411
572
|
);
|
|
412
573
|
}
|
|
@@ -611,4 +772,4 @@ window.__ModuleLoader__.load({
|
|
|
611
772
|
module.exports = { apply, inject: ['slots', 'locale', 'settingsScope'] };
|
|
612
773
|
return module.exports;
|
|
613
774
|
}
|
|
614
|
-
})
|
|
775
|
+
})
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { TaskStore } from './store.js';
|
|
2
2
|
import { TaskScheduler, parseScheduleExpression } from './scheduler.js';
|
|
3
3
|
import { SessionRunner } from './runner.js';
|
|
4
|
+
import { getModelsHandler } from './models-handler.js';
|
|
5
|
+
import { chatStartHandler } from './chat-start.js';
|
|
4
6
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
5
8
|
|
|
6
9
|
export const name = '@goodandready/dsh-cron';
|
|
7
|
-
export const inject = ['tools', 'webServer', 'settings'];
|
|
10
|
+
export const inject = ['tools', 'webServer', 'settings', 'llm', 'agents', 'agentDefaultModel'];
|
|
8
11
|
|
|
9
12
|
function parseJsonBody(req) {
|
|
10
13
|
return new Promise((resolve, reject) => {
|
|
@@ -101,6 +104,8 @@ export function apply(ctx, config) {
|
|
|
101
104
|
type: body.type || 'prompt',
|
|
102
105
|
delivery: body.delivery || 'current',
|
|
103
106
|
status: body.status || 'active',
|
|
107
|
+
provider: body.provider || undefined,
|
|
108
|
+
model: body.model || undefined,
|
|
104
109
|
});
|
|
105
110
|
scheduler.scheduleTask(task);
|
|
106
111
|
sendJson(res, 200, { ok: true, task });
|
|
@@ -114,6 +119,24 @@ export function apply(ctx, config) {
|
|
|
114
119
|
}
|
|
115
120
|
}), 'dsh-cron: /tasks');
|
|
116
121
|
|
|
122
|
+
// GET /dsh-cron/models
|
|
123
|
+
ctx.effect(() => ctx.webServer.register({
|
|
124
|
+
kind: 'exact',
|
|
125
|
+
path: '/dsh-cron/models',
|
|
126
|
+
handler: async (req, res) => {
|
|
127
|
+
await getModelsHandler(ctx, req, res, sendJson);
|
|
128
|
+
}
|
|
129
|
+
}), 'dsh-cron: /models');
|
|
130
|
+
|
|
131
|
+
// POST /dsh-cron/chat/start
|
|
132
|
+
ctx.effect(() => ctx.webServer.register({
|
|
133
|
+
kind: 'exact',
|
|
134
|
+
path: '/dsh-cron/chat/start',
|
|
135
|
+
handler: async (req, res) => {
|
|
136
|
+
await chatStartHandler(ctx, req, res, parseJsonBody, sendJson, randomUUID);
|
|
137
|
+
}
|
|
138
|
+
}), 'dsh-cron: /chat/start');
|
|
139
|
+
|
|
117
140
|
// Prefix routes for actions (/dsh-cron/action/*)
|
|
118
141
|
ctx.effect(() => ctx.webServer.register({
|
|
119
142
|
kind: 'prefix',
|
|
@@ -128,11 +151,11 @@ export function apply(ctx, config) {
|
|
|
128
151
|
const taskId = toggleMatch[1];
|
|
129
152
|
const task = store.get(taskId);
|
|
130
153
|
if (!task) {
|
|
131
|
-
sendJson(res, 404, { ok: false, error: '
|
|
154
|
+
sendJson(res, 404, { ok: false, error: 'Task not found' });
|
|
132
155
|
return;
|
|
133
156
|
}
|
|
134
|
-
const updated = task.status === 'active'
|
|
135
|
-
? scheduler.pauseTask(taskId)
|
|
157
|
+
const updated = task.status === 'active'
|
|
158
|
+
? scheduler.pauseTask(taskId)
|
|
136
159
|
: scheduler.resumeTask(taskId);
|
|
137
160
|
sendJson(res, 200, { ok: true, task: updated });
|
|
138
161
|
return;
|
|
@@ -141,13 +164,8 @@ export function apply(ctx, config) {
|
|
|
141
164
|
const runMatch = pathname.match(/^\/dsh-cron\/action\/([^/]+)\/run$/);
|
|
142
165
|
if (req.method === 'POST' && runMatch) {
|
|
143
166
|
const taskId = runMatch[1];
|
|
144
|
-
const task = store.get(taskId);
|
|
145
|
-
if (!task) {
|
|
146
|
-
sendJson(res, 404, { ok: false, error: 'Задача не найдена' });
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
167
|
scheduler.runTask(taskId);
|
|
150
|
-
sendJson(res, 200, { ok: true
|
|
168
|
+
sendJson(res, 200, { ok: true });
|
|
151
169
|
return;
|
|
152
170
|
}
|
|
153
171
|
|
|
@@ -175,7 +193,9 @@ export function apply(ctx, config) {
|
|
|
175
193
|
title: { type: 'string', required: true, description: 'Краткое название задачи' },
|
|
176
194
|
schedule: { type: 'string', required: true, description: 'Выражение cron или интервал (every 30m, 0 9 * * 1-5, daily)' },
|
|
177
195
|
prompt: { type: 'string', required: true, description: 'Инструкция для агента при запуске' },
|
|
178
|
-
delivery: { type: 'string', description: 'Режим запуска: current или isolated' }
|
|
196
|
+
delivery: { type: 'string', description: 'Режим запуска: current или isolated' },
|
|
197
|
+
provider: { type: 'string', description: 'ID провайдера LLM для выполнения задачи' },
|
|
198
|
+
model: { type: 'string', description: 'ID модели LLM для выполнения задачи' }
|
|
179
199
|
},
|
|
180
200
|
output: {
|
|
181
201
|
schema: {
|
|
@@ -190,7 +210,7 @@ export function apply(ctx, config) {
|
|
|
190
210
|
return [{ type: 'text', text: value.message }];
|
|
191
211
|
}
|
|
192
212
|
},
|
|
193
|
-
execute: async ({ title, schedule, prompt, delivery }) => {
|
|
213
|
+
execute: async ({ title, schedule, prompt, delivery, provider, model }) => {
|
|
194
214
|
try {
|
|
195
215
|
const parsed = parseScheduleExpression(schedule);
|
|
196
216
|
const task = store.set({
|
|
@@ -199,6 +219,8 @@ export function apply(ctx, config) {
|
|
|
199
219
|
scheduleText: parsed.humanText,
|
|
200
220
|
prompt,
|
|
201
221
|
delivery: delivery || 'isolated',
|
|
222
|
+
provider: provider || undefined,
|
|
223
|
+
model: model || undefined,
|
|
202
224
|
status: 'active'
|
|
203
225
|
});
|
|
204
226
|
scheduler.scheduleTask(task);
|
|
@@ -346,4 +368,4 @@ export function apply(ctx, config) {
|
|
|
346
368
|
return;
|
|
347
369
|
}
|
|
348
370
|
});
|
|
349
|
-
}
|
|
371
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export async function getModelsHandler(ctx, req, res, sendJson) {
|
|
2
|
+
const current = ctx.get('agentDefaultModel')?.currentSelection?.() ?? null;
|
|
3
|
+
let providers = [];
|
|
4
|
+
try {
|
|
5
|
+
if (ctx.llm && typeof ctx.llm.listProviders === 'function') {
|
|
6
|
+
providers = (ctx.llm.listProviders() ?? []).map((p) => ({ id: p.id, name: p.name ?? p.id }));
|
|
7
|
+
}
|
|
8
|
+
} catch {
|
|
9
|
+
providers = [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
13
|
+
const wanted = url.searchParams.get('provider') || '';
|
|
14
|
+
if (wanted === '') {
|
|
15
|
+
sendJson(res, 200, { current, providers, models: [] });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
let rows = [];
|
|
21
|
+
if (ctx.llm && typeof ctx.llm.listModels === 'function') {
|
|
22
|
+
rows = await ctx.llm.listModels(wanted);
|
|
23
|
+
}
|
|
24
|
+
sendJson(res, 200, {
|
|
25
|
+
current,
|
|
26
|
+
providers,
|
|
27
|
+
models: (rows ?? []).map((m) => ({ id: m.id, name: m.name ?? m.id })),
|
|
28
|
+
});
|
|
29
|
+
} catch {
|
|
30
|
+
sendJson(res, 200, { current, providers, models: [] });
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/prompt.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the initial prompt for starting a session with DSH agent to configure a cron task.
|
|
3
|
+
* @param {string} userText
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function buildAgentCronPrompt(userText) {
|
|
7
|
+
const text = (userText || '').trim();
|
|
8
|
+
return `Пользователь хочет запланировать cron-задачу через DSH Cron:
|
|
9
|
+
"${text}"
|
|
10
|
+
|
|
11
|
+
ИНСТРУКЦИЯ ДЛЯ АГЕНТА:
|
|
12
|
+
1. Твоя цель — помочь пользователю настроить и зарегистрировать запланированную задачу с помощью инструмента \`cron_schedule_task\`.
|
|
13
|
+
2. Предложи подходящую и экономичную LLM-модель для выполнения этого крона (например, \`Qwen/Qwen3.8-Flash\` через Command Code или \`deepseek/deepseek-v4-flash\`), уточни у пользователя, устраивает ли она его или он предпочитает другую подключенную модель.
|
|
14
|
+
3. Уточни и сформулируй точное расписание cron (например, "0 * * * *" или интервал вроде "every 1h").
|
|
15
|
+
4. Примени Silent Rule (правило тишины): если задача мониторинга/проверки не обнаружила проблем, критических изменений или ошибок, агент должен возвращать ПУСТОЙ ответ или короткое уведомление без лишнего шума.
|
|
16
|
+
5. После согласования параметров ОБЯЗАТЕЛЬНО вызови инструмент \`cron_schedule_task\`, передав title, schedule, prompt и delivery ("isolated" или "current").`;
|
|
17
|
+
}
|
package/lib/runner.js
CHANGED
|
@@ -9,7 +9,51 @@ export class SessionRunner {
|
|
|
9
9
|
async execute(task) {
|
|
10
10
|
console.log(`[dsh-cron] executing task "${task.title}" (${task.id})`);
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// 1. If ctx.agents is available, run through agent session
|
|
13
|
+
if (this.ctx && this.ctx.agents && typeof this.ctx.agents.create === 'function') {
|
|
14
|
+
try {
|
|
15
|
+
const { randomUUID } = await import('node:crypto');
|
|
16
|
+
let createUserMessage = (m) => m;
|
|
17
|
+
try {
|
|
18
|
+
const llmModule = await import('@deepseek-ai/dsh-llm');
|
|
19
|
+
if (llmModule && typeof llmModule.createUserMessage === 'function') {
|
|
20
|
+
createUserMessage = llmModule.createUserMessage;
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
|
|
24
|
+
let mintSessionId = () => `cron-exec-${task.id}-${randomUUID()}`;
|
|
25
|
+
try {
|
|
26
|
+
const sessionModule = await import('@deepseek-ai/dsh-session');
|
|
27
|
+
if (sessionModule && typeof sessionModule.SessionId === 'function') {
|
|
28
|
+
const sid = mintSessionId();
|
|
29
|
+
mintSessionId = () => sessionModule.SessionId(sid);
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
|
|
33
|
+
const defaultSel = this.ctx.get?.('agentDefaultModel')?.currentSelection?.();
|
|
34
|
+
const provider = task.provider || defaultSel?.provider;
|
|
35
|
+
const model = task.model || defaultSel?.model;
|
|
36
|
+
|
|
37
|
+
const handle = await this.ctx.agents.create({
|
|
38
|
+
sessionId: mintSessionId(),
|
|
39
|
+
meta: {},
|
|
40
|
+
agentOptions: provider && model ? { provider, model } : undefined,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await handle.agent.whenIdle();
|
|
44
|
+
handle.agent.followup(createUserMessage({
|
|
45
|
+
content: [{ type: 'text', text: task.prompt }],
|
|
46
|
+
source: { kind: 'plugin', plugin: 'dsh-cron', form: 'cron-execute' },
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
return `[dsh-cron] Agent started session ${handle.agent.session?.id || 'cron'}`;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(`[dsh-cron] agent execution failed:`, err.message);
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Check if legacy session service is present
|
|
13
57
|
if (this.ctx && this.ctx.session && typeof this.ctx.session.create === 'function') {
|
|
14
58
|
try {
|
|
15
59
|
const session = await this.ctx.session.create({
|
|
@@ -27,7 +71,7 @@ export class SessionRunner {
|
|
|
27
71
|
}
|
|
28
72
|
}
|
|
29
73
|
|
|
30
|
-
// Direct shell / script task fallback
|
|
74
|
+
// 3. Direct shell / script task fallback
|
|
31
75
|
if (task.type === 'script' && task.prompt) {
|
|
32
76
|
const { exec } = await import('node:child_process');
|
|
33
77
|
const { promisify } = await import('node:util');
|