@kevin5251984/guild 0.2.21 → 0.2.23
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/cordis.yml +2 -0
- package/package.json +1 -1
- package/src/cron-schedule.ts +388 -0
- package/src/cron.ts +403 -0
- package/src/db.ts +135 -0
- package/src/generate.ts +9 -0
- package/src/handlers.ts +5 -0
- package/src/harness.ts +25 -5
- package/src/plugins/cron.ts +26 -0
- package/src/public/chat.css +112 -9
- package/src/public/chat.html +354 -19
- package/src/public/i18n.js +20 -0
- package/src/public/md.js +99 -22
- package/src/public/mobile.css +12 -2
- package/src/public/mobile.html +23 -5
- package/src/router.ts +97 -1
- package/src/store.ts +22 -1
- package/src/subagent.ts +3 -0
- package/src/tools.ts +57 -1
package/src/public/chat.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
|
|
9
9
|
<link rel="icon" href="/favicon-16.png" type="image/png" sizes="16x16" />
|
|
10
10
|
<title>Guild — 大廳</title>
|
|
11
|
-
<link rel="stylesheet" href="/chat.css?v=
|
|
11
|
+
<link rel="stylesheet" href="/chat.css?v=nav-fold" />
|
|
12
12
|
<script src="/i18n.js"></script>
|
|
13
13
|
<script src="/md.js"></script>
|
|
14
14
|
</head>
|
|
@@ -18,17 +18,38 @@
|
|
|
18
18
|
<div class="side-top">
|
|
19
19
|
<input class="search" id="filter" placeholder="搜尋" data-i18n-placeholder="search" />
|
|
20
20
|
</div>
|
|
21
|
-
<div class="
|
|
22
|
-
<
|
|
23
|
-
|
|
21
|
+
<div class="nav-sec" data-nav-sec="channels">
|
|
22
|
+
<div class="section-h">
|
|
23
|
+
<button type="button" class="sec-fold" data-fold="channels" aria-expanded="true" aria-controls="channels">
|
|
24
|
+
<span class="sec-chev" aria-hidden="true">▾</span>
|
|
25
|
+
<span data-i18n="channels">委託</span>
|
|
26
|
+
</button>
|
|
27
|
+
<button type="button" class="add-ch" id="toggle-create" title="新委託" data-i18n-title="newChannel">+</button>
|
|
28
|
+
</div>
|
|
29
|
+
<form class="create-row" id="create-row">
|
|
30
|
+
<input id="new-channel" placeholder="委託名稱" data-i18n-placeholder="newChannelName" />
|
|
31
|
+
<button type="submit" data-i18n="create">建立</button>
|
|
32
|
+
</form>
|
|
33
|
+
<ul class="navlist" id="channels"></ul>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="nav-sec" data-nav-sec="dms">
|
|
36
|
+
<div class="section-h">
|
|
37
|
+
<button type="button" class="sec-fold" data-fold="dms" aria-expanded="true" aria-controls="dms">
|
|
38
|
+
<span class="sec-chev" aria-hidden="true">▾</span>
|
|
39
|
+
<span data-i18n="dms">密談</span>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
<ul class="navlist" id="dms"></ul>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="nav-sec" data-nav-sec="cron">
|
|
45
|
+
<div class="section-h">
|
|
46
|
+
<button type="button" class="sec-fold" data-fold="cron" aria-expanded="true" aria-controls="cron-nav">
|
|
47
|
+
<span class="sec-chev" aria-hidden="true">▾</span>
|
|
48
|
+
<span data-i18n="cron.title">排程</span>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
<ul class="navlist" id="cron-nav"></ul>
|
|
24
52
|
</div>
|
|
25
|
-
<form class="create-row" id="create-row">
|
|
26
|
-
<input id="new-channel" placeholder="委託名稱" data-i18n-placeholder="newChannelName" />
|
|
27
|
-
<button type="submit" data-i18n="create">建立</button>
|
|
28
|
-
</form>
|
|
29
|
-
<ul class="navlist" id="channels"></ul>
|
|
30
|
-
<div class="section-h" data-i18n="dms">密談</div>
|
|
31
|
-
<ul class="navlist" id="dms"></ul>
|
|
32
53
|
<div class="side-foot">
|
|
33
54
|
<a href="/studio" data-i18n="nav.studio">編制</a>
|
|
34
55
|
<a href="/library" data-i18n="nav.library">工坊</a>
|
|
@@ -116,6 +137,7 @@
|
|
|
116
137
|
<button type="button" class="attach-item" data-attach="rules"><span class="attach-ico">☰</span><span data-i18n="attach.rules">規則</span></button>
|
|
117
138
|
<button type="button" class="attach-item" data-attach="term"><span class="attach-ico">>_</span><span data-i18n="attach.term">終端</span></button>
|
|
118
139
|
<button type="button" class="attach-item" data-attach="tree"><span class="attach-ico">🗺</span><span data-i18n="attach.tree">工作區</span></button>
|
|
140
|
+
<button type="button" class="attach-item" data-attach="cron"><span class="attach-ico">⏱</span><span data-i18n="attach.cron">排程</span></button>
|
|
119
141
|
<button type="button" class="attach-item" data-attach="slash"><span class="attach-ico">/</span><span data-i18n="attach.slash">指令</span></button>
|
|
120
142
|
</div>
|
|
121
143
|
</div>
|
|
@@ -230,6 +252,19 @@
|
|
|
230
252
|
<button type="button" id="channel-md-save" data-i18n="save">儲存</button>
|
|
231
253
|
</div>
|
|
232
254
|
</dialog>
|
|
255
|
+
<dialog id="cron-dialog">
|
|
256
|
+
<h2 data-i18n="cron.title">排程</h2>
|
|
257
|
+
<p data-i18n="cron.help">新增會請這席呼叫 cronjob。時間用自然語言寫即可,例如「每10分鐘跟我說一次 Hi」。guildd 每 60 秒看一次;到點後這席開一輪新的 @handle,prompt 必須自包含。</p>
|
|
258
|
+
<div id="cron-list" class="cron-list"></div>
|
|
259
|
+
<label for="cron-bot" data-i18n="cron.seat">席</label>
|
|
260
|
+
<select id="cron-bot"></select>
|
|
261
|
+
<label for="cron-ask" data-i18n="cron.ask">要排什麼</label>
|
|
262
|
+
<textarea id="cron-ask" rows="4" placeholder="每10分鐘跟我說一次 Hi" data-i18n-placeholder="cron.askPh"></textarea>
|
|
263
|
+
<div class="dialog-actions">
|
|
264
|
+
<button type="button" class="ghost" id="cron-cancel" data-i18n="cancel">取消</button>
|
|
265
|
+
<button type="button" id="cron-add" data-i18n="cron.add">請他排</button>
|
|
266
|
+
</div>
|
|
267
|
+
</dialog>
|
|
233
268
|
<dialog id="bot-memory">
|
|
234
269
|
<h2 data-i18n="memory.bot">MEMORY.md</h2>
|
|
235
270
|
<p data-i18n="memory.botHelp">這名冒險者學會的站立筆記。有用的回合後會自動更新。</p>
|
|
@@ -320,6 +355,19 @@
|
|
|
320
355
|
return new Set();
|
|
321
356
|
}
|
|
322
357
|
})(),
|
|
358
|
+
cronJobs: [],
|
|
359
|
+
navFold: (function () {
|
|
360
|
+
try {
|
|
361
|
+
const raw = JSON.parse(localStorage.getItem("guild-nav-fold") || "{}");
|
|
362
|
+
return {
|
|
363
|
+
channels: Boolean(raw.channels),
|
|
364
|
+
dms: Boolean(raw.dms),
|
|
365
|
+
cron: Boolean(raw.cron),
|
|
366
|
+
};
|
|
367
|
+
} catch {
|
|
368
|
+
return { channels: false, dms: false, cron: false };
|
|
369
|
+
}
|
|
370
|
+
})(),
|
|
323
371
|
};
|
|
324
372
|
|
|
325
373
|
function hue(key) {
|
|
@@ -1422,6 +1470,65 @@
|
|
|
1422
1470
|
const nb = Number.isFinite(tb) ? tb : 0;
|
|
1423
1471
|
return nb - na;
|
|
1424
1472
|
}
|
|
1473
|
+
function navSecFolded(sec) {
|
|
1474
|
+
const q = (document.getElementById("filter").value || "").trim();
|
|
1475
|
+
if (q) return false;
|
|
1476
|
+
return Boolean(state.navFold[sec]);
|
|
1477
|
+
}
|
|
1478
|
+
function applyNavFold() {
|
|
1479
|
+
["channels", "dms", "cron"].forEach((sec) => {
|
|
1480
|
+
const el = document.querySelector('[data-nav-sec="' + sec + '"]');
|
|
1481
|
+
if (!el) return;
|
|
1482
|
+
const folded = navSecFolded(sec);
|
|
1483
|
+
el.classList.toggle("folded", folded);
|
|
1484
|
+
const btn = el.querySelector("[data-fold]");
|
|
1485
|
+
if (btn) btn.setAttribute("aria-expanded", folded ? "false" : "true");
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function saveNavFold() {
|
|
1489
|
+
try {
|
|
1490
|
+
localStorage.setItem("guild-nav-fold", JSON.stringify(state.navFold));
|
|
1491
|
+
} catch {
|
|
1492
|
+
/* ignore quota */
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
function cronJobHref(job) {
|
|
1496
|
+
const id = String((job && job.roomId) || "");
|
|
1497
|
+
if ((state.channels || []).some((ch) => ch.id === id)) {
|
|
1498
|
+
return "#c/" + encodeURIComponent(id);
|
|
1499
|
+
}
|
|
1500
|
+
if (id.indexOf("dm-") === 0) {
|
|
1501
|
+
return "#d/" + encodeURIComponent(id.slice(3));
|
|
1502
|
+
}
|
|
1503
|
+
return "#c/" + encodeURIComponent(id);
|
|
1504
|
+
}
|
|
1505
|
+
function cronNavPreview(job) {
|
|
1506
|
+
const bot = botById(job.botId);
|
|
1507
|
+
const handle = bot ? bot.handle : "bot";
|
|
1508
|
+
return t("cron.navWhere", {
|
|
1509
|
+
handle: handle,
|
|
1510
|
+
schedule: job.schedule || "",
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
async function loadNavCron() {
|
|
1514
|
+
try {
|
|
1515
|
+
const res = await fetch("/cron");
|
|
1516
|
+
const body = await res.json().catch(() => ({}));
|
|
1517
|
+
const jobs = Array.isArray(body.jobs) ? body.jobs : [];
|
|
1518
|
+
const sig = jobs
|
|
1519
|
+
.map((job) =>
|
|
1520
|
+
[job.id, job.nextRunAt, job.paused ? "1" : "0", job.running ? "1" : "0", job.name].join(":"),
|
|
1521
|
+
)
|
|
1522
|
+
.join("|");
|
|
1523
|
+
if (sig === state.cronSig) return;
|
|
1524
|
+
state.cronSig = sig;
|
|
1525
|
+
state.cronJobs = jobs;
|
|
1526
|
+
} catch {
|
|
1527
|
+
if (state.cronSig != null) return;
|
|
1528
|
+
state.cronJobs = [];
|
|
1529
|
+
}
|
|
1530
|
+
renderNav();
|
|
1531
|
+
}
|
|
1425
1532
|
function renderNav() {
|
|
1426
1533
|
const q = document.getElementById("filter").value.toLowerCase();
|
|
1427
1534
|
const all = state.channels || [];
|
|
@@ -1544,6 +1651,53 @@
|
|
|
1544
1651
|
});
|
|
1545
1652
|
})
|
|
1546
1653
|
.join("");
|
|
1654
|
+
const cronBox = document.getElementById("cron-nav");
|
|
1655
|
+
if (cronBox) {
|
|
1656
|
+
const jobs = (state.cronJobs || []).filter((job) => {
|
|
1657
|
+
if (!q) return true;
|
|
1658
|
+
const bot = botById(job.botId);
|
|
1659
|
+
const hay = [
|
|
1660
|
+
job.name,
|
|
1661
|
+
job.schedule,
|
|
1662
|
+
job.prompt,
|
|
1663
|
+
bot && bot.handle,
|
|
1664
|
+
bot && bot.name,
|
|
1665
|
+
]
|
|
1666
|
+
.filter(Boolean)
|
|
1667
|
+
.join(" ")
|
|
1668
|
+
.toLowerCase();
|
|
1669
|
+
return hay.includes(q);
|
|
1670
|
+
});
|
|
1671
|
+
cronBox.innerHTML = jobs.length
|
|
1672
|
+
? jobs
|
|
1673
|
+
.map((job) => {
|
|
1674
|
+
const bot = botById(job.botId);
|
|
1675
|
+
const onRoom =
|
|
1676
|
+
cronJobHref(job) ===
|
|
1677
|
+
(state.kind === "dm"
|
|
1678
|
+
? "#d/" + encodeURIComponent(state.id)
|
|
1679
|
+
: "#c/" + encodeURIComponent(state.id));
|
|
1680
|
+
return navRowHtml({
|
|
1681
|
+
href: cronJobHref(job),
|
|
1682
|
+
on: onRoom,
|
|
1683
|
+
av: bot
|
|
1684
|
+
? '<span class="avatar" style="background:' +
|
|
1685
|
+
hue(bot.handle) +
|
|
1686
|
+
'">' +
|
|
1687
|
+
avatarFace(bot) +
|
|
1688
|
+
"</span>"
|
|
1689
|
+
: "",
|
|
1690
|
+
name: job.name || t("cron.title"),
|
|
1691
|
+
updatedAt: job.nextRunAt,
|
|
1692
|
+
preview: cronNavPreview(job),
|
|
1693
|
+
busy: Boolean(job.running),
|
|
1694
|
+
chip: job.paused ? t("cron.pause") : "",
|
|
1695
|
+
});
|
|
1696
|
+
})
|
|
1697
|
+
.join("")
|
|
1698
|
+
: '<li class="nav-empty">' + escapeHtml(t("cron.navEmpty")) + "</li>";
|
|
1699
|
+
}
|
|
1700
|
+
applyNavFold();
|
|
1547
1701
|
}
|
|
1548
1702
|
|
|
1549
1703
|
function memberRowHtml(bot, action, locked, full) {
|
|
@@ -2087,6 +2241,7 @@
|
|
|
2087
2241
|
syncThreadChrome();
|
|
2088
2242
|
return;
|
|
2089
2243
|
}
|
|
2244
|
+
const keptHtml = holdHtmlFrames(root);
|
|
2090
2245
|
root.innerHTML =
|
|
2091
2246
|
rows
|
|
2092
2247
|
.map((msg, index) => {
|
|
@@ -2242,6 +2397,7 @@
|
|
|
2242
2397
|
root.scrollTop = Math.max(0, root.scrollHeight - fromBottom);
|
|
2243
2398
|
}
|
|
2244
2399
|
syncThreadChrome();
|
|
2400
|
+
putHtmlFrames(root, keptHtml);
|
|
2245
2401
|
hydrateHtmlPreviews(root);
|
|
2246
2402
|
}
|
|
2247
2403
|
function hydrateHtmlPreviews(root) {
|
|
@@ -2249,15 +2405,16 @@
|
|
|
2249
2405
|
root.querySelectorAll(".md-html-preview").forEach((box) => {
|
|
2250
2406
|
const src = box.querySelector(".md-html-src");
|
|
2251
2407
|
const frame = box.querySelector(".md-html-frame");
|
|
2252
|
-
if (!src || !frame
|
|
2253
|
-
frame.dataset.ready = "1";
|
|
2408
|
+
if (!src || !frame) return;
|
|
2254
2409
|
const raw = src.value;
|
|
2255
2410
|
const lang = (box.querySelector(".md-fence-lang") || {}).textContent || "";
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
raw
|
|
2259
|
-
|
|
2260
|
-
|
|
2411
|
+
const doc =
|
|
2412
|
+
typeof htmlPreviewSrcdoc === "function"
|
|
2413
|
+
? htmlPreviewSrcdoc(raw, lang)
|
|
2414
|
+
: raw;
|
|
2415
|
+
if (frame.dataset.ready && frame.srcdoc === doc) return;
|
|
2416
|
+
frame.dataset.ready = "1";
|
|
2417
|
+
frame.srcdoc = doc;
|
|
2261
2418
|
});
|
|
2262
2419
|
}
|
|
2263
2420
|
function openHtmlZoom(raw) {
|
|
@@ -2285,6 +2442,7 @@
|
|
|
2285
2442
|
renderNav();
|
|
2286
2443
|
renderHead();
|
|
2287
2444
|
setHash();
|
|
2445
|
+
loadNavCron().catch(() => {});
|
|
2288
2446
|
}
|
|
2289
2447
|
async function deleteChannel(id, name) {
|
|
2290
2448
|
if (!id || id === "channel-general") {
|
|
@@ -2593,8 +2751,11 @@
|
|
|
2593
2751
|
const pending = (state.messages || []).filter(
|
|
2594
2752
|
(msg) => String(msg.id).indexOf("pending-") === 0,
|
|
2595
2753
|
);
|
|
2596
|
-
if (!extras.length && !pending.length) return;
|
|
2597
2754
|
const serverYou = incoming.filter((msg) => msg.author === "you");
|
|
2755
|
+
const dropPending = pending.filter((msg) =>
|
|
2756
|
+
serverYou.some((row) => row.body === msg.body),
|
|
2757
|
+
);
|
|
2758
|
+
if (!extras.length && !dropPending.length) return;
|
|
2598
2759
|
state.messages = (state.messages || [])
|
|
2599
2760
|
.filter((msg) => {
|
|
2600
2761
|
if (String(msg.id).indexOf("pending-") === 0) {
|
|
@@ -3000,6 +3161,19 @@
|
|
|
3000
3161
|
}
|
|
3001
3162
|
});
|
|
3002
3163
|
document.getElementById("filter").addEventListener("input", renderNav);
|
|
3164
|
+
document.getElementById("nav-drawer").addEventListener("click", (event) => {
|
|
3165
|
+
const btn = event.target.closest("[data-fold]");
|
|
3166
|
+
if (!btn || event.target.closest(".add-ch")) return;
|
|
3167
|
+
event.preventDefault();
|
|
3168
|
+
const sec = btn.getAttribute("data-fold");
|
|
3169
|
+
if (!sec) return;
|
|
3170
|
+
state.navFold[sec] = !state.navFold[sec];
|
|
3171
|
+
saveNavFold();
|
|
3172
|
+
applyNavFold();
|
|
3173
|
+
if (sec === "cron" && !state.navFold.cron) {
|
|
3174
|
+
loadNavCron().catch(() => {});
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3003
3177
|
document.getElementById("toggle-create").addEventListener("click", () => {
|
|
3004
3178
|
document.getElementById("create-row").classList.toggle("open");
|
|
3005
3179
|
document.getElementById("new-channel").focus();
|
|
@@ -3264,6 +3438,64 @@
|
|
|
3264
3438
|
channelMdDialog.close();
|
|
3265
3439
|
await loadWorkspace();
|
|
3266
3440
|
});
|
|
3441
|
+
document.getElementById("cron-cancel").addEventListener("click", () => {
|
|
3442
|
+
document.getElementById("cron-dialog").close();
|
|
3443
|
+
});
|
|
3444
|
+
document.getElementById("cron-add").addEventListener("click", async () => {
|
|
3445
|
+
const botId = document.getElementById("cron-bot").value;
|
|
3446
|
+
const ask = document.getElementById("cron-ask").value.trim();
|
|
3447
|
+
const bot = botById(botId);
|
|
3448
|
+
if (!bot) {
|
|
3449
|
+
alert(t("cron.needSeat"));
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
if (!ask) {
|
|
3453
|
+
alert(t("cron.needAsk"));
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
const raw = "@" + bot.handle + " " + t("cron.askBody", { ask: ask });
|
|
3457
|
+
document.getElementById("cron-ask").value = "";
|
|
3458
|
+
document.getElementById("cron-dialog").close();
|
|
3459
|
+
const split = splitSendTargets([botId]);
|
|
3460
|
+
if (split.fresh.length) {
|
|
3461
|
+
await flushSend(raw, [], "", [botId], botId);
|
|
3462
|
+
} else {
|
|
3463
|
+
enqueuePending({
|
|
3464
|
+
raw: raw,
|
|
3465
|
+
packed: [],
|
|
3466
|
+
replyTo: "",
|
|
3467
|
+
botIds: [botId],
|
|
3468
|
+
assigneeId: botId,
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
document.getElementById("cron-list").addEventListener("click", async (event) => {
|
|
3473
|
+
const btn = event.target.closest("[data-cron-act]");
|
|
3474
|
+
const row = event.target.closest("[data-cron-id]");
|
|
3475
|
+
if (!btn || !row) return;
|
|
3476
|
+
const id = row.getAttribute("data-cron-id");
|
|
3477
|
+
const act = btn.getAttribute("data-cron-act");
|
|
3478
|
+
if (act === "remove") {
|
|
3479
|
+
const res = await fetch("/cron/" + encodeURIComponent(id), { method: "DELETE" });
|
|
3480
|
+
if (!res.ok) {
|
|
3481
|
+
const body = await res.json().catch(() => ({}));
|
|
3482
|
+
alert(body.error || "cron failed");
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
} else {
|
|
3486
|
+
const res = await fetch(
|
|
3487
|
+
"/cron/" + encodeURIComponent(id) + "/" + act,
|
|
3488
|
+
{ method: "POST" },
|
|
3489
|
+
);
|
|
3490
|
+
const body = await res.json().catch(() => ({}));
|
|
3491
|
+
if (!res.ok) {
|
|
3492
|
+
alert(body.error || "cron failed");
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
await renderCronList();
|
|
3497
|
+
loadNavCron().catch(() => {});
|
|
3498
|
+
});
|
|
3267
3499
|
document.getElementById("bot-memory-cancel").addEventListener("click", () => {
|
|
3268
3500
|
botMemoryDialog.close();
|
|
3269
3501
|
});
|
|
@@ -4299,6 +4531,85 @@
|
|
|
4299
4531
|
body: chunks.join("\n\n") || t("attach.noPersona"),
|
|
4300
4532
|
});
|
|
4301
4533
|
}
|
|
4534
|
+
function cronRoomId() {
|
|
4535
|
+
if (state.kind === "dm") return "dm-" + state.id;
|
|
4536
|
+
return state.id;
|
|
4537
|
+
}
|
|
4538
|
+
async function loadCronJobs() {
|
|
4539
|
+
const id = cronRoomId();
|
|
4540
|
+
if (!id) return [];
|
|
4541
|
+
const res = await fetch("/cron?room=" + encodeURIComponent(id));
|
|
4542
|
+
const body = await res.json().catch(() => ({}));
|
|
4543
|
+
return Array.isArray(body.jobs) ? body.jobs : [];
|
|
4544
|
+
}
|
|
4545
|
+
function cronWhen(iso) {
|
|
4546
|
+
const d = new Date(iso || "");
|
|
4547
|
+
if (!Number.isFinite(d.getTime())) return String(iso || "");
|
|
4548
|
+
return d.toLocaleString();
|
|
4549
|
+
}
|
|
4550
|
+
async function renderCronList() {
|
|
4551
|
+
const box = document.getElementById("cron-list");
|
|
4552
|
+
const jobs = await loadCronJobs();
|
|
4553
|
+
if (!jobs.length) {
|
|
4554
|
+
box.innerHTML = '<div class="cron-empty">' + t("cron.empty") + "</div>";
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
box.innerHTML = jobs
|
|
4558
|
+
.map((job) => {
|
|
4559
|
+
const pauseLabel = job.paused ? t("cron.resume") : t("cron.pause");
|
|
4560
|
+
const pauseAct = job.paused ? "resume" : "pause";
|
|
4561
|
+
const err =
|
|
4562
|
+
job.lastStatus === "failed" && job.lastError
|
|
4563
|
+
? " · " + escapeHtml(job.lastError)
|
|
4564
|
+
: "";
|
|
4565
|
+
return (
|
|
4566
|
+
'<div class="cron-row" data-cron-id="' +
|
|
4567
|
+
escapeHtml(job.id) +
|
|
4568
|
+
'"><div><b>' +
|
|
4569
|
+
escapeHtml(job.name) +
|
|
4570
|
+
"</b><span>" +
|
|
4571
|
+
escapeHtml(job.schedule) +
|
|
4572
|
+
" · " +
|
|
4573
|
+
t("cron.next", { when: cronWhen(job.nextRunAt) }) +
|
|
4574
|
+
(job.paused ? " · paused" : "") +
|
|
4575
|
+
err +
|
|
4576
|
+
'</span></div><div class="cron-acts">' +
|
|
4577
|
+
'<button type="button" data-cron-act="' +
|
|
4578
|
+
pauseAct +
|
|
4579
|
+
'">' +
|
|
4580
|
+
pauseLabel +
|
|
4581
|
+
'</button><button type="button" data-cron-act="run">' +
|
|
4582
|
+
t("cron.run") +
|
|
4583
|
+
'</button><button type="button" class="ghost danger" data-cron-act="remove">' +
|
|
4584
|
+
t("cron.remove") +
|
|
4585
|
+
"</button></div></div>"
|
|
4586
|
+
);
|
|
4587
|
+
})
|
|
4588
|
+
.join("");
|
|
4589
|
+
}
|
|
4590
|
+
async function openCronDialog() {
|
|
4591
|
+
const dialog = document.getElementById("cron-dialog");
|
|
4592
|
+
const sel = document.getElementById("cron-bot");
|
|
4593
|
+
const ch = currentChannel();
|
|
4594
|
+
const members = ch ? ch.members || [] : [];
|
|
4595
|
+
const dmBot =
|
|
4596
|
+
state.kind === "dm" && state.id ? botById(state.id) : null;
|
|
4597
|
+
const bots = members.length ? members : dmBot ? [dmBot] : [];
|
|
4598
|
+
sel.innerHTML = bots
|
|
4599
|
+
.map(
|
|
4600
|
+
(bot) =>
|
|
4601
|
+
'<option value="' +
|
|
4602
|
+
escapeHtml(bot.id) +
|
|
4603
|
+
'">@' +
|
|
4604
|
+
escapeHtml(bot.handle) +
|
|
4605
|
+
"</option>",
|
|
4606
|
+
)
|
|
4607
|
+
.join("");
|
|
4608
|
+
await renderCronList();
|
|
4609
|
+
dialog.showModal();
|
|
4610
|
+
const ask = document.getElementById("cron-ask");
|
|
4611
|
+
if (ask) ask.focus();
|
|
4612
|
+
}
|
|
4302
4613
|
function openSlashSheet() {
|
|
4303
4614
|
const rows = [
|
|
4304
4615
|
{ id: "channel-md", label: t("slash.editMd"), hint: t("slash.editMdHint") },
|
|
@@ -4306,6 +4617,7 @@
|
|
|
4306
4617
|
{ id: "git", label: t("slash.git"), hint: "status + diff" },
|
|
4307
4618
|
{ id: "here", label: t("slash.here"), hint: t("slash.hereHint") },
|
|
4308
4619
|
{ id: "library", label: t("slash.library"), hint: "/library" },
|
|
4620
|
+
{ id: "cron", label: t("slash.cron"), hint: t("slash.cronHint") },
|
|
4309
4621
|
];
|
|
4310
4622
|
if (state.kind !== "channel") {
|
|
4311
4623
|
rows.splice(0, 1);
|
|
@@ -4335,6 +4647,11 @@
|
|
|
4335
4647
|
}
|
|
4336
4648
|
if (id === "library") {
|
|
4337
4649
|
window.location.href = "/library";
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
if (id === "cron") {
|
|
4653
|
+
hideAttach();
|
|
4654
|
+
openCronDialog();
|
|
4338
4655
|
}
|
|
4339
4656
|
}
|
|
4340
4657
|
async function runAttach(kind) {
|
|
@@ -4356,6 +4673,10 @@
|
|
|
4356
4673
|
renderAttachSheet();
|
|
4357
4674
|
return;
|
|
4358
4675
|
}
|
|
4676
|
+
if (kind === "cron") {
|
|
4677
|
+
hideAttach();
|
|
4678
|
+
return openCronDialog();
|
|
4679
|
+
}
|
|
4359
4680
|
if (kind === "slash") return openSlashSheet();
|
|
4360
4681
|
}
|
|
4361
4682
|
function startLiveTimers() {
|
|
@@ -5167,6 +5488,15 @@
|
|
|
5167
5488
|
hint: "/library",
|
|
5168
5489
|
glyph: "/",
|
|
5169
5490
|
},
|
|
5491
|
+
{
|
|
5492
|
+
kind: "cmd",
|
|
5493
|
+
section: "cmd",
|
|
5494
|
+
cmdId: "cron",
|
|
5495
|
+
insert: "",
|
|
5496
|
+
name: t("slash.cron"),
|
|
5497
|
+
hint: t("slash.cronHint"),
|
|
5498
|
+
glyph: "/",
|
|
5499
|
+
},
|
|
5170
5500
|
];
|
|
5171
5501
|
if (state.kind !== "channel") {
|
|
5172
5502
|
return rows.filter(
|
|
@@ -6064,6 +6394,11 @@
|
|
|
6064
6394
|
setNavOpen(false);
|
|
6065
6395
|
openFromHash().catch((err) => alert(err.message));
|
|
6066
6396
|
});
|
|
6397
|
+
applyNavFold();
|
|
6398
|
+
setInterval(() => {
|
|
6399
|
+
if (document.hidden || navSecFolded("cron")) return;
|
|
6400
|
+
loadNavCron().catch(() => {});
|
|
6401
|
+
}, 15000);
|
|
6067
6402
|
Promise.all([openFromHash(), loadModelPick()])
|
|
6068
6403
|
.then(() => loadSlashCatalog().catch(() => {}))
|
|
6069
6404
|
.catch((err) => {
|
package/src/public/i18n.js
CHANGED
|
@@ -31,6 +31,8 @@ var I18N_ROWS = [
|
|
|
31
31
|
["search.traj", "搜尋 system / tool / spawn / 子代理 / 結果", "Search system / tool / spawn / subagent / result"],
|
|
32
32
|
["channels", "委託", "Channels"],
|
|
33
33
|
["dms", "密談", "Whispers"],
|
|
34
|
+
["cron.navEmpty", "還沒有排程", "No schedules"],
|
|
35
|
+
["cron.navWhere", "@{handle} · {schedule}", "@{handle} · {schedule}"],
|
|
34
36
|
["newChannel", "新委託", "New channel"],
|
|
35
37
|
["newChannelName", "委託名稱", "Channel name"],
|
|
36
38
|
["create", "建立", "Create"],
|
|
@@ -156,6 +158,7 @@ var I18N_ROWS = [
|
|
|
156
158
|
["attach.rules", "規則", "Rules"],
|
|
157
159
|
["attach.term", "終端", "Terminal"],
|
|
158
160
|
["attach.tree", "工作區", "Workspace"],
|
|
161
|
+
["attach.cron", "排程", "Schedule"],
|
|
159
162
|
["attach.slash", "指令", "Commands"],
|
|
160
163
|
["insert", "插入 {name}", "Insert {name}"],
|
|
161
164
|
["remove", "移除", "Remove"],
|
|
@@ -224,6 +227,23 @@ var I18N_ROWS = [
|
|
|
224
227
|
["slash.here", "叫所有人", "Ping everyone"],
|
|
225
228
|
["slash.hereHint", "通知這個據點上的所有冒險者。通常是錯的:出活仍點名一人。", "Notify every adventurer in this hall. Usually wrong: call one @handle."],
|
|
226
229
|
["slash.library", "開工坊", "Open workshop"],
|
|
230
|
+
["slash.cron", "排程", "Schedule"],
|
|
231
|
+
["slash.cronHint", "請這席用 cronjob 排", "Ask this seat to cronjob"],
|
|
232
|
+
["cron.title", "排程", "Schedule"],
|
|
233
|
+
["cron.help", "新增會請這席呼叫 cronjob。時間用自然語言寫即可,例如「每10分鐘跟我說一次 Hi」。guildd 每 60 秒看一次;到點後這席開一輪新的 @handle,prompt 必須自包含。", "Add asks this seat to call cronjob. Write the time in natural language, e.g. \"every 10 minutes say Hi\". guildd ticks every 60s. Due jobs start a fresh @handle turn with a self-contained prompt."],
|
|
234
|
+
["cron.seat", "席", "Seat"],
|
|
235
|
+
["cron.ask", "要排什麼", "What to schedule"],
|
|
236
|
+
["cron.askPh", "每10分鐘跟我說一次 Hi", "every 10 minutes say Hi"],
|
|
237
|
+
["cron.askBody", "請用 cronjob 排程:{ask}", "Create a cronjob for this: {ask}"],
|
|
238
|
+
["cron.needAsk", "先寫要排什麼", "Write what to schedule first"],
|
|
239
|
+
["cron.needSeat", "這個據點沒有席", "No seat in this hall"],
|
|
240
|
+
["cron.add", "請他排", "Ask them"],
|
|
241
|
+
["cron.pause", "暫停", "Pause"],
|
|
242
|
+
["cron.resume", "繼續", "Resume"],
|
|
243
|
+
["cron.run", "立刻跑", "Run now"],
|
|
244
|
+
["cron.remove", "刪除", "Remove"],
|
|
245
|
+
["cron.empty", "這個據點還沒有排程。", "No schedules in this hall."],
|
|
246
|
+
["cron.next", "下次 {when}", "Next {when}"],
|
|
227
247
|
["slash.sec.skill", "技能", "Skills"],
|
|
228
248
|
["slash.sec.agent", "子代理", "Subagents"],
|
|
229
249
|
["slash.sec.cmd", "指令", "Commands"],
|