openclacky 1.3.2 → 1.3.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/Dockerfile +3 -0
- data/README.md +1 -1
- data/README_JA.md +237 -0
- data/lib/clacky/agent/session_serializer.rb +49 -5
- data/lib/clacky/agent/time_machine.rb +247 -26
- data/lib/clacky/agent.rb +12 -1
- data/lib/clacky/agent_config.rb +14 -2
- data/lib/clacky/default_agents/_panels/git/panel.js +201 -0
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +640 -0
- data/lib/clacky/default_agents/coding/profile.yml +3 -0
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_skills/cron-task-creator/SKILL.md +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +6 -4
- data/lib/clacky/default_skills/media-gen/SKILL.md +30 -6
- data/lib/clacky/media/openai_compat.rb +64 -1
- data/lib/clacky/media/output_dir.rb +43 -0
- data/lib/clacky/message_history.rb +9 -0
- data/lib/clacky/server/channel/channel_manager.rb +26 -0
- data/lib/clacky/server/git_panel.rb +115 -0
- data/lib/clacky/server/http_server.rb +497 -12
- data/lib/clacky/server/server_master.rb +6 -4
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +473 -60
- data/lib/clacky/web/app.js +30 -7
- data/lib/clacky/web/components/code-editor.js +197 -0
- data/lib/clacky/web/{notify.js → components/notify.js} +1 -1
- data/lib/clacky/web/core/aside.js +112 -0
- data/lib/clacky/web/core/ext.js +387 -0
- data/lib/clacky/web/features/backup/store.js +92 -0
- data/lib/clacky/web/features/backup/view.js +94 -0
- data/lib/clacky/web/features/billing/store.js +163 -0
- data/lib/clacky/web/{billing.js → features/billing/view.js} +132 -240
- data/lib/clacky/web/features/brand/store.js +110 -0
- data/lib/clacky/web/{brand.js → features/brand/view.js} +49 -199
- data/lib/clacky/web/features/channels/store.js +103 -0
- data/lib/clacky/web/{channels.js → features/channels/view.js} +50 -127
- data/lib/clacky/web/features/creator/store.js +81 -0
- data/lib/clacky/web/{creator.js → features/creator/view.js} +53 -102
- data/lib/clacky/web/features/mcp/store.js +158 -0
- data/lib/clacky/web/{mcp.js → features/mcp/view.js} +57 -134
- data/lib/clacky/web/features/model-tester/store.js +77 -0
- data/lib/clacky/web/features/model-tester/view.js +7 -0
- data/lib/clacky/web/features/profile/store.js +170 -0
- data/lib/clacky/web/{profile.js → features/profile/view.js} +94 -144
- data/lib/clacky/web/features/share/store.js +145 -0
- data/lib/clacky/web/{share.js → features/share/view.js} +66 -202
- data/lib/clacky/web/features/skills/store.js +303 -0
- data/lib/clacky/web/features/skills/view.js +550 -0
- data/lib/clacky/web/features/tasks/store.js +135 -0
- data/lib/clacky/web/features/tasks/view.js +241 -0
- data/lib/clacky/web/features/trash/store.js +242 -0
- data/lib/clacky/web/{trash.js → features/trash/view.js} +102 -293
- data/lib/clacky/web/features/version/store.js +165 -0
- data/lib/clacky/web/features/version/view.js +323 -0
- data/lib/clacky/web/features/workspace/store.js +99 -0
- data/lib/clacky/web/features/workspace/view.js +305 -0
- data/lib/clacky/web/i18n.js +56 -6
- data/lib/clacky/web/index.html +117 -58
- data/lib/clacky/web/sessions.js +221 -25
- data/lib/clacky/web/settings.js +118 -22
- data/lib/clacky/web/skills.js +3 -863
- data/lib/clacky/web/vendor/codemirror/codemirror.min.js +29 -0
- data/lib/clacky.rb +1 -0
- metadata +45 -20
- data/lib/clacky/web/backup.js +0 -119
- data/lib/clacky/web/model-tester.js +0 -66
- data/lib/clacky/web/tasks.js +0 -373
- data/lib/clacky/web/version.js +0 -449
- data/lib/clacky/web/workspace.js +0 -316
- /data/lib/clacky/web/{notify.mp3 → assets/notify.mp3} +0 -0
- /data/lib/clacky/web/{datepicker.js → components/datepicker.js} +0 -0
- /data/lib/clacky/web/{onboard.js → components/onboard.js} +0 -0
- /data/lib/clacky/web/{sidebar.js → components/sidebar.js} +0 -0
- /data/lib/clacky/web/{marked.min.js → vendor/marked/marked.min.js} +0 -0
data/lib/clacky/web/tasks.js
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
// ── Tasks — task/schedule state, rendering, CRUD ──────────────────────────
|
|
2
|
-
//
|
|
3
|
-
// Responsibilities:
|
|
4
|
-
// - Single source of truth for tasks + schedules data
|
|
5
|
-
// - Render the "Scheduled Tasks" entry in the sidebar
|
|
6
|
-
// - Show/render the task list table in the main panel
|
|
7
|
-
// - CRUD: load, run, editInSession (creates new session), delete
|
|
8
|
-
//
|
|
9
|
-
// Panel switching is delegated to Router — Tasks only manages data + rendering.
|
|
10
|
-
//
|
|
11
|
-
// Depends on: WS (ws.js), Sessions (sessions.js), Router (app.js),
|
|
12
|
-
// global $ / escapeHtml helpers
|
|
13
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
const Tasks = (() => {
|
|
16
|
-
// ── Private state ──────────────────────────────────────────────────────
|
|
17
|
-
let _tasks = []; // [{ name, content, cron, enabled, scheduled }]
|
|
18
|
-
|
|
19
|
-
// ── Private helpers ────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
function _humanCron(cron) {
|
|
22
|
-
if (!cron) return cron;
|
|
23
|
-
const parts = cron.trim().split(/\s+/);
|
|
24
|
-
if (parts.length !== 5) return cron;
|
|
25
|
-
const [min, hour, dom, month, dow] = parts;
|
|
26
|
-
|
|
27
|
-
const isAny = v => v === "*";
|
|
28
|
-
const isNum = v => /^\d+$/.test(v);
|
|
29
|
-
const pad = n => String(n).padStart(2, "0");
|
|
30
|
-
|
|
31
|
-
const lang = (typeof I18n !== "undefined" && I18n.lang()) || "zh";
|
|
32
|
-
const isZh = lang === "zh";
|
|
33
|
-
|
|
34
|
-
const dowNames = isZh
|
|
35
|
-
? ["周日","周一","周二","周三","周四","周五","周六"]
|
|
36
|
-
: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
|
|
37
|
-
|
|
38
|
-
const monthNames = isZh
|
|
39
|
-
? ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"]
|
|
40
|
-
: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
41
|
-
|
|
42
|
-
const timeStr = (isNum(hour) && isNum(min))
|
|
43
|
-
? `${pad(hour)}:${pad(min)}`
|
|
44
|
-
: null;
|
|
45
|
-
|
|
46
|
-
// Every N minutes
|
|
47
|
-
if (min.startsWith("*/") && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
48
|
-
const n = min.slice(2);
|
|
49
|
-
return isZh ? `每 ${n} 分钟` : `Every ${n} min`;
|
|
50
|
-
}
|
|
51
|
-
// Every N hours
|
|
52
|
-
if ((isAny(min) || isNum(min)) && hour.startsWith("*/") && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
53
|
-
const n = hour.slice(2);
|
|
54
|
-
return isZh ? `每 ${n} 小时` : `Every ${n} hr`;
|
|
55
|
-
}
|
|
56
|
-
// Every minute
|
|
57
|
-
if (isAny(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
58
|
-
return isZh ? "每分钟" : "Every minute";
|
|
59
|
-
}
|
|
60
|
-
// Every hour at :MM
|
|
61
|
-
if (isNum(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
62
|
-
return isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
|
|
63
|
-
}
|
|
64
|
-
// Specific day-of-week
|
|
65
|
-
if (timeStr && isAny(dom) && isAny(month) && isNum(dow)) {
|
|
66
|
-
const d = dowNames[parseInt(dow, 10)] || dow;
|
|
67
|
-
return isZh ? `每${d} ${timeStr}` : `${d} ${timeStr}`;
|
|
68
|
-
}
|
|
69
|
-
// Weekdays (1-5)
|
|
70
|
-
if (timeStr && isAny(dom) && isAny(month) && dow === "1-5") {
|
|
71
|
-
return isZh ? `工作日 ${timeStr}` : `Weekdays ${timeStr}`;
|
|
72
|
-
}
|
|
73
|
-
// Weekends (0,6 or 6,0)
|
|
74
|
-
if (timeStr && isAny(dom) && isAny(month) && (dow === "0,6" || dow === "6,0")) {
|
|
75
|
-
return isZh ? `周末 ${timeStr}` : `Weekends ${timeStr}`;
|
|
76
|
-
}
|
|
77
|
-
// Every day at HH:MM
|
|
78
|
-
if (timeStr && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
79
|
-
return isZh ? `每天 ${timeStr}` : `Daily ${timeStr}`;
|
|
80
|
-
}
|
|
81
|
-
// Specific day of month
|
|
82
|
-
if (timeStr && isNum(dom) && isAny(month) && isAny(dow)) {
|
|
83
|
-
return isZh ? `每月 ${dom} 日 ${timeStr}` : `Monthly day ${dom} ${timeStr}`;
|
|
84
|
-
}
|
|
85
|
-
// Specific month + day
|
|
86
|
-
if (timeStr && isNum(dom) && isNum(month) && isAny(dow)) {
|
|
87
|
-
const m = monthNames[parseInt(month, 10) - 1] || month;
|
|
88
|
-
return isZh ? `${m}${dom}日 ${timeStr}` : `${m} ${dom} ${timeStr}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return cron;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Render a single task row in the main panel table. */
|
|
95
|
-
function _renderTaskRow(t) {
|
|
96
|
-
const row = document.createElement("div");
|
|
97
|
-
row.className = "task-card";
|
|
98
|
-
row.dataset.name = t.name;
|
|
99
|
-
|
|
100
|
-
const isPaused = t.scheduled && t.enabled === false;
|
|
101
|
-
row.classList.toggle("task-card-paused", isPaused);
|
|
102
|
-
|
|
103
|
-
const schedLabel = t.scheduled
|
|
104
|
-
? `<span class="task-card-cron" title="${escapeHtml(t.cron)}">${escapeHtml(_humanCron(t.cron))}</span>`
|
|
105
|
-
: `<span class="task-card-cron task-card-cron-manual">${I18n.t("tasks.manual")}</span>`;
|
|
106
|
-
|
|
107
|
-
const pausedBadge = isPaused
|
|
108
|
-
? `<span class="task-card-badge task-card-badge-paused">${I18n.t("tasks.paused")}</span>`
|
|
109
|
-
: "";
|
|
110
|
-
|
|
111
|
-
const content = t.content || "";
|
|
112
|
-
const isTruncated = content.trim().length > 0;
|
|
113
|
-
const previewText = escapeHtml(content.replace(/\s+/g, " ").trim()) || escapeHtml(I18n.t("tasks.empty"));
|
|
114
|
-
|
|
115
|
-
const toggleBtnHtml = t.scheduled ? (isPaused
|
|
116
|
-
? `<button class="task-action-btn task-btn-toggle task-btn-resume">
|
|
117
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
118
|
-
<polygon points="6 3 20 12 6 21 6 3"/>
|
|
119
|
-
</svg>
|
|
120
|
-
<span>${I18n.t("tasks.btn.resume")}</span>
|
|
121
|
-
</button>`
|
|
122
|
-
: `<button class="task-action-btn task-btn-toggle task-btn-pause">
|
|
123
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
124
|
-
<rect x="6" y="4" width="4" height="16" rx="1"/>
|
|
125
|
-
<rect x="14" y="4" width="4" height="16" rx="1"/>
|
|
126
|
-
</svg>
|
|
127
|
-
<span>${I18n.t("tasks.btn.pause")}</span>
|
|
128
|
-
</button>`
|
|
129
|
-
) : "";
|
|
130
|
-
|
|
131
|
-
row.innerHTML = `
|
|
132
|
-
<div class="task-card-main">
|
|
133
|
-
<div class="task-card-icon">
|
|
134
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
135
|
-
<circle cx="12" cy="12" r="10"/>
|
|
136
|
-
<polyline points="12 6 12 12 16 14"/>
|
|
137
|
-
</svg>
|
|
138
|
-
</div>
|
|
139
|
-
<div class="task-card-info">
|
|
140
|
-
<div class="task-card-title-row">
|
|
141
|
-
<span class="task-card-name">${escapeHtml(t.name)}</span>
|
|
142
|
-
${pausedBadge}
|
|
143
|
-
${schedLabel}
|
|
144
|
-
</div>
|
|
145
|
-
<div class="task-card-preview${isTruncated ? " task-card-preview-expandable" : ""}">${previewText}</div>
|
|
146
|
-
</div>
|
|
147
|
-
<div class="task-card-actions">
|
|
148
|
-
<button class="task-run-btn task-btn-run" title="${I18n.t("tasks.btn.run")}">
|
|
149
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
150
|
-
<polygon points="6 3 20 12 6 21 6 3"/>
|
|
151
|
-
</svg>
|
|
152
|
-
<span>${I18n.t("tasks.btn.run")}</span>
|
|
153
|
-
</button>
|
|
154
|
-
${toggleBtnHtml}
|
|
155
|
-
<button class="task-action-btn task-btn-edit">
|
|
156
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
157
|
-
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>
|
|
158
|
-
<path d="m15 5 4 4"/>
|
|
159
|
-
</svg>
|
|
160
|
-
<span>${I18n.t("tasks.btn.edit")}</span>
|
|
161
|
-
</button>
|
|
162
|
-
<button class="task-action-btn task-btn-del">
|
|
163
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
164
|
-
<path d="M3 6h18"/><path d="M19 6l-1 14H6L5 6"/><path d="M8 6V4h8v2"/>
|
|
165
|
-
</svg>
|
|
166
|
-
<span>${I18n.t("tasks.btn.delete")}</span>
|
|
167
|
-
</button>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
${isTruncated ? `<div class="task-card-detail" hidden><pre class="task-card-detail-content">${escapeHtml(content)}</pre></div>` : ""}`;
|
|
171
|
-
|
|
172
|
-
row.querySelector(".task-btn-run").addEventListener("click", e => {
|
|
173
|
-
e.stopPropagation();
|
|
174
|
-
Tasks.run(t.name);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (isTruncated) {
|
|
178
|
-
const previewEl = row.querySelector(".task-card-preview");
|
|
179
|
-
const detailEl = row.querySelector(".task-card-detail");
|
|
180
|
-
previewEl.addEventListener("click", e => {
|
|
181
|
-
e.stopPropagation();
|
|
182
|
-
const expanded = !detailEl.hidden;
|
|
183
|
-
detailEl.hidden = expanded;
|
|
184
|
-
row.classList.toggle("task-card-expanded", !expanded);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
const toggleBtn = row.querySelector(".task-btn-toggle");
|
|
188
|
-
if (toggleBtn) {
|
|
189
|
-
toggleBtn.addEventListener("click", e => {
|
|
190
|
-
e.stopPropagation();
|
|
191
|
-
Tasks.toggleEnabled(t.name, isPaused); // isPaused=true means we want to enable
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
row.querySelector(".task-btn-edit").addEventListener("click", e => {
|
|
195
|
-
e.stopPropagation();
|
|
196
|
-
Tasks.editInSession(t.name);
|
|
197
|
-
});
|
|
198
|
-
row.querySelector(".task-btn-del").addEventListener("click", e => {
|
|
199
|
-
e.stopPropagation();
|
|
200
|
-
Tasks.delete(t.name);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
return row;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ── Public API ─────────────────────────────────────────────────────────
|
|
207
|
-
return {
|
|
208
|
-
|
|
209
|
-
// ── Data ─────────────────────────────────────────────────────────────
|
|
210
|
-
|
|
211
|
-
/** Fetch cron tasks from server; re-render sidebar + panel if open. */
|
|
212
|
-
async load() {
|
|
213
|
-
try {
|
|
214
|
-
const res = await fetch("/api/cron-tasks");
|
|
215
|
-
const data = await res.json();
|
|
216
|
-
_tasks = data.cron_tasks || [];
|
|
217
|
-
Tasks.renderSection();
|
|
218
|
-
if (Router.current === "tasks") Tasks.renderTable();
|
|
219
|
-
} catch (e) {
|
|
220
|
-
console.error("[Tasks] load failed", e);
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
// ── Router interface ──────────────────────────────────────────────────
|
|
225
|
-
|
|
226
|
-
/** Called by Router when the tasks panel becomes active. */
|
|
227
|
-
onPanelShow() {
|
|
228
|
-
Tasks.load();
|
|
229
|
-
const btn = $("btn-create-task");
|
|
230
|
-
if (btn) btn.onclick = () => Tasks.createInSession();
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
// ── Sidebar rendering ─────────────────────────────────────────────────
|
|
234
|
-
|
|
235
|
-
renderSection() {
|
|
236
|
-
// Sidebar item is static in HTML — just update the label text.
|
|
237
|
-
const labelEl = $("tasks-sidebar-label");
|
|
238
|
-
if (!labelEl) return;
|
|
239
|
-
labelEl.textContent = I18n.t("sidebar.tasks");
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
// ── Main panel table ──────────────────────────────────────────────────
|
|
243
|
-
|
|
244
|
-
/** Render all tasks as rows in the main panel table. */
|
|
245
|
-
renderTable() {
|
|
246
|
-
const table = $("task-list-table");
|
|
247
|
-
table.innerHTML = "";
|
|
248
|
-
|
|
249
|
-
if (_tasks.length === 0) {
|
|
250
|
-
const empty = document.createElement("div");
|
|
251
|
-
empty.className = "task-table-empty";
|
|
252
|
-
empty.innerHTML = `
|
|
253
|
-
<p>${I18n.t("tasks.noScheduled")}</p>
|
|
254
|
-
<button class="task-create-btn" id="btn-create-task-empty">
|
|
255
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
256
|
-
<path d="M5 12h14"/>
|
|
257
|
-
<path d="M12 5v14"/>
|
|
258
|
-
</svg> ${I18n.t("tasks.btn.createTask")}
|
|
259
|
-
</button>`;
|
|
260
|
-
table.appendChild(empty);
|
|
261
|
-
const btn = table.querySelector("#btn-create-task-empty");
|
|
262
|
-
if (btn) btn.addEventListener("click", () => Tasks.createInSession());
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
_tasks.forEach(t => table.appendChild(_renderTaskRow(t)));
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
// ── CRUD ─────────────────────────────────────────────────────────────
|
|
270
|
-
|
|
271
|
-
async run(name) {
|
|
272
|
-
const res = await fetch(`/api/cron-tasks/${encodeURIComponent(name)}/run`, {
|
|
273
|
-
method: "POST"
|
|
274
|
-
});
|
|
275
|
-
const data = await res.json();
|
|
276
|
-
if (!res.ok) { alert(I18n.t("tasks.runError") + (data.error || "unknown")); return; }
|
|
277
|
-
|
|
278
|
-
if (data.session) {
|
|
279
|
-
await Tasks.load();
|
|
280
|
-
Sessions.add(data.session);
|
|
281
|
-
Sessions.renderList();
|
|
282
|
-
Sessions.setPendingRunTask(data.session.id);
|
|
283
|
-
Sessions.select(data.session.id);
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
|
|
287
|
-
/** Toggle a scheduled task's enabled flag. `wasPaused` is the current
|
|
288
|
-
* paused-state before the click; if true, we resume (enabled: true).
|
|
289
|
-
* Optimistic: we update local state first, then reload on success. */
|
|
290
|
-
async toggleEnabled(name, wasPaused) {
|
|
291
|
-
const nextEnabled = wasPaused; // paused → resume(true); running → pause(false)
|
|
292
|
-
const res = await fetch(`/api/cron-tasks/${encodeURIComponent(name)}`, {
|
|
293
|
-
method: "PATCH",
|
|
294
|
-
headers: { "Content-Type": "application/json" },
|
|
295
|
-
body: JSON.stringify({ enabled: nextEnabled })
|
|
296
|
-
});
|
|
297
|
-
if (!res.ok) {
|
|
298
|
-
let msg = "";
|
|
299
|
-
try { msg = (await res.json()).error || ""; } catch (_) {}
|
|
300
|
-
alert(I18n.t("tasks.toggleError") + (msg ? " " + msg : ""));
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
await Tasks.load();
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
/** Create a new task by opening a new session and sending /create-task. */
|
|
307
|
-
async createInSession() {
|
|
308
|
-
const maxN = Sessions.all.reduce((max, s) => {
|
|
309
|
-
const m = s.name.match(/^Session (\d+)$/);
|
|
310
|
-
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
311
|
-
}, 0);
|
|
312
|
-
const res = await fetch("/api/sessions", {
|
|
313
|
-
method: "POST",
|
|
314
|
-
headers: { "Content-Type": "application/json" },
|
|
315
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "manual" })
|
|
316
|
-
});
|
|
317
|
-
const data = await res.json();
|
|
318
|
-
if (!res.ok) { alert(I18n.t("tasks.sessionError") + (data.error || "unknown")); return; }
|
|
319
|
-
|
|
320
|
-
const session = data.session;
|
|
321
|
-
if (!session) return;
|
|
322
|
-
|
|
323
|
-
// If WS is not yet connected (e.g. called during onboarding), boot the UI
|
|
324
|
-
// first so WS connects, then use setPendingMessage so the command is sent
|
|
325
|
-
// once the socket is ready. This mirrors Onboard._startSoulSession().
|
|
326
|
-
if (!WS.ready) {
|
|
327
|
-
WS.connect();
|
|
328
|
-
Skills.load();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
Sessions.add(session);
|
|
332
|
-
Sessions.renderList();
|
|
333
|
-
Sessions.setPendingMessage(session.id, "/cron-task-creator");
|
|
334
|
-
Sessions.select(session.id);
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
/** Edit a task by creating a new session and auto-sending the edit command. */
|
|
338
|
-
async editInSession(name) {
|
|
339
|
-
const maxN = Sessions.all.reduce((max, s) => {
|
|
340
|
-
const m = s.name.match(/^Session (\d+)$/);
|
|
341
|
-
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
342
|
-
}, 0);
|
|
343
|
-
const res = await fetch("/api/sessions", {
|
|
344
|
-
method: "POST",
|
|
345
|
-
headers: { "Content-Type": "application/json" },
|
|
346
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "manual" })
|
|
347
|
-
});
|
|
348
|
-
const data = await res.json();
|
|
349
|
-
if (!res.ok) { alert("Error creating session: " + (data.error || "unknown")); return; }
|
|
350
|
-
|
|
351
|
-
const session = data.session;
|
|
352
|
-
if (!session) return;
|
|
353
|
-
|
|
354
|
-
if (!WS.ready) {
|
|
355
|
-
WS.connect();
|
|
356
|
-
Skills.load();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
Sessions.add(session);
|
|
360
|
-
Sessions.renderList();
|
|
361
|
-
Sessions.setPendingMessage(session.id, `/cron-task-creator I'm editing ${name} task`);
|
|
362
|
-
Sessions.select(session.id);
|
|
363
|
-
},
|
|
364
|
-
|
|
365
|
-
async delete(name) {
|
|
366
|
-
if (!confirm(I18n.t("tasks.confirmDelete", { name }))) return;
|
|
367
|
-
const res = await fetch(`/api/cron-tasks/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
368
|
-
if (!res.ok) { alert(I18n.t("tasks.deleteError")); return; }
|
|
369
|
-
|
|
370
|
-
await Tasks.load();
|
|
371
|
-
},
|
|
372
|
-
};
|
|
373
|
-
})();
|