@kevin5251984/guild 0.2.19 → 0.2.20
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/package.json +2 -2
- package/src/cli.ts +2 -5
- package/src/db.ts +39 -6
- package/src/generate.ts +3 -3
- package/src/handlers.ts +29 -45
- package/src/harness.ts +20 -4
- package/src/host-browse.ts +149 -4
- package/src/llm.ts +37 -15
- package/src/mcp.ts +32 -5
- package/src/mention.ts +80 -12
- package/src/oauth.ts +2 -2
- package/src/opencode-free.ts +3 -2
- package/src/public/chat.html +142 -26
- package/src/public/i18n.js +3 -0
- package/src/public/mobile.css +120 -14
- package/src/public/mobile.html +186 -16
- package/src/public/settings.html +51 -8
- package/src/reasoning-catalog.ts +346 -0
- package/src/router.ts +108 -8
- package/src/store.ts +15 -6
- package/src/subagent.ts +10 -5
- package/src/tools.ts +8 -3
- package/src/version.ts +25 -0
- package/vendor/protocol/src/index.ts +13 -1
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=
|
|
12
|
+
<link rel="stylesheet" href="/mobile.css?v=enamel" />
|
|
13
13
|
<script src="/i18n.js"></script>
|
|
14
14
|
<script src="/md.js"></script>
|
|
15
15
|
</head>
|
|
@@ -39,17 +39,26 @@
|
|
|
39
39
|
<div id="mention-results"></div>
|
|
40
40
|
</div>
|
|
41
41
|
<textarea id="draft" rows="1" placeholder="Ask anything" data-i18n-placeholder="askAnything"></textarea>
|
|
42
|
+
<div class="composer-steer" id="composer-steer" hidden></div>
|
|
42
43
|
<div class="composer-actions">
|
|
43
44
|
<button type="submit" class="send" id="send" data-i18n="send">送出</button>
|
|
44
45
|
<button type="button" class="stop-all" id="stop" hidden data-i18n="live.stop">停止</button>
|
|
46
|
+
<button type="button" class="steer" id="steer" hidden data-i18n="steer.insert">插入引導</button>
|
|
45
47
|
</div>
|
|
46
48
|
</div>
|
|
47
49
|
</form>
|
|
48
50
|
</section>
|
|
49
|
-
<div class="flash" id="flash" hidden></div>
|
|
51
|
+
<div class="flash" id="flash" role="status" aria-live="polite" hidden></div>
|
|
50
52
|
<script>
|
|
51
53
|
(function () {
|
|
52
54
|
const COLORS = ["#7c6af7", "#5b8def", "#2ea887", "#e07a3d", "#d4537e"];
|
|
55
|
+
const SEAT_HUE = {
|
|
56
|
+
infra: "#5b8def",
|
|
57
|
+
pm: "#2ea887",
|
|
58
|
+
rd: "#e07a3d",
|
|
59
|
+
design: "#7c6af7",
|
|
60
|
+
marketing: "#d4537e",
|
|
61
|
+
};
|
|
53
62
|
const state = {
|
|
54
63
|
channels: [],
|
|
55
64
|
bots: [],
|
|
@@ -64,6 +73,8 @@
|
|
|
64
73
|
};
|
|
65
74
|
|
|
66
75
|
function hue(key) {
|
|
76
|
+
const seat = SEAT_HUE[String(key).toLowerCase()];
|
|
77
|
+
if (seat) return seat;
|
|
67
78
|
let n = 0;
|
|
68
79
|
for (const ch of String(key)) n = (n + ch.charCodeAt(0) * 17) % COLORS.length;
|
|
69
80
|
return COLORS[n];
|
|
@@ -80,9 +91,18 @@
|
|
|
80
91
|
.trim()
|
|
81
92
|
.split(/\s+/);
|
|
82
93
|
if (parts[0] && /[A-Za-z]/.test(parts[0][0])) {
|
|
83
|
-
|
|
94
|
+
const letters = parts.slice(0, 2).map((part) => part[0]).filter(Boolean);
|
|
95
|
+
if (
|
|
96
|
+
letters.length === 2 &&
|
|
97
|
+
/[A-Za-z]/.test(letters[0]) &&
|
|
98
|
+
/[A-Za-z]/.test(letters[1])
|
|
99
|
+
) {
|
|
100
|
+
return letters.join("").toUpperCase();
|
|
101
|
+
}
|
|
102
|
+
return letters[0].toUpperCase();
|
|
84
103
|
}
|
|
85
|
-
|
|
104
|
+
const glyph = Array.from(String(name).replace(/\s+/g, ""))[0] || "?";
|
|
105
|
+
return glyph;
|
|
86
106
|
}
|
|
87
107
|
function portraitUrl(bot) {
|
|
88
108
|
const url = bot && typeof bot.portrait === "string" ? bot.portrait.trim() : "";
|
|
@@ -121,6 +141,13 @@
|
|
|
121
141
|
? "/dms/" + encodeURIComponent(r.id) + "/abort"
|
|
122
142
|
: "/channels/" + encodeURIComponent(r.id) + "/abort";
|
|
123
143
|
}
|
|
144
|
+
function steerUrl() {
|
|
145
|
+
const r = currentRoomRef();
|
|
146
|
+
if (!r) return "";
|
|
147
|
+
return r.kind === "dm"
|
|
148
|
+
? "/dms/" + encodeURIComponent(r.id) + "/steer"
|
|
149
|
+
: "/channels/" + encodeURIComponent(r.id) + "/steer";
|
|
150
|
+
}
|
|
124
151
|
function parseHash() {
|
|
125
152
|
const raw = location.hash.replace(/^#/, "");
|
|
126
153
|
if (raw.startsWith("d/")) {
|
|
@@ -143,15 +170,17 @@
|
|
|
143
170
|
const h = window.visualViewport ? window.visualViewport.height : window.innerHeight;
|
|
144
171
|
document.documentElement.style.setProperty("--vvh", h + "px");
|
|
145
172
|
}
|
|
146
|
-
function toast(text) {
|
|
173
|
+
function toast(text, opts) {
|
|
147
174
|
const el = document.getElementById("flash");
|
|
175
|
+
const ok = Boolean(opts && opts.ok);
|
|
148
176
|
el.hidden = false;
|
|
149
177
|
el.textContent = text;
|
|
178
|
+
el.classList.toggle("err", !ok);
|
|
150
179
|
el.classList.add("on");
|
|
151
180
|
clearTimeout(toast.timer);
|
|
152
181
|
toast.timer = setTimeout(() => {
|
|
153
182
|
el.classList.remove("on");
|
|
154
|
-
}, 1800);
|
|
183
|
+
}, (opts && opts.ms) || 1800);
|
|
155
184
|
}
|
|
156
185
|
function relTime(iso) {
|
|
157
186
|
if (!iso) return t("noActivity");
|
|
@@ -206,17 +235,54 @@
|
|
|
206
235
|
.replace(/```[\s\S]*?```/g, " ")
|
|
207
236
|
.replace(/`[^`]*`/g, " ");
|
|
208
237
|
}
|
|
238
|
+
function deferredHandles(text) {
|
|
239
|
+
const raw = String(text || "");
|
|
240
|
+
const out = [];
|
|
241
|
+
const add = (name) => {
|
|
242
|
+
const key = String(name || "").toLowerCase();
|
|
243
|
+
if (key && out.indexOf(key) < 0) out.push(key);
|
|
244
|
+
};
|
|
245
|
+
const scan = raw.replace(/```[\s\S]*?```/g, " ");
|
|
246
|
+
const cue =
|
|
247
|
+
/(?:完成後|通過後|最後(?:才|由)?|才需要|才叫|才交)[^@\n]{0,24}@([A-Za-z0-9_-]+)/gi;
|
|
248
|
+
const bare =
|
|
249
|
+
/(?:完成後|才需要|才叫)\s*(?:call|ask|由)\s*`?@?([A-Za-z0-9_-]+)`?/gi;
|
|
250
|
+
let row;
|
|
251
|
+
while ((row = cue.exec(scan))) add(row[1]);
|
|
252
|
+
while ((row = bare.exec(scan))) add(row[1]);
|
|
253
|
+
const later =
|
|
254
|
+
/^[ \t]*(?:\d+[.)、]\s*|[-*•]\s+)(?:通過後|最後|然後|之後|完成後)\s*`?@([A-Za-z0-9_-]+)`?/i;
|
|
255
|
+
const lines = raw.split(/\r?\n/);
|
|
256
|
+
for (let i = 0; i < lines.length; i++) {
|
|
257
|
+
const hit = lines[i].match(later);
|
|
258
|
+
if (hit) add(hit[1]);
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
209
262
|
function summonedBotIds(text) {
|
|
210
263
|
const raw = String(text || "");
|
|
264
|
+
const deferred = {};
|
|
265
|
+
deferredHandles(raw).forEach((handle) => {
|
|
266
|
+
deferred[handle] = true;
|
|
267
|
+
});
|
|
268
|
+
const members = new Set(
|
|
269
|
+
state.kind === "channel"
|
|
270
|
+
? ((currentChannel() && currentChannel().memberIds) || [])
|
|
271
|
+
: state.kind === "dm"
|
|
272
|
+
? [state.id]
|
|
273
|
+
: [],
|
|
274
|
+
);
|
|
211
275
|
const known = new Map(
|
|
212
|
-
(state.bots || [])
|
|
213
|
-
|
|
214
|
-
bot
|
|
215
|
-
|
|
276
|
+
(state.bots || [])
|
|
277
|
+
.filter((bot) => !members.size || members.has(bot.id))
|
|
278
|
+
.map((bot) => [
|
|
279
|
+
String(bot.handle).toLowerCase(),
|
|
280
|
+
bot.id,
|
|
281
|
+
]),
|
|
216
282
|
);
|
|
217
283
|
const out = [];
|
|
218
|
-
const push = (id) => {
|
|
219
|
-
if (id && out.indexOf(id) < 0) out.push(id);
|
|
284
|
+
const push = (id, handle) => {
|
|
285
|
+
if (id && !deferred[handle] && out.indexOf(id) < 0) out.push(id);
|
|
220
286
|
};
|
|
221
287
|
let fence = false;
|
|
222
288
|
const lines = raw.split(/\r?\n/);
|
|
@@ -233,7 +299,8 @@
|
|
|
233
299
|
if (!m) continue;
|
|
234
300
|
const names = m[1].match(/@([A-Za-z0-9_-]+)/g) || [];
|
|
235
301
|
for (let n = 0; n < names.length; n++) {
|
|
236
|
-
|
|
302
|
+
const handle = names[n].slice(1).toLowerCase();
|
|
303
|
+
push(known.get(handle), handle);
|
|
237
304
|
}
|
|
238
305
|
}
|
|
239
306
|
if (out.length) return out;
|
|
@@ -241,7 +308,8 @@
|
|
|
241
308
|
const re = /(?:^|\s)@([A-Za-z0-9_-]+)\b/g;
|
|
242
309
|
let row;
|
|
243
310
|
while ((row = re.exec(scan))) {
|
|
244
|
-
|
|
311
|
+
const handle = String(row[1]).toLowerCase();
|
|
312
|
+
push(known.get(handle), handle);
|
|
245
313
|
}
|
|
246
314
|
return out;
|
|
247
315
|
}
|
|
@@ -349,6 +417,10 @@
|
|
|
349
417
|
escapeHtml(bot.id) +
|
|
350
418
|
'">' +
|
|
351
419
|
t("live.stop") +
|
|
420
|
+
'</button><button type="button" class="turn-steer" data-live-steer="' +
|
|
421
|
+
escapeHtml(bot.id) +
|
|
422
|
+
'">' +
|
|
423
|
+
t("live.steer") +
|
|
352
424
|
"</button></div></div></div></article>"
|
|
353
425
|
);
|
|
354
426
|
})
|
|
@@ -412,10 +484,28 @@
|
|
|
412
484
|
root.innerHTML = notice + rows.map(msgHtml).join("") + turnStatusHtml();
|
|
413
485
|
if (pin) root.scrollTop = root.scrollHeight;
|
|
414
486
|
}
|
|
487
|
+
function steerModLabel() {
|
|
488
|
+
return /Mac|iPhone|iPad/.test(navigator.platform || "") ? "\u2318" : "Ctrl+";
|
|
489
|
+
}
|
|
490
|
+
function syncSteerHint() {
|
|
491
|
+
const hint = document.getElementById("composer-steer");
|
|
492
|
+
if (!hint) return;
|
|
493
|
+
if (!isBusy()) {
|
|
494
|
+
hint.hidden = true;
|
|
495
|
+
hint.textContent = "";
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const hintKey = t("steer.hint", { mod: steerModLabel() });
|
|
499
|
+
hint.hidden = false;
|
|
500
|
+
hint.textContent = hintKey === "steer.hint" ? t("live.steer") : hintKey;
|
|
501
|
+
}
|
|
415
502
|
function syncComposer() {
|
|
416
503
|
const busy = isBusy();
|
|
417
504
|
document.getElementById("send").hidden = busy;
|
|
418
505
|
document.getElementById("stop").hidden = !busy;
|
|
506
|
+
const steer = document.getElementById("steer");
|
|
507
|
+
if (steer) steer.hidden = !busy;
|
|
508
|
+
syncSteerHint();
|
|
419
509
|
}
|
|
420
510
|
function navRow(opts) {
|
|
421
511
|
const live = opts.busy
|
|
@@ -681,6 +771,63 @@
|
|
|
681
771
|
renderThread();
|
|
682
772
|
await loadMessages().catch(() => {});
|
|
683
773
|
}
|
|
774
|
+
async function sendSteer(raw, botId) {
|
|
775
|
+
const text = String(raw || "").trim();
|
|
776
|
+
if (!text || !state.kind) return;
|
|
777
|
+
const payload = { body: text };
|
|
778
|
+
if (botId) payload.botId = botId;
|
|
779
|
+
else {
|
|
780
|
+
const ids = botsFromSend(text);
|
|
781
|
+
if (ids.length === 1) payload.botId = ids[0];
|
|
782
|
+
}
|
|
783
|
+
const res = await fetch(steerUrl(), {
|
|
784
|
+
method: "POST",
|
|
785
|
+
headers: { "content-type": "application/json" },
|
|
786
|
+
body: JSON.stringify(payload),
|
|
787
|
+
});
|
|
788
|
+
const body = await res.json().catch(() => ({}));
|
|
789
|
+
if (!res.ok) throw new Error(body.error || "steer failed");
|
|
790
|
+
await loadMessages({ merge: true }).catch(() => {});
|
|
791
|
+
await pollLive();
|
|
792
|
+
const who = body.message && body.message.steerBotId
|
|
793
|
+
? botById(body.message.steerBotId)
|
|
794
|
+
: null;
|
|
795
|
+
toast(
|
|
796
|
+
who ? t("steer.tagTo", { who: who.name || who.handle }) : t("steer.tag"),
|
|
797
|
+
{ ok: true, ms: 2000 },
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
async function steerDraft(botId) {
|
|
801
|
+
const draft = document.getElementById("draft");
|
|
802
|
+
const raw = draft.value.trim();
|
|
803
|
+
if (!raw) {
|
|
804
|
+
draft.focus();
|
|
805
|
+
toast(t("live.steer"), { ok: true, ms: 1400 });
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
draft.value = "";
|
|
809
|
+
fitDraft();
|
|
810
|
+
hideMention();
|
|
811
|
+
state.messages = state.messages.concat([
|
|
812
|
+
{
|
|
813
|
+
id: "pending-steer-" + Date.now(),
|
|
814
|
+
author: "you",
|
|
815
|
+
body: raw,
|
|
816
|
+
createdAt: new Date().toISOString(),
|
|
817
|
+
},
|
|
818
|
+
]);
|
|
819
|
+
renderThread();
|
|
820
|
+
try {
|
|
821
|
+
await sendSteer(raw, botId);
|
|
822
|
+
} catch (err) {
|
|
823
|
+
draft.value = raw;
|
|
824
|
+
fitDraft();
|
|
825
|
+
await loadMessages().catch(() => {});
|
|
826
|
+
toast(err.message || String(err));
|
|
827
|
+
}
|
|
828
|
+
syncComposer();
|
|
829
|
+
schedule();
|
|
830
|
+
}
|
|
684
831
|
async function sendDraft() {
|
|
685
832
|
const draft = document.getElementById("draft");
|
|
686
833
|
const raw = draft.value.trim();
|
|
@@ -736,11 +883,22 @@
|
|
|
736
883
|
document.getElementById("stop").addEventListener("click", () => {
|
|
737
884
|
stopTurn();
|
|
738
885
|
});
|
|
886
|
+
document.getElementById("steer").addEventListener("click", () => {
|
|
887
|
+
steerDraft().catch((err) => toast(err.message || String(err)));
|
|
888
|
+
});
|
|
739
889
|
document.getElementById("thread").addEventListener("click", (event) => {
|
|
740
890
|
const stop = event.target.closest("[data-live-stop]");
|
|
741
|
-
if (
|
|
891
|
+
if (stop) {
|
|
892
|
+
event.preventDefault();
|
|
893
|
+
stopTurn(stop.getAttribute("data-live-stop"));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const steer = event.target.closest("[data-live-steer]");
|
|
897
|
+
if (!steer) return;
|
|
742
898
|
event.preventDefault();
|
|
743
|
-
|
|
899
|
+
steerDraft(steer.getAttribute("data-live-steer")).catch((err) =>
|
|
900
|
+
toast(err.message || String(err)),
|
|
901
|
+
);
|
|
744
902
|
});
|
|
745
903
|
document.getElementById("draft").addEventListener("input", () => {
|
|
746
904
|
fitDraft();
|
|
@@ -748,6 +906,18 @@
|
|
|
748
906
|
});
|
|
749
907
|
document.getElementById("draft").addEventListener("keydown", (event) => {
|
|
750
908
|
const pop = document.getElementById("mention-pop");
|
|
909
|
+
if (
|
|
910
|
+
pop.hidden &&
|
|
911
|
+
event.key === "Enter" &&
|
|
912
|
+
!event.shiftKey &&
|
|
913
|
+
(event.metaKey || event.ctrlKey)
|
|
914
|
+
) {
|
|
915
|
+
if (isBusy()) {
|
|
916
|
+
event.preventDefault();
|
|
917
|
+
steerDraft().catch((err) => toast(err.message || String(err)));
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
751
921
|
if (pop.hidden) return;
|
|
752
922
|
if (event.key === "ArrowDown") {
|
|
753
923
|
event.preventDefault();
|
package/src/public/settings.html
CHANGED
|
@@ -64,13 +64,10 @@
|
|
|
64
64
|
</div>
|
|
65
65
|
<p class="main-now" id="main-now"></p>
|
|
66
66
|
<div class="toggles">
|
|
67
|
-
<span>
|
|
68
|
-
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
<option value="medium">Medium</option>
|
|
72
|
-
<option value="high">High</option>
|
|
73
|
-
</select>
|
|
67
|
+
<span id="reasoning-wrap">
|
|
68
|
+
<span data-i18n="reasoning">推理強度</span>
|
|
69
|
+
<select id="reasoning"></select>
|
|
70
|
+
</span>
|
|
74
71
|
<label><input type="checkbox" id="fast" /> Fast</label>
|
|
75
72
|
</div>
|
|
76
73
|
<div class="panel-head" style="margin-top:1rem">
|
|
@@ -278,6 +275,51 @@
|
|
|
278
275
|
)
|
|
279
276
|
.join("");
|
|
280
277
|
if (preferred && preferred.provider === psel.value) msel.value = preferred.model;
|
|
278
|
+
if (psel.id === "provider") fillReasoning();
|
|
279
|
+
}
|
|
280
|
+
function reasonLabel(value) {
|
|
281
|
+
if (value === "none") return t("reason.none");
|
|
282
|
+
if (value === "minimal") return t("reason.min");
|
|
283
|
+
if (value === "low") return t("reason.low");
|
|
284
|
+
if (value === "medium") return t("reason.mid");
|
|
285
|
+
if (value === "high") return t("reason.high");
|
|
286
|
+
if (value === "xhigh") return t("reason.xhigh");
|
|
287
|
+
if (value === "max") return t("reason.max");
|
|
288
|
+
return value || "";
|
|
289
|
+
}
|
|
290
|
+
function fillReasoning() {
|
|
291
|
+
const sel = document.getElementById("reasoning");
|
|
292
|
+
const wrap = document.getElementById("reasoning-wrap");
|
|
293
|
+
const ref = currentRef();
|
|
294
|
+
const group = pickerGroups().find((p) => p.id === (ref && ref.provider));
|
|
295
|
+
const model = ((group && group.models) || []).find(
|
|
296
|
+
(m) => m.id === (ref && ref.model),
|
|
297
|
+
);
|
|
298
|
+
const spec = (model && model.reasoning) || null;
|
|
299
|
+
const raw = spec && spec.supportedEfforts;
|
|
300
|
+
const choices = raw && raw.length
|
|
301
|
+
? spec.mandatory
|
|
302
|
+
? raw.filter((key) => key !== "none")
|
|
303
|
+
: raw.slice()
|
|
304
|
+
: [];
|
|
305
|
+
sel.innerHTML = choices
|
|
306
|
+
.map(
|
|
307
|
+
(value) =>
|
|
308
|
+
'<option value="' +
|
|
309
|
+
escapeHtml(value) +
|
|
310
|
+
'">' +
|
|
311
|
+
escapeHtml(reasonLabel(value)) +
|
|
312
|
+
"</option>",
|
|
313
|
+
)
|
|
314
|
+
.join("");
|
|
315
|
+
const want = data.reasoning;
|
|
316
|
+
if (want && choices.indexOf(want) >= 0) sel.value = want;
|
|
317
|
+
else if (spec && spec.defaultEffort && choices.indexOf(spec.defaultEffort) >= 0) {
|
|
318
|
+
sel.value = spec.defaultEffort;
|
|
319
|
+
} else if (choices.length) {
|
|
320
|
+
sel.value = choices[0];
|
|
321
|
+
}
|
|
322
|
+
if (wrap) wrap.hidden = !choices.length;
|
|
281
323
|
}
|
|
282
324
|
function currentRef() {
|
|
283
325
|
const provider = document.getElementById("provider").value;
|
|
@@ -359,7 +401,7 @@
|
|
|
359
401
|
}
|
|
360
402
|
function renderPicker() {
|
|
361
403
|
fillSelect(document.getElementById("provider"), pickerGroups(), data.default);
|
|
362
|
-
|
|
404
|
+
fillReasoning();
|
|
363
405
|
document.getElementById("fast").checked = Boolean(data.fast);
|
|
364
406
|
const mainNow = document.getElementById("main-now");
|
|
365
407
|
if (mainNow) {
|
|
@@ -696,6 +738,7 @@
|
|
|
696
738
|
data.default,
|
|
697
739
|
);
|
|
698
740
|
});
|
|
741
|
+
document.getElementById("model").addEventListener("change", fillReasoning);
|
|
699
742
|
document.getElementById("apply").addEventListener("click", () => applyPicker());
|
|
700
743
|
document.getElementById("reset-aux").addEventListener("click", async () => {
|
|
701
744
|
await applyPicker({ aux: {} });
|