@goodandready/dsh-cron 0.1.4 → 0.1.6
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 +257 -77
- 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,26 @@ 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],
|
|
30
|
-
[
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
html[${ACTIVE_ATTR}]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
html[${ACTIVE_ATTR}] [data-pane=conversation],
|
|
32
|
+
html[${ACTIVE_ATTR}] [class*=centerCol] {
|
|
33
|
+
position: relative !important;
|
|
34
|
+
}
|
|
35
|
+
html[${ACTIVE_ATTR}] [data-pane=conversation] > *:not([${VIEW_ATTR}]),
|
|
36
|
+
html[${ACTIVE_ATTR}] [class*=centerCol] > *:not([${VIEW_ATTR}]) {
|
|
37
|
+
visibility: hidden !important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dsh-cron-entry-clone { display: flex; align-items: center; gap: 8px; }
|
|
41
|
+
.dsh-cron-entry-icon { display: flex; align-items: center; justify-content: center; flex: none; width: 16px; height: 16px; }
|
|
38
42
|
.dsh-cron-entry-label { text-overflow: ellipsis; overflow: hidden; }
|
|
39
43
|
[data-dsh-frame][data-sidebar-collapsed] .dsh-cron-entry-label { display: none; }
|
|
40
44
|
|
|
@@ -67,6 +71,7 @@ window.__ModuleLoader__.load({
|
|
|
67
71
|
.dsh-cron-task-info { flex: 1; min-width: 0; }
|
|
68
72
|
.dsh-cron-task-title { font-size: 15px; font-weight: 500; margin-bottom: 3px; }
|
|
69
73
|
.dsh-cron-task-sched { font-size: 13px; color: #9ca3af; }
|
|
74
|
+
.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
75
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
71
76
|
.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
77
|
.dsh-cron-icon-btn:hover { background: #2a2a2a; color: #fff; }
|
|
@@ -81,15 +86,25 @@ window.__ModuleLoader__.load({
|
|
|
81
86
|
.dsh-cron-rec-desc { font-size: 13px; color: #71717a; }
|
|
82
87
|
|
|
83
88
|
.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:
|
|
89
|
+
.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
90
|
.dsh-cron-modal-title { font-size: 18px; font-weight: 600; margin-bottom: 18px; }
|
|
86
91
|
.dsh-cron-form-group { margin-bottom: 16px; }
|
|
87
92
|
.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; }
|
|
93
|
+
.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; }
|
|
94
|
+
.dsh-cron-form-group select { cursor: pointer; }
|
|
89
95
|
.dsh-cron-form-group textarea { min-height: 80px; resize: vertical; }
|
|
90
|
-
.dsh-cron-
|
|
91
|
-
.dsh-cron-
|
|
96
|
+
.dsh-cron-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
97
|
+
.dsh-cron-modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 20px; }
|
|
98
|
+
.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; }
|
|
99
|
+
.dsh-cron-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
92
100
|
.dsh-cron-btn-secondary { background: transparent; color: #999; border: 1px solid #383838; border-radius: 8px; padding: 7px 16px; font-size: 13.5px; cursor: pointer; }
|
|
101
|
+
.dsh-cron-chat-box { position: relative; background: #141414; border: 1px solid #333; border-radius: 12px; padding: 12px; }
|
|
102
|
+
.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; }
|
|
103
|
+
.dsh-cron-chat-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding-top: 8px; border-top: 1px solid #222; }
|
|
104
|
+
.dsh-cron-chat-hint { font-size: 12px; color: #666; }
|
|
105
|
+
.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; }
|
|
106
|
+
.dsh-cron-send-btn:hover:not(:disabled) { background: #fff; transform: translateY(-1px); }
|
|
107
|
+
.dsh-cron-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
93
108
|
|
|
94
109
|
.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
110
|
.dsh-cron-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
|
@@ -129,28 +144,55 @@ window.__ModuleLoader__.load({
|
|
|
129
144
|
};
|
|
130
145
|
}
|
|
131
146
|
|
|
147
|
+
function openSession(ctx, sessionId) {
|
|
148
|
+
if (!sessionId) return false;
|
|
149
|
+
const sessions = ctx && typeof ctx.get === 'function' ? ctx.get('sessions') : ctx?.sessions;
|
|
150
|
+
if (!sessions || typeof sessions.open !== 'function') return false;
|
|
151
|
+
try {
|
|
152
|
+
sessions.open(sessionId);
|
|
153
|
+
return true;
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
132
159
|
function CronScreen(props) {
|
|
133
|
-
const { toggle, onClose } = props;
|
|
160
|
+
const { ctx, toggle, onClose } = props;
|
|
134
161
|
const [tab, setTab] = React.useState('all');
|
|
135
162
|
const [query, setQuery] = React.useState('');
|
|
136
163
|
const [tasks, setTasks] = React.useState([]);
|
|
137
164
|
const [recs, setRecs] = React.useState([]);
|
|
138
165
|
const [dropdownOpen, setDropdownOpen] = React.useState(false);
|
|
139
|
-
|
|
166
|
+
|
|
167
|
+
// Manual modal state
|
|
168
|
+
const [manualModalOpen, setManualModalOpen] = React.useState(false);
|
|
140
169
|
const [formTitle, setFormTitle] = React.useState('');
|
|
141
170
|
const [formSchedule, setFormSchedule] = React.useState('');
|
|
142
171
|
const [formPrompt, setFormPrompt] = React.useState('');
|
|
172
|
+
const [formProvider, setFormProvider] = React.useState('');
|
|
173
|
+
const [formModel, setFormModel] = React.useState('');
|
|
174
|
+
|
|
175
|
+
// DSH dialog state
|
|
176
|
+
const [dshModalOpen, setDshModalOpen] = React.useState(false);
|
|
177
|
+
const [dshPrompt, setDshPrompt] = React.useState('');
|
|
178
|
+
const [dshLoading, setDshLoading] = React.useState(false);
|
|
179
|
+
|
|
180
|
+
// Models list state
|
|
181
|
+
const [providers, setProviders] = React.useState([]);
|
|
182
|
+
const [models, setModels] = React.useState([]);
|
|
183
|
+
const [defaultModelInfo, setDefaultModelInfo] = React.useState(null);
|
|
143
184
|
|
|
144
185
|
React.useEffect(() => {
|
|
145
186
|
const onKey = (e) => {
|
|
146
187
|
if (e.key !== 'Escape') return;
|
|
147
|
-
if (
|
|
188
|
+
if (manualModalOpen) { setManualModalOpen(false); return; }
|
|
189
|
+
if (dshModalOpen) { setDshModalOpen(false); return; }
|
|
148
190
|
if (dropdownOpen) { setDropdownOpen(false); return; }
|
|
149
191
|
if (onClose) onClose();
|
|
150
192
|
};
|
|
151
193
|
document.addEventListener('keydown', onKey);
|
|
152
194
|
return () => document.removeEventListener('keydown', onKey);
|
|
153
|
-
}, [
|
|
195
|
+
}, [manualModalOpen, dshModalOpen, dropdownOpen, onClose]);
|
|
154
196
|
|
|
155
197
|
const fetchTasks = async () => {
|
|
156
198
|
try {
|
|
@@ -163,7 +205,21 @@ window.__ModuleLoader__.load({
|
|
|
163
205
|
} catch (err) {}
|
|
164
206
|
};
|
|
165
207
|
|
|
208
|
+
const loadModels = async (providerToFetch) => {
|
|
209
|
+
try {
|
|
210
|
+
const url = '/dsh-cron/models' + (providerToFetch ? '?provider=' + encodeURIComponent(providerToFetch) : '');
|
|
211
|
+
const res = await fetch(url);
|
|
212
|
+
const data = await res.json();
|
|
213
|
+
if (data) {
|
|
214
|
+
if (data.providers) setProviders(data.providers);
|
|
215
|
+
if (data.models) setModels(data.models);
|
|
216
|
+
if (data.current) setDefaultModelInfo(data.current);
|
|
217
|
+
}
|
|
218
|
+
} catch (err) {}
|
|
219
|
+
};
|
|
220
|
+
|
|
166
221
|
React.useEffect(() => { fetchTasks(); }, [tab, query]);
|
|
222
|
+
React.useEffect(() => { loadModels(); }, []);
|
|
167
223
|
|
|
168
224
|
const handleToggleTask = async (id) => {
|
|
169
225
|
try {
|
|
@@ -188,21 +244,34 @@ window.__ModuleLoader__.load({
|
|
|
188
244
|
} catch (err) {}
|
|
189
245
|
};
|
|
190
246
|
|
|
191
|
-
const
|
|
247
|
+
const openManualModal = (preset) => {
|
|
192
248
|
setDropdownOpen(false);
|
|
193
249
|
if (preset) {
|
|
194
250
|
setFormTitle(preset.title || '');
|
|
195
251
|
setFormSchedule(preset.schedule || '');
|
|
196
252
|
setFormPrompt(preset.prompt || '');
|
|
253
|
+
setFormProvider(defaultModelInfo?.provider || '');
|
|
254
|
+
setFormModel(defaultModelInfo?.model || '');
|
|
255
|
+
if (defaultModelInfo?.provider) loadModels(defaultModelInfo.provider);
|
|
197
256
|
} else {
|
|
198
257
|
setFormTitle('');
|
|
199
258
|
setFormSchedule('0 9 * * 1-5');
|
|
200
259
|
setFormPrompt('');
|
|
260
|
+
setFormProvider(defaultModelInfo?.provider || '');
|
|
261
|
+
setFormModel(defaultModelInfo?.model || '');
|
|
262
|
+
if (defaultModelInfo?.provider) loadModels(defaultModelInfo.provider);
|
|
201
263
|
}
|
|
202
|
-
|
|
264
|
+
setManualModalOpen(true);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handleProviderChange = (e) => {
|
|
268
|
+
const prov = e.target.value;
|
|
269
|
+
setFormProvider(prov);
|
|
270
|
+
setFormModel('');
|
|
271
|
+
loadModels(prov);
|
|
203
272
|
};
|
|
204
273
|
|
|
205
|
-
const
|
|
274
|
+
const handleSaveManual = async (e) => {
|
|
206
275
|
e.preventDefault();
|
|
207
276
|
try {
|
|
208
277
|
const res = await fetch('/dsh-cron/tasks', {
|
|
@@ -211,27 +280,54 @@ window.__ModuleLoader__.load({
|
|
|
211
280
|
body: JSON.stringify({
|
|
212
281
|
title: formTitle,
|
|
213
282
|
schedule: formSchedule,
|
|
214
|
-
prompt: formPrompt
|
|
283
|
+
prompt: formPrompt,
|
|
284
|
+
provider: formProvider || undefined,
|
|
285
|
+
model: formModel || undefined,
|
|
215
286
|
})
|
|
216
287
|
});
|
|
217
288
|
const data = await res.json();
|
|
218
289
|
if (!data.ok) { alert(data.error || 'Ошибка'); return; }
|
|
219
|
-
|
|
290
|
+
setManualModalOpen(false);
|
|
220
291
|
fetchTasks();
|
|
221
292
|
} catch (err) { alert(err.message); }
|
|
222
293
|
};
|
|
223
294
|
|
|
224
|
-
const
|
|
295
|
+
const openDshModal = () => {
|
|
225
296
|
setDropdownOpen(false);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
297
|
+
setDshPrompt('');
|
|
298
|
+
setDshModalOpen(true);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const handleStartWithDSH = async () => {
|
|
302
|
+
const text = dshPrompt.trim();
|
|
303
|
+
if (!text) return;
|
|
304
|
+
|
|
305
|
+
setDshLoading(true);
|
|
306
|
+
try {
|
|
307
|
+
const res = await fetch('/dsh-cron/chat/start', {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
310
|
+
body: JSON.stringify({ prompt: text })
|
|
311
|
+
});
|
|
312
|
+
const data = await res.json();
|
|
313
|
+
if (!data.ok || !data.sessionId) {
|
|
314
|
+
alert(data.error || 'Не удалось запустить диалог с агентом');
|
|
315
|
+
setDshLoading(false);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
setDshModalOpen(false);
|
|
320
|
+
if (onClose) onClose();
|
|
321
|
+
else toggle.set(false);
|
|
322
|
+
|
|
323
|
+
if (!openSession(ctx, data.sessionId)) {
|
|
324
|
+
alert('Сессия создана (' + data.sessionId + '), перейдите к ней в списке чатов');
|
|
233
325
|
}
|
|
234
|
-
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
alert('Ошибка запуска: ' + err.message);
|
|
328
|
+
} finally {
|
|
329
|
+
setDshLoading(false);
|
|
330
|
+
}
|
|
235
331
|
};
|
|
236
332
|
|
|
237
333
|
return React.createElement('div', { className: 'dsh-cron-overlay' },
|
|
@@ -261,8 +357,8 @@ window.__ModuleLoader__.load({
|
|
|
261
357
|
React.createElement('span', { dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } })
|
|
262
358
|
),
|
|
263
359
|
dropdownOpen && React.createElement('div', { className: 'dsh-cron-dropdown' },
|
|
264
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick:
|
|
265
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () =>
|
|
360
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: openDshModal }, '💬 Создать с DSH'),
|
|
361
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () => openManualModal(null) }, '✏️ Настроить вручную')
|
|
266
362
|
)
|
|
267
363
|
)
|
|
268
364
|
),
|
|
@@ -282,46 +378,58 @@ window.__ModuleLoader__.load({
|
|
|
282
378
|
className: 'dsh-cron-tab',
|
|
283
379
|
'data-active': tab === t ? 'true' : undefined,
|
|
284
380
|
onClick: () => setTab(t)
|
|
285
|
-
},
|
|
286
|
-
t === 'all' ? 'Все' : t === 'active' ? 'Активные' : t === 'paused' ? 'Приостановленные' : 'Завершено'
|
|
287
|
-
)
|
|
381
|
+
}, t === 'all' ? 'Все' : t === 'active' ? 'Активные' : t === 'paused' ? 'На паузе' : 'Завершенные')
|
|
288
382
|
)
|
|
289
383
|
),
|
|
290
384
|
React.createElement('div', { className: 'dsh-cron-task-list' },
|
|
291
|
-
tasks.length === 0
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
385
|
+
tasks.length === 0
|
|
386
|
+
? React.createElement('div', { style: { color: '#777', padding: '16px 0', fontSize: '14px' } }, 'Нет запланированных задач')
|
|
387
|
+
: tasks.map((task) =>
|
|
388
|
+
React.createElement('div', { key: task.id, className: 'dsh-cron-task-item' },
|
|
389
|
+
React.createElement('button', {
|
|
390
|
+
className: 'dsh-cron-task-status-btn',
|
|
391
|
+
'data-active': task.status === 'active' ? 'true' : undefined,
|
|
392
|
+
title: task.status === 'active' ? 'Приостановить' : 'Возобновить',
|
|
393
|
+
onClick: () => handleToggleTask(task.id)
|
|
394
|
+
}, React.createElement('span', { dangerouslySetInnerHTML: { __html: task.status === 'active' ? ICON_PAUSE : ICON_PLAY } })),
|
|
395
|
+
React.createElement('div', { className: 'dsh-cron-task-info' },
|
|
396
|
+
React.createElement('div', { className: 'dsh-cron-task-title' },
|
|
397
|
+
task.title,
|
|
398
|
+
task.model ? React.createElement('span', { className: 'dsh-cron-task-model-tag' }, task.model.split('/').pop()) : null
|
|
399
|
+
),
|
|
400
|
+
React.createElement('div', { className: 'dsh-cron-task-sched' },
|
|
401
|
+
task.scheduleText || task.schedule,
|
|
402
|
+
task.nextRunAt ? ` · след: ${new Date(task.nextRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : ''
|
|
403
|
+
)
|
|
404
|
+
),
|
|
405
|
+
React.createElement('div', { className: 'dsh-cron-task-actions' },
|
|
406
|
+
React.createElement('button', {
|
|
407
|
+
className: 'dsh-cron-icon-btn',
|
|
408
|
+
title: 'Запустить немедленно',
|
|
409
|
+
onClick: () => handleRunNow(task.id)
|
|
410
|
+
}, 'Запустить'),
|
|
411
|
+
React.createElement('button', {
|
|
412
|
+
className: 'dsh-cron-icon-btn',
|
|
413
|
+
title: 'Удалить задачу',
|
|
414
|
+
onClick: () => handleDelete(task.id)
|
|
415
|
+
}, React.createElement('span', { dangerouslySetInnerHTML: { __html: ICON_TRASH } }))
|
|
416
|
+
)
|
|
417
|
+
)
|
|
307
418
|
)
|
|
308
|
-
)
|
|
309
|
-
)
|
|
310
419
|
),
|
|
311
|
-
React.createElement('div', { className: 'dsh-cron-recs-title' }, '
|
|
420
|
+
React.createElement('div', { className: 'dsh-cron-recs-title' }, 'Рекомендуемые задачи'),
|
|
312
421
|
React.createElement('div', { className: 'dsh-cron-recs-list' },
|
|
313
422
|
recs.map((rec) =>
|
|
314
423
|
React.createElement('div', {
|
|
315
424
|
key: rec.id,
|
|
316
425
|
className: 'dsh-cron-rec-card',
|
|
317
|
-
onClick: () =>
|
|
426
|
+
onClick: () => openManualModal(rec)
|
|
318
427
|
},
|
|
319
|
-
React.createElement('div', {
|
|
320
|
-
|
|
321
|
-
dangerouslySetInnerHTML: {
|
|
428
|
+
React.createElement('div', { className: 'dsh-cron-rec-icon' },
|
|
429
|
+
React.createElement('span', { dangerouslySetInnerHTML: {
|
|
322
430
|
__html: rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY
|
|
323
|
-
}
|
|
324
|
-
|
|
431
|
+
} })
|
|
432
|
+
),
|
|
325
433
|
React.createElement('div', { className: 'dsh-cron-rec-content' },
|
|
326
434
|
React.createElement('div', { className: 'dsh-cron-rec-title' },
|
|
327
435
|
rec.title,
|
|
@@ -333,10 +441,12 @@ window.__ModuleLoader__.load({
|
|
|
333
441
|
)
|
|
334
442
|
)
|
|
335
443
|
),
|
|
336
|
-
|
|
444
|
+
|
|
445
|
+
// 1. Manual creation modal
|
|
446
|
+
manualModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setManualModalOpen(false) },
|
|
337
447
|
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
338
448
|
React.createElement('div', { className: 'dsh-cron-modal-title' }, 'Настройка запланированной задачи'),
|
|
339
|
-
React.createElement('form', { onSubmit:
|
|
449
|
+
React.createElement('form', { onSubmit: handleSaveManual },
|
|
340
450
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
341
451
|
React.createElement('label', null, 'Название задачи'),
|
|
342
452
|
React.createElement('input', {
|
|
@@ -355,6 +465,28 @@ window.__ModuleLoader__.load({
|
|
|
355
465
|
onChange: (e) => setFormSchedule(e.target.value)
|
|
356
466
|
})
|
|
357
467
|
),
|
|
468
|
+
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
469
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
470
|
+
React.createElement('label', null, 'Провайдер LLM'),
|
|
471
|
+
React.createElement('select', {
|
|
472
|
+
value: formProvider,
|
|
473
|
+
onChange: handleProviderChange
|
|
474
|
+
},
|
|
475
|
+
React.createElement('option', { value: '' }, 'По умолчанию (DSH)'),
|
|
476
|
+
providers.map((p) => React.createElement('option', { key: p.id, value: p.id }, p.name || p.id))
|
|
477
|
+
)
|
|
478
|
+
),
|
|
479
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
480
|
+
React.createElement('label', null, 'Модель LLM'),
|
|
481
|
+
React.createElement('select', {
|
|
482
|
+
value: formModel,
|
|
483
|
+
onChange: (e) => setFormModel(e.target.value)
|
|
484
|
+
},
|
|
485
|
+
React.createElement('option', { value: '' }, 'По умолчанию'),
|
|
486
|
+
models.map((m) => React.createElement('option', { key: m.id, value: m.id }, m.name || m.id))
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
),
|
|
358
490
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
359
491
|
React.createElement('label', null, 'Промпт / Инструкция для агента'),
|
|
360
492
|
React.createElement('textarea', {
|
|
@@ -368,7 +500,7 @@ window.__ModuleLoader__.load({
|
|
|
368
500
|
React.createElement('button', {
|
|
369
501
|
type: 'button',
|
|
370
502
|
className: 'dsh-cron-btn-secondary',
|
|
371
|
-
onClick: () =>
|
|
503
|
+
onClick: () => setManualModalOpen(false)
|
|
372
504
|
}, 'Отмена'),
|
|
373
505
|
React.createElement('button', {
|
|
374
506
|
type: 'submit',
|
|
@@ -377,6 +509,56 @@ window.__ModuleLoader__.load({
|
|
|
377
509
|
)
|
|
378
510
|
)
|
|
379
511
|
)
|
|
512
|
+
),
|
|
513
|
+
|
|
514
|
+
// 2. "Создать с DSH" modal
|
|
515
|
+
dshModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => !dshLoading && setDshModalOpen(false) },
|
|
516
|
+
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
517
|
+
React.createElement('div', { className: 'dsh-cron-modal-title' }, 'Запланировать задачу с DSH Агентом'),
|
|
518
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
519
|
+
React.createElement('label', null, 'Опишите задачу и желаемую периодичность:'),
|
|
520
|
+
React.createElement('div', { className: 'dsh-cron-chat-box' },
|
|
521
|
+
React.createElement('textarea', {
|
|
522
|
+
className: 'dsh-cron-chat-input',
|
|
523
|
+
rows: 4,
|
|
524
|
+
autoFocus: true,
|
|
525
|
+
disabled: dshLoading,
|
|
526
|
+
placeholder: 'Например: проверяй каждые 2 часа свободное место на сервере и наличие упавших systemd-сервисов, присылай алерт только если что-то не так...',
|
|
527
|
+
value: dshPrompt,
|
|
528
|
+
onKeyDown: (e) => {
|
|
529
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
530
|
+
e.preventDefault();
|
|
531
|
+
handleStartWithDSH();
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
onChange: (e) => setDshPrompt(e.target.value)
|
|
535
|
+
}),
|
|
536
|
+
React.createElement('div', { className: 'dsh-cron-chat-foot' },
|
|
537
|
+
React.createElement('span', { className: 'dsh-cron-chat-hint' }, 'Ctrl+Enter для отправки'),
|
|
538
|
+
React.createElement('button', {
|
|
539
|
+
type: 'button',
|
|
540
|
+
className: 'dsh-cron-send-btn',
|
|
541
|
+
disabled: dshLoading || !dshPrompt.trim(),
|
|
542
|
+
title: 'Отправить агенту',
|
|
543
|
+
onClick: handleStartWithDSH
|
|
544
|
+
},
|
|
545
|
+
React.createElement('span', {
|
|
546
|
+
style: { display: 'flex', alignItems: 'center' },
|
|
547
|
+
dangerouslySetInnerHTML: { __html: ICON_SEND }
|
|
548
|
+
})
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
)
|
|
552
|
+
),
|
|
553
|
+
React.createElement('div', { className: 'dsh-cron-modal-foot' },
|
|
554
|
+
React.createElement('button', {
|
|
555
|
+
type: 'button',
|
|
556
|
+
className: 'dsh-cron-btn-secondary',
|
|
557
|
+
disabled: dshLoading,
|
|
558
|
+
onClick: () => setDshModalOpen(false)
|
|
559
|
+
}, 'Отмена')
|
|
560
|
+
)
|
|
561
|
+
)
|
|
380
562
|
)
|
|
381
563
|
);
|
|
382
564
|
}
|
|
@@ -552,13 +734,11 @@ window.__ModuleLoader__.load({
|
|
|
552
734
|
ensureStyles();
|
|
553
735
|
const toggle = createToggle();
|
|
554
736
|
|
|
555
|
-
// Карточка в Настройках -> Плагины
|
|
556
737
|
const cardSlot = registerFirst(ctx, [
|
|
557
738
|
{ name: 'settings.plugin.item', key: NS, inject: () => ({ ctx, toggle }) },
|
|
558
739
|
{ name: 'settings.section', id: '@goodandready/dsh-cron', order: 34, label: () => 'Cron Задачи', inject: () => ({ ctx, toggle }) }
|
|
559
740
|
], CronSettingsCard);
|
|
560
741
|
|
|
561
|
-
// Чип в шапке диалога
|
|
562
742
|
const chipSlot = registerFirst(ctx, [
|
|
563
743
|
{ name: 'conversation.session.header.utilities', id: '@goodandready/dsh-cron.chip', order: 27, inject: () => ({ ctx, toggle }) }
|
|
564
744
|
], () => React.createElement('button', {
|
|
@@ -583,4 +763,4 @@ window.__ModuleLoader__.load({
|
|
|
583
763
|
module.exports = { apply, inject: ['slots', 'locale', 'settingsScope'] };
|
|
584
764
|
return module.exports;
|
|
585
765
|
}
|
|
586
|
-
})
|
|
766
|
+
})
|
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');
|