@kevin5251984/guild 0.2.21 → 0.2.22
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/md.js
CHANGED
|
@@ -19,22 +19,39 @@ function isFileCode(text) {
|
|
|
19
19
|
return /[A-Za-z0-9]\/[A-Za-z0-9._@+-]/.test(s);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function localImgSrc(href) {
|
|
23
|
+
const raw = String(href || "")
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/"/g, "");
|
|
26
|
+
if (!raw) return "";
|
|
27
|
+
if (/^\/generated\/[A-Za-z0-9._-]+$/.test(raw)) return raw;
|
|
28
|
+
if (/^https?:\/\//i.test(raw)) return raw;
|
|
29
|
+
let path = raw;
|
|
30
|
+
if (/^file:\/\//i.test(path)) path = path.replace(/^file:\/\//i, "");
|
|
31
|
+
if (!path.startsWith("/") || path.startsWith("//") || path.includes("..")) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
if (!/\.(?:png|jpe?g|gif|webp)$/i.test(path)) return "";
|
|
35
|
+
return "/local?p=" + encodeURIComponent(path);
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
function inlineMd(text) {
|
|
23
39
|
let out = escapeMd(text);
|
|
24
|
-
out = out.replace(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
out = out.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, function (m, alt, href) {
|
|
41
|
+
const src = localImgSrc(href);
|
|
42
|
+
if (!src) return m;
|
|
43
|
+
const label = alt || "image";
|
|
44
|
+
return (
|
|
45
|
+
'<a class="md-img-link" href="' +
|
|
46
|
+
src +
|
|
47
|
+
'" target="_blank" rel="noopener noreferrer">' +
|
|
48
|
+
'<img class="md-img" src="' +
|
|
49
|
+
src +
|
|
50
|
+
'" alt="' +
|
|
51
|
+
label +
|
|
52
|
+
'" loading="lazy"></a>'
|
|
53
|
+
);
|
|
54
|
+
});
|
|
38
55
|
out = out.replace(/\[([^\]]+)\]\((https?:[^)\s]+)\)/g, function (_m, label, href) {
|
|
39
56
|
return (
|
|
40
57
|
'<a href="' +
|
|
@@ -327,23 +344,83 @@ function renderMarkdown(raw) {
|
|
|
327
344
|
return html.join("");
|
|
328
345
|
}
|
|
329
346
|
|
|
347
|
+
function htmlPreviewSrcdoc(raw, lang) {
|
|
348
|
+
const text = String(raw || "");
|
|
349
|
+
if (/svg/i.test(String(lang || ""))) {
|
|
350
|
+
return (
|
|
351
|
+
'<!doctype html><html><body style="margin:0;background:#fff">' +
|
|
352
|
+
text +
|
|
353
|
+
"</body></html>"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (/<!doctype html|<html[\s>]/i.test(text)) return text;
|
|
357
|
+
return (
|
|
358
|
+
'<!doctype html><html><body style="margin:0;background:transparent">' +
|
|
359
|
+
text +
|
|
360
|
+
"</body></html>"
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
330
364
|
function hydrateHtmlPreviews(root) {
|
|
331
365
|
if (!root || typeof root.querySelectorAll !== "function") return;
|
|
332
366
|
root.querySelectorAll(".md-html-preview").forEach((box) => {
|
|
333
367
|
const src = box.querySelector(".md-html-src");
|
|
334
368
|
const frame = box.querySelector(".md-html-frame");
|
|
335
|
-
if (!src || !frame
|
|
336
|
-
frame.dataset.ready = "1";
|
|
369
|
+
if (!src || !frame) return;
|
|
337
370
|
const raw = src.value;
|
|
338
371
|
const lang = (box.querySelector(".md-fence-lang") || {}).textContent || "";
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
372
|
+
const doc = htmlPreviewSrcdoc(raw, lang);
|
|
373
|
+
if (frame.dataset.ready && frame.srcdoc === doc) return;
|
|
374
|
+
frame.dataset.ready = "1";
|
|
375
|
+
frame.srcdoc = doc;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function holdHtmlFrames(root) {
|
|
380
|
+
const frames = [];
|
|
381
|
+
if (!root || typeof root.querySelectorAll !== "function") return frames;
|
|
382
|
+
root.querySelectorAll("article.msg .md-html-preview").forEach((box) => {
|
|
383
|
+
const article = box.closest("article.msg[data-id]");
|
|
384
|
+
const frame = box.querySelector(".md-html-frame");
|
|
385
|
+
const src = box.querySelector(".md-html-src");
|
|
386
|
+
if (!article || !frame || !src || !frame.dataset.ready) return;
|
|
387
|
+
frames.push({
|
|
388
|
+
id: article.getAttribute("data-id"),
|
|
389
|
+
src: src.value,
|
|
390
|
+
view: box.getAttribute("data-view") || "preview",
|
|
391
|
+
frame: frame,
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
return frames;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function putHtmlFrames(root, frames) {
|
|
398
|
+
if (!root || typeof root.querySelector !== "function") return;
|
|
399
|
+
(frames || []).forEach((item) => {
|
|
400
|
+
const article = root.querySelector(
|
|
401
|
+
'article.msg[data-id="' + CSS.escape(item.id) + '"]',
|
|
402
|
+
);
|
|
403
|
+
const box = article && article.querySelector(".md-html-preview");
|
|
404
|
+
const src = box && box.querySelector(".md-html-src");
|
|
405
|
+
const fresh = box && box.querySelector(".md-html-frame");
|
|
406
|
+
if (!box || !src || !fresh || src.value !== item.src) return;
|
|
407
|
+
item.frame.dataset.ready = "1";
|
|
408
|
+
fresh.replaceWith(item.frame);
|
|
409
|
+
box.setAttribute("data-view", item.view);
|
|
410
|
+
box.querySelectorAll("[data-html-view]").forEach((tab) => {
|
|
411
|
+
tab.classList.toggle("on", tab.getAttribute("data-html-view") === item.view);
|
|
412
|
+
});
|
|
344
413
|
});
|
|
345
414
|
}
|
|
346
415
|
|
|
347
416
|
if (typeof module !== "undefined" && module.exports) {
|
|
348
|
-
module.exports = {
|
|
417
|
+
module.exports = {
|
|
418
|
+
renderMarkdown,
|
|
419
|
+
inlineMd,
|
|
420
|
+
hydrateHtmlPreviews,
|
|
421
|
+
htmlPreviewSrcdoc,
|
|
422
|
+
holdHtmlFrames,
|
|
423
|
+
putHtmlFrames,
|
|
424
|
+
localImgSrc,
|
|
425
|
+
};
|
|
349
426
|
}
|
package/src/public/mobile.css
CHANGED
|
@@ -391,7 +391,8 @@ textarea:focus-visible {
|
|
|
391
391
|
min-height: 220px;
|
|
392
392
|
height: 52vh;
|
|
393
393
|
border: 0;
|
|
394
|
-
background:
|
|
394
|
+
background: transparent;
|
|
395
|
+
color-scheme: dark;
|
|
395
396
|
border-radius: 0 0 12px 12px;
|
|
396
397
|
}
|
|
397
398
|
.assistant-text .md-html-preview[data-view="code"] .md-html-frame { display: none; }
|
|
@@ -441,8 +442,17 @@ textarea:focus-visible {
|
|
|
441
442
|
border-radius: 16px;
|
|
442
443
|
background: #fff;
|
|
443
444
|
}
|
|
444
|
-
.assistant-text
|
|
445
|
+
.assistant-text .md-img-link {
|
|
446
|
+
display: inline-block;
|
|
445
447
|
max-width: 100%;
|
|
448
|
+
line-height: 0;
|
|
449
|
+
}
|
|
450
|
+
.assistant-text img.md-img {
|
|
451
|
+
max-width: min(100%, 22rem);
|
|
452
|
+
max-height: min(16rem, 40vh);
|
|
453
|
+
width: auto;
|
|
454
|
+
height: auto;
|
|
455
|
+
object-fit: contain;
|
|
446
456
|
border-radius: 10px;
|
|
447
457
|
}
|
|
448
458
|
.turn-live {
|
package/src/public/mobile.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
|
|
10
10
|
<link rel="icon" href="/favicon-16.png" type="image/png" sizes="16x16" />
|
|
11
11
|
<title>Guild — 外出</title>
|
|
12
|
-
<link rel="stylesheet" href="/mobile.css?v=html-
|
|
12
|
+
<link rel="stylesheet" href="/mobile.css?v=html-frame" />
|
|
13
13
|
<script src="/i18n.js"></script>
|
|
14
14
|
<script src="/md.js"></script>
|
|
15
15
|
</head>
|
|
@@ -484,6 +484,8 @@
|
|
|
484
484
|
return (
|
|
485
485
|
'<article class="msg' +
|
|
486
486
|
(you ? " you" : " bot") +
|
|
487
|
+
'" data-id="' +
|
|
488
|
+
escapeHtml(msg.id || "") +
|
|
487
489
|
'"><span class="avatar" style="' +
|
|
488
490
|
avStyle +
|
|
489
491
|
'">' +
|
|
@@ -520,7 +522,10 @@
|
|
|
520
522
|
escapeHtml(t("mobile.recent", { n: cap })) +
|
|
521
523
|
"</div>"
|
|
522
524
|
: "";
|
|
525
|
+
const keptHtml =
|
|
526
|
+
typeof holdHtmlFrames === "function" ? holdHtmlFrames(root) : [];
|
|
523
527
|
root.innerHTML = notice + rows.map(msgHtml).join("") + turnStatusHtml();
|
|
528
|
+
if (typeof putHtmlFrames === "function") putHtmlFrames(root, keptHtml);
|
|
524
529
|
if (typeof hydrateHtmlPreviews === "function") {
|
|
525
530
|
hydrateHtmlPreviews(root);
|
|
526
531
|
}
|
|
@@ -662,12 +667,25 @@
|
|
|
662
667
|
renderList();
|
|
663
668
|
if (state.kind) renderHead();
|
|
664
669
|
}
|
|
670
|
+
function sameMessages(next) {
|
|
671
|
+
const prev = state.messages || [];
|
|
672
|
+
if (prev.length !== next.length) return false;
|
|
673
|
+
return next.every(
|
|
674
|
+
(msg, i) =>
|
|
675
|
+
prev[i].id === msg.id &&
|
|
676
|
+
prev[i].body === msg.body &&
|
|
677
|
+
Boolean(prev[i].finishedAt) === Boolean(msg.finishedAt),
|
|
678
|
+
);
|
|
679
|
+
}
|
|
665
680
|
async function loadMessages(opts) {
|
|
666
681
|
if (!state.kind) return;
|
|
667
682
|
const res = await fetch(roomMessagesUrl());
|
|
668
683
|
const body = await res.json().catch(() => []);
|
|
669
684
|
if (!res.ok) throw new Error(body.error || "load failed");
|
|
670
|
-
|
|
685
|
+
const incoming = Array.isArray(body) ? body : [];
|
|
686
|
+
const unchanged = sameMessages(incoming);
|
|
687
|
+
state.messages = incoming;
|
|
688
|
+
if (opts && opts.merge && unchanged) return;
|
|
671
689
|
renderThread({ pinBottom: !(opts && opts.merge) });
|
|
672
690
|
}
|
|
673
691
|
async function pollLive() {
|
|
@@ -684,10 +702,10 @@
|
|
|
684
702
|
state.lives = lives;
|
|
685
703
|
if (!lives.length && !state.posting) {
|
|
686
704
|
if (was) await loadMessages({ merge: true }).catch(() => {});
|
|
687
|
-
|
|
705
|
+
} else if (now !== was) {
|
|
706
|
+
await loadMessages({ merge: true }).catch(() => {});
|
|
688
707
|
} else {
|
|
689
|
-
|
|
690
|
-
else renderThread();
|
|
708
|
+
renderThread();
|
|
691
709
|
}
|
|
692
710
|
syncComposer();
|
|
693
711
|
} catch {
|
package/src/router.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import { extname } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import type { LibraryKind, ModelRef, ModelsFile } from "@guild/protocol";
|
|
5
6
|
import {
|
|
@@ -66,6 +67,30 @@ import { hostGit, hostList, hostRead, hostTree } from "./host-browse.ts";
|
|
|
66
67
|
import { listHostSkills } from "./host-skills.ts";
|
|
67
68
|
import { listHostAgents } from "./host-agents.ts";
|
|
68
69
|
import { generatedDir, isSafeGeneratedName } from "./image-gen.ts";
|
|
70
|
+
import {
|
|
71
|
+
createCronJob,
|
|
72
|
+
fireCronJob,
|
|
73
|
+
pauseCronJob,
|
|
74
|
+
publicCronJob,
|
|
75
|
+
removeCronJob,
|
|
76
|
+
resumeCronJob,
|
|
77
|
+
} from "./cron.ts";
|
|
78
|
+
import {
|
|
79
|
+
allowedWorkspaceWritePath,
|
|
80
|
+
resolveToolPath,
|
|
81
|
+
workspaceFromEnv,
|
|
82
|
+
} from "./harness.ts";
|
|
83
|
+
|
|
84
|
+
const LOCAL_IMAGE_CAP = 12 * 1024 * 1024;
|
|
85
|
+
|
|
86
|
+
function localImageType(path: string): string | null {
|
|
87
|
+
const ext = extname(path).toLowerCase();
|
|
88
|
+
if (ext === ".png") return "image/png";
|
|
89
|
+
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
90
|
+
if (ext === ".gif") return "image/gif";
|
|
91
|
+
if (ext === ".webp") return "image/webp";
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
69
94
|
|
|
70
95
|
const PUBLIC = fileURLToPath(new URL("./public/", import.meta.url));
|
|
71
96
|
|
|
@@ -391,6 +416,38 @@ export async function handleRequest(
|
|
|
391
416
|
return;
|
|
392
417
|
}
|
|
393
418
|
|
|
419
|
+
if (method === "GET" && path === "/local") {
|
|
420
|
+
const raw = requestUrl(req).searchParams.get("p") || "";
|
|
421
|
+
const type = localImageType(raw);
|
|
422
|
+
if (!raw.startsWith("/") || raw.includes("\0") || raw.includes("..") || !type) {
|
|
423
|
+
json(res, 404, { error: "not_found", path });
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const target = resolveToolPath(raw);
|
|
427
|
+
if (!allowedWorkspaceWritePath(target, workspaceFromEnv(env), store.dataDir)) {
|
|
428
|
+
json(res, 404, { error: "not_found", path });
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!existsSync(target)) {
|
|
432
|
+
json(res, 404, { error: "not_found", path });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const st = statSync(target);
|
|
436
|
+
if (!st.isFile() || st.size > LOCAL_IMAGE_CAP) {
|
|
437
|
+
json(res, 404, { error: "not_found", path });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const bytes = readFileSync(target);
|
|
441
|
+
res.writeHead(200, {
|
|
442
|
+
"content-type": type,
|
|
443
|
+
"content-length": bytes.length,
|
|
444
|
+
"cache-control": "private, max-age=60",
|
|
445
|
+
"x-content-type-options": "nosniff",
|
|
446
|
+
});
|
|
447
|
+
res.end(bytes);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
394
451
|
if (method === "GET" && path.startsWith("/generated/")) {
|
|
395
452
|
const name = decodeURIComponent(path.slice("/generated/".length));
|
|
396
453
|
if (!isSafeGeneratedName(name)) {
|
|
@@ -444,6 +501,45 @@ export async function handleRequest(
|
|
|
444
501
|
return;
|
|
445
502
|
}
|
|
446
503
|
|
|
504
|
+
if (path === "/cron" && method === "GET") {
|
|
505
|
+
const room = requestUrl(req).searchParams.get("room") || "";
|
|
506
|
+
json(res, 200, {
|
|
507
|
+
jobs: store.listCronJobs(room || undefined).map(publicCronJob),
|
|
508
|
+
});
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (path === "/cron" && method === "POST") {
|
|
512
|
+
const body = asRecord(await readJson(req));
|
|
513
|
+
const job = createCronJob(store, {
|
|
514
|
+
roomId: str(body, "roomId") || str(body, "room_id"),
|
|
515
|
+
botId: str(body, "botId") || str(body, "bot_id"),
|
|
516
|
+
prompt: str(body, "prompt"),
|
|
517
|
+
schedule: str(body, "schedule"),
|
|
518
|
+
name: str(body, "name"),
|
|
519
|
+
});
|
|
520
|
+
json(res, 201, publicCronJob(job));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const cronAct = path.match(/^\/cron\/([^/]+)\/(pause|resume|run)$/);
|
|
524
|
+
if (cronAct && method === "POST") {
|
|
525
|
+
const id = decodeURIComponent(cronAct[1]);
|
|
526
|
+
if (cronAct[2] === "pause") {
|
|
527
|
+
json(res, 200, publicCronJob(pauseCronJob(store, id)));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (cronAct[2] === "resume") {
|
|
531
|
+
json(res, 200, publicCronJob(resumeCronJob(store, id)));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
json(res, 200, await fireCronJob(store, id, env));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const cronOne = path.match(/^\/cron\/([^/]+)$/);
|
|
538
|
+
if (cronOne && method === "DELETE") {
|
|
539
|
+
json(res, 200, removeCronJob(store, decodeURIComponent(cronOne[1])));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
447
543
|
if (method === "GET" && (path === "/bots" || path === "/bench")) {
|
|
448
544
|
json(res, 200, listBench(store));
|
|
449
545
|
return;
|
package/src/store.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { TrajectoryDraft, TrajectoryEvent } from "./trajectory.ts";
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
|
-
import { closeGuildDb, openGuildDb, type GuildDb } from "./db.ts";
|
|
13
|
+
import { closeGuildDb, openGuildDb, type CronJobRow, type GuildDb } from "./db.ts";
|
|
14
14
|
import type {
|
|
15
15
|
Bot,
|
|
16
16
|
ChatAttachment,
|
|
@@ -1329,6 +1329,27 @@ export class GuildStore {
|
|
|
1329
1329
|
if (!this.getRoom(roomId)) throw new StoreError(404, "room not found");
|
|
1330
1330
|
this.db.writeCompact(roomId, compact);
|
|
1331
1331
|
}
|
|
1332
|
+
|
|
1333
|
+
listCronJobs(roomId?: string) {
|
|
1334
|
+
if (roomId && !this.getRoom(roomId)) throw new StoreError(404, "room not found");
|
|
1335
|
+
return this.db.listCronJobs(roomId);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
getCronJob(id: string) {
|
|
1339
|
+
const job = this.db.getCronJob(id);
|
|
1340
|
+
if (!job) throw new StoreError(404, "cron job not found");
|
|
1341
|
+
return job;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
writeCronJob(job: CronJobRow): void {
|
|
1345
|
+
if (!this.getRoom(job.roomId)) throw new StoreError(404, "room not found");
|
|
1346
|
+
if (!this.getBot(job.botId)) throw new StoreError(400, "bot not found");
|
|
1347
|
+
this.db.upsertCronJob(job);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
deleteCronJob(id: string): boolean {
|
|
1351
|
+
return this.db.deleteCronJob(id);
|
|
1352
|
+
}
|
|
1332
1353
|
}
|
|
1333
1354
|
|
|
1334
1355
|
function normalizePortrait(raw: string | null | undefined): string | undefined {
|
package/src/subagent.ts
CHANGED
|
@@ -373,6 +373,9 @@ export async function spawnSubagent(input: {
|
|
|
373
373
|
allowWrite: child.allowWrite,
|
|
374
374
|
sandbox: child.sandbox,
|
|
375
375
|
workspace: input.ctx.workspace,
|
|
376
|
+
roomId: input.ctx.roomId,
|
|
377
|
+
botId: input.ctx.botId,
|
|
378
|
+
cronRun: input.ctx.cronRun,
|
|
376
379
|
dispatch: input.ctx.dispatch,
|
|
377
380
|
signal: input.ctx.signal,
|
|
378
381
|
},
|
package/src/tools.ts
CHANGED
|
@@ -78,6 +78,12 @@ export type ToolContext = {
|
|
|
78
78
|
) => Promise<ToolOutcome>;
|
|
79
79
|
/** Devin-style background spawn handles for this turn. Same Map across dispatch clones. */
|
|
80
80
|
spawnHandles?: Map<string, SpawnHandle>;
|
|
81
|
+
/** Channel or DM this turn is in. Cron jobs default here. */
|
|
82
|
+
roomId?: string;
|
|
83
|
+
/** Seat running this turn. cronjob create defaults here. */
|
|
84
|
+
botId?: string;
|
|
85
|
+
/** Hermes: cron child sessions cannot manage cron. */
|
|
86
|
+
cronRun?: boolean;
|
|
81
87
|
};
|
|
82
88
|
|
|
83
89
|
export type SpawnHandle = {
|
|
@@ -191,6 +197,33 @@ export function guildTools(
|
|
|
191
197
|
(tool) => tool.name !== "image_gen" && tool.name !== "browser",
|
|
192
198
|
);
|
|
193
199
|
}
|
|
200
|
+
if (!ctx.cronRun) {
|
|
201
|
+
tools.push({
|
|
202
|
+
name: "cronjob",
|
|
203
|
+
description:
|
|
204
|
+
"Schedule a later hall turn (Hermes cronjob). Fresh @handle turn with a self-contained prompt. Actions: create, list, pause, resume, run, remove. schedule may be natural language (每10分鐘, 10分鐘後, 每天9點, in 30 minutes, every 2h, 0 9 * * *, ISO). Split when vs task: schedule is the time phrase, prompt is the work. bot_id defaults to this seat. Do not create cron jobs from a cron run.",
|
|
205
|
+
parameters: Type.Object({
|
|
206
|
+
action: Type.String({
|
|
207
|
+
description: "create | list | pause | resume | run | remove",
|
|
208
|
+
}),
|
|
209
|
+
schedule: Type.Optional(
|
|
210
|
+
Type.String({
|
|
211
|
+
description:
|
|
212
|
+
"Natural language or Hermes form: 每10分鐘, in 30m, every 2h, 每天9點, 0 9 * * *, ISO",
|
|
213
|
+
}),
|
|
214
|
+
),
|
|
215
|
+
prompt: Type.Optional(
|
|
216
|
+
Type.String({ description: "Self-contained task for the seat" }),
|
|
217
|
+
),
|
|
218
|
+
name: Type.Optional(Type.String({ description: "Short job name" })),
|
|
219
|
+
job_id: Type.Optional(Type.String({ description: "Job id or name" })),
|
|
220
|
+
bot_id: Type.Optional(Type.String({ description: "Seat to run" })),
|
|
221
|
+
room_id: Type.Optional(
|
|
222
|
+
Type.String({ description: "Room id. Defaults to this hall." }),
|
|
223
|
+
),
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
194
227
|
tools.push({
|
|
195
228
|
name: "skill",
|
|
196
229
|
description: `Load a staffed skill's full instructions by name.${available}`,
|
|
@@ -387,6 +420,21 @@ function openaiParameters(name: string): {
|
|
|
387
420
|
required: ["prompt"],
|
|
388
421
|
};
|
|
389
422
|
}
|
|
423
|
+
if (name === "cronjob") {
|
|
424
|
+
return {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
action: { type: "string", description: "create | list | pause | resume | run | remove" },
|
|
428
|
+
schedule: { type: "string" },
|
|
429
|
+
prompt: { type: "string" },
|
|
430
|
+
name: { type: "string" },
|
|
431
|
+
job_id: { type: "string" },
|
|
432
|
+
bot_id: { type: "string" },
|
|
433
|
+
room_id: { type: "string" },
|
|
434
|
+
},
|
|
435
|
+
required: ["action"],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
390
438
|
if (name === "browser") {
|
|
391
439
|
return {
|
|
392
440
|
type: "object",
|
|
@@ -437,6 +485,7 @@ export const BUILTIN_TOOL_NAMES = [
|
|
|
437
485
|
"read_spawn",
|
|
438
486
|
"image_gen",
|
|
439
487
|
"browser",
|
|
488
|
+
"cronjob",
|
|
440
489
|
] as const;
|
|
441
490
|
|
|
442
491
|
export async function executeTool(
|
|
@@ -512,6 +561,12 @@ export async function builtinExecute(
|
|
|
512
561
|
env: ctx.env,
|
|
513
562
|
});
|
|
514
563
|
}
|
|
564
|
+
if (name === "cronjob") {
|
|
565
|
+
return {
|
|
566
|
+
text: "cronjob needs guildd (the cron plugin)",
|
|
567
|
+
isError: true,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
515
570
|
if (name === "browser") {
|
|
516
571
|
const { runBrowser } = await import("./browser.ts");
|
|
517
572
|
return runBrowser(args, {
|
|
@@ -817,7 +872,7 @@ export function nextToolRound(round: number): ToolRoundPhase {
|
|
|
817
872
|
}
|
|
818
873
|
|
|
819
874
|
export const TOOL_SYSTEM = `You ARE already running on the user's local computer (Guild, same design as Pi / DeepSeek Harness).
|
|
820
|
-
Tools: run, read, write, list, skill, spawn, image_gen, browser, plus any connected MCP tools (names start with mcp__).
|
|
875
|
+
Tools: run, read, write, list, skill, spawn, image_gen, browser, cronjob, plus any connected MCP tools (names start with mcp__).
|
|
821
876
|
You can inspect RAM, disk, CPU, processes, files, and run shell commands.
|
|
822
877
|
Never say you cannot access this machine. Never tell the user to run the command themselves.
|
|
823
878
|
When the question is about this computer, call tools first, then answer with evidence from the output.
|
|
@@ -827,4 +882,5 @@ You stay coordinator. Spawn is the specialist, not a last resort (Devin run_suba
|
|
|
827
882
|
Independent tool calls in one round also run in parallel — fire several reads/searches together.
|
|
828
883
|
Check the [exit code: N] marker on every run result; investigate failures before moving on. Prefer the workdir argument over cd.
|
|
829
884
|
To follow a staffed skill, call skill with its exact name (or slug) before applying it. Relative paths in a skill resolve against that skill's base directory.
|
|
885
|
+
When the user asks to 排程 / schedule a later hall turn — including natural-language times like 每10分鐘, 10分鐘後, tomorrow 9am — call cronjob create. schedule is the time phrase; prompt is the self-contained task (the job will not see this live turn). bot_id defaults to you. Also accepts in 30m, every 2h, 0 9 * * *, ISO. A cron run cannot create more cron jobs.
|
|
830
886
|
Prefer small commands. macOS RAM: sysctl hw.memsize ; memory_pressure. Disk: df -h.`;
|