@songsid/agend 2.2.0-beta.14 → 2.2.0-beta.16
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/dist/cli.js +58 -3
- package/dist/cli.js.map +1 -1
- package/dist/compact-watch.d.ts +78 -0
- package/dist/compact-watch.js +247 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +14 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +45 -5
- package/dist/daemon.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +17 -3
- package/dist/fleet-manager.js +101 -19
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-avatar.d.ts +4 -3
- package/dist/instance-avatar.js +6 -5
- package/dist/instance-avatar.js.map +1 -1
- package/dist/launchd-handoff.d.ts +43 -0
- package/dist/launchd-handoff.js +56 -0
- package/dist/launchd-handoff.js.map +1 -0
- package/dist/locale.js +24 -4
- package/dist/locale.js.map +1 -1
- package/dist/login-shell.d.ts +21 -0
- package/dist/login-shell.js +98 -0
- package/dist/login-shell.js.map +1 -0
- package/dist/provider-probe.d.ts +29 -0
- package/dist/provider-probe.js +198 -2
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-web-gateway.js +2 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +1 -1
- package/dist/quickstart-api.js +8 -6
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +12 -0
- package/dist/quickstart.js +36 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-installer.d.ts +24 -0
- package/dist/service-installer.js +49 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +11 -0
- package/dist/service-plan.js +23 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/topic-commands.d.ts +16 -0
- package/dist/topic-commands.js +39 -15
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +7 -2
- package/dist/transcript-jsonl.js +13 -7
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/ui/chat-store.js +32 -2
- package/dist/ui/chat-strings.js +8 -2
- package/dist/ui/panel-chat.js +4 -1
- package/dist/ui/settings-dialogs.js +9 -4
- package/dist/ui/settings-strings.js +6 -2
- package/dist/ui/settings-token.js +7 -0
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/app.css +1 -0
- package/dist/ui/shared/panel-view.js +8 -0
- package/dist/ui/shared/view-strings.js +8 -0
- package/dist/usage/providers.d.ts +28 -8
- package/dist/usage/providers.js +96 -41
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +26 -9
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.js +1 -1
- package/dist/web-api.js.map +1 -1
- package/dist/web-commands.d.ts +14 -1
- package/dist/web-commands.js +6 -3
- package/dist/web-commands.js.map +1 -1
- package/dist/web-upload.d.ts +7 -3
- package/dist/web-upload.js +20 -8
- package/dist/web-upload.js.map +1 -1
- package/package.json +1 -1
package/dist/transcript-jsonl.js
CHANGED
|
@@ -10,11 +10,14 @@ export const LONG_HEAD_BYTES = 8 * 1024;
|
|
|
10
10
|
* final reply, a pasted prompt) — is read whole and parsed, up to this size. Longer: skipped like any long line.
|
|
11
11
|
*/
|
|
12
12
|
export const WHOLE_LINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The line of `bytes` bytes at `offset`, as text, when it is at most `max` bytes; null otherwise or when it cannot be read whole.
|
|
15
|
+
* `handle`: read from this open file (the caller's, left open) instead of opening `path`.
|
|
16
|
+
*/
|
|
17
|
+
export async function readWholeLine(path, offset, bytes, max = WHOLE_LINE_MAX_BYTES, handle) {
|
|
15
18
|
if (bytes <= 0 || bytes > max)
|
|
16
19
|
return null;
|
|
17
|
-
const fh = await open(path, "r");
|
|
20
|
+
const fh = handle ?? await open(path, "r");
|
|
18
21
|
try {
|
|
19
22
|
const buf = Buffer.alloc(bytes);
|
|
20
23
|
for (let filled = 0; filled < bytes;) {
|
|
@@ -26,7 +29,8 @@ export async function readWholeLine(path, offset, bytes, max = WHOLE_LINE_MAX_BY
|
|
|
26
29
|
return buf.toString("utf-8");
|
|
27
30
|
}
|
|
28
31
|
finally {
|
|
29
|
-
|
|
32
|
+
if (!handle)
|
|
33
|
+
await fh.close();
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
/** A look's records in file order: each complete line, and each long line where it sits among them. */
|
|
@@ -97,11 +101,12 @@ function headText(buf, max) {
|
|
|
97
101
|
export async function readNewLines(path, fromOffset, opts = {}) {
|
|
98
102
|
const maxBytes = Math.max(1, opts.maxBytes ?? READ_MAX_BYTES);
|
|
99
103
|
const maxLine = Math.max(1, opts.maxLine ?? LINE_MAX_BYTES);
|
|
100
|
-
|
|
104
|
+
// `handle`: one file the caller opened and checked (its identity), read and sized through that handle only.
|
|
105
|
+
const stats = opts.handle ? await opts.handle.stat() : await stat(path);
|
|
101
106
|
const empty = (offset, skipping, more, longEnd) => ({ lines: [], offsets: [], newOffset: offset, long: [], more, skipping, ...(longEnd !== undefined ? { longEnd } : {}) });
|
|
102
107
|
if (stats.size <= fromOffset)
|
|
103
108
|
return empty(fromOffset, !!opts.skipping, false);
|
|
104
|
-
const fh = await open(path, "r");
|
|
109
|
+
const fh = opts.handle ?? await open(path, "r");
|
|
105
110
|
try {
|
|
106
111
|
const read = async (at, length) => {
|
|
107
112
|
const buffer = Buffer.alloc(length);
|
|
@@ -158,7 +163,8 @@ export async function readNewLines(path, fromOffset, opts = {}) {
|
|
|
158
163
|
return { lines, offsets, newOffset, long, more: window.length === budget && newOffset < stats.size, skipping: false, ...ended };
|
|
159
164
|
}
|
|
160
165
|
finally {
|
|
161
|
-
|
|
166
|
+
if (!opts.handle)
|
|
167
|
+
await fh.close();
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
const BOUNDARY_CHUNK = 64 * 1024;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-jsonl.js","sourceRoot":"","sources":["../src/transcript-jsonl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"transcript-jsonl.js","sourceRoot":"","sources":["../src/transcript-jsonl.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/chat-store.js
CHANGED
|
@@ -31,6 +31,13 @@ function writePollMine(mine) {
|
|
|
31
31
|
try { globalThis.localStorage?.setItem(POLL_MINE_KEY, JSON.stringify(mine)); } catch { /* storage unavailable */ }
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Board 003648ac: an answer the server marks `sent` (handed over, and its text says only that) leaves after this. */
|
|
35
|
+
const SENT_CLEAR_MS = 8000;
|
|
36
|
+
const COMPACT_DONE_CLEAR_MS = 6000;
|
|
37
|
+
const COMPACT_UNKNOWN_CLEAR_MS = 12000;
|
|
38
|
+
/** The fleet stops following a /compact after 10 minutes (compact-watch.ts); the page waits a minute more. */
|
|
39
|
+
const COMPACT_PAGE_LIMIT_MS = 11 * 60_000;
|
|
40
|
+
|
|
34
41
|
/** Build a store. `deps` (fetch, toast, announce, t, now) are injected so tests can drive every path. */
|
|
35
42
|
export function createChatStore(deps) {
|
|
36
43
|
const s = {
|
|
@@ -77,6 +84,7 @@ export function createChatStore(deps) {
|
|
|
77
84
|
const waits = i.state === "awaiting_input" ? (typeof i.interaction_summary === "string" ? i.interaction_summary : "") : null;
|
|
78
85
|
if (i.name === s.current && waits != null && s.awaiting[i.name] == null) deps.announce(tf("chat.awaiting", i.name));
|
|
79
86
|
s.awaiting[i.name] = waits;
|
|
87
|
+
if (i.compact) noteCompact(i.name, i.compact);
|
|
80
88
|
}
|
|
81
89
|
changed(null);
|
|
82
90
|
}
|
|
@@ -337,10 +345,32 @@ export function createChatStore(deps) {
|
|
|
337
345
|
if (!yes || !alive()) { delete s.commands[target]; changed(target); return; }
|
|
338
346
|
r = await post({ confirm: r.confirm.token });
|
|
339
347
|
}
|
|
340
|
-
|
|
341
|
-
|
|
348
|
+
const follow = r && r.follow && r.follow.kind === "compact" ? r.follow : null;
|
|
349
|
+
showCommand(target, r && r.choices ? { command, busy: false, choices: r.choices }
|
|
350
|
+
: r && r.error ? { command, busy: false, error: r.error }
|
|
351
|
+
: { command, busy: false, text: (r && r.text) || "", ...(r && r.sent === true ? { sent: true } : {}), ...(follow ? { compact: follow.id ? "running" : "unknown", watch: follow.id } : {}) });
|
|
342
352
|
changed(target);
|
|
343
353
|
}
|
|
354
|
+
// Board 003648ac: an answer the server marks `sent` leaves by itself; a sent /compact says "compacting…" until the
|
|
355
|
+
// status frame's `compact` (same watch id) says it ended, then leaves when it is done. Everything else — a refusal
|
|
356
|
+
// (even one worded as text), a list to choose from, an answer to read, a compaction timed out or lost — stays.
|
|
357
|
+
function showCommand(target, card) {
|
|
358
|
+
s.commands[target] = card;
|
|
359
|
+
const later = (ms, fn) => deps.setTimeout(() => { if (s.commands[target] === card) { fn(); changed(target); } }, ms);
|
|
360
|
+
const leave = () => { delete s.commands[target]; };
|
|
361
|
+
if (card.busy || card.error || card.choices || card.compact === "timeout" || card.compact === "lost") return;
|
|
362
|
+
if (card.compact === "running") later(COMPACT_PAGE_LIMIT_MS, () => showCommand(target, { ...card, compact: "timeout" }));
|
|
363
|
+
else if (card.compact === "done") later(COMPACT_DONE_CLEAR_MS, leave);
|
|
364
|
+
else if (card.compact === "unknown") later(COMPACT_UNKNOWN_CLEAR_MS, leave);
|
|
365
|
+
else if (card.sent === true) later(SENT_CLEAR_MS, leave);
|
|
366
|
+
}
|
|
367
|
+
/** The status frame's word on a followed /compact: done, timed out, or lost. */
|
|
368
|
+
function noteCompact(name, view) {
|
|
369
|
+
const c = s.commands[name];
|
|
370
|
+
if (!c || c.compact !== "running" || !view || view.id !== c.watch || !["done", "timeout", "lost"].includes(view.state)) return;
|
|
371
|
+
showCommand(name, { ...c, compact: view.state });
|
|
372
|
+
changed(name);
|
|
373
|
+
}
|
|
344
374
|
function dismissCommand(target) { delete s.commands[target]; changed(target); }
|
|
345
375
|
/** Whether a command can start for `target` now (none is out): checked before a typed command line is consumed. */
|
|
346
376
|
const commandFree = (target) => !(s.commands[target] && s.commands[target].busy);
|
package/dist/ui/chat-strings.js
CHANGED
|
@@ -4,6 +4,9 @@ import { register } from "/assets/app-i18n.js";
|
|
|
4
4
|
register("chat", {
|
|
5
5
|
en: {
|
|
6
6
|
cmdConfirm: "Clear", cmdConfirmBackend: "Switch and restart", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
|
|
7
|
+
compactRunning: "Compacting…", compactDone: "✅ Conversation compacted", compactUnknown: "This backend does not report when compaction ends.",
|
|
8
|
+
compactTimeout: "No sign that compaction finished within {0} minutes — check the agent's own chat.",
|
|
9
|
+
compactLost: "Can't follow this compaction: the command did not reach the agent, or the agent stopped or changed backend.",
|
|
7
10
|
cmdChoose: "Choose", cmdCurrent: "current", chipCompact: "Compact", chipClear: "Clear…", chipContext: "Context {0}% used",
|
|
8
11
|
chipModel: "Model: {0} — choose another", chipEffort: "Effort: {0} — choose another",
|
|
9
12
|
cmd_ctx: "Context use, model and effort", cmd_compact: "Compact the conversation", cmd_clear: "Clear the conversation (asks first)",
|
|
@@ -19,7 +22,7 @@ register("chat", {
|
|
|
19
22
|
a11yWorking: "{0} started working", a11yIdle: "{0} finished", a11yReplied: "{0} replied", conversationWith: "Conversation with {0}",
|
|
20
23
|
stopping: "Stopping {0}…", awaiting: "{0} is waiting for your input", approxNote: "Read from the terminal, so approximate", elapsedTitle: "Time since this page saw it start (approximate)",
|
|
21
24
|
dropHere: "Drop files to attach them", asText: "As text", pasteAttached: "A long paste ({0} characters) was attached as a text file", pasteKeptAsText: "No room for another attachment: the long paste stays as text",
|
|
22
|
-
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerMore: "/ for commands · Esc stops the reply", attachTitle: "Attach files (
|
|
25
|
+
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerMore: "/ for commands · Esc stops the reply", attachTitle: "Attach files (any type — or paste / drop them)",
|
|
23
26
|
pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
|
|
24
27
|
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
|
|
25
28
|
pvConfirm: "Allow HTML previews?\n\nThis applies on every device where you are signed in. A preview runs the agent's HTML in an isolated frame. It cannot use your login, but it may be able to send data out, and it can slow or freeze this tab. You still click Preview each time.",
|
|
@@ -89,6 +92,9 @@ register("chat", {
|
|
|
89
92
|
},
|
|
90
93
|
"zh-TW": {
|
|
91
94
|
cmdConfirm: "清空", cmdConfirmBackend: "切換並重新啟動", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdDismiss: "關閉", cmdCommands: "指令",
|
|
95
|
+
compactRunning: "壓縮中…", compactDone: "✅ 對話已壓縮", compactUnknown: "這個後端不會回報壓縮何時完成。",
|
|
96
|
+
compactTimeout: "{0} 分鐘內沒有看到壓縮完成的紀錄,請到代理自己的對話確認。",
|
|
97
|
+
compactLost: "無法再追蹤這次壓縮:指令沒有送到代理,或代理已停止、換了後端。",
|
|
92
98
|
cmdChoose: "選擇", cmdCurrent: "目前", chipCompact: "壓縮", chipClear: "清空…", chipContext: "已用 {0}% 的 context",
|
|
93
99
|
chipModel: "模型:{0}——選擇其他", chipEffort: "推理強度:{0}——選擇其他",
|
|
94
100
|
cmd_ctx: "context 用量、模型與推理強度", cmd_compact: "壓縮對話", cmd_clear: "清空對話(會先確認)",
|
|
@@ -104,7 +110,7 @@ register("chat", {
|
|
|
104
110
|
a11yWorking: "{0} 開始工作", a11yIdle: "{0} 已完成", a11yReplied: "{0} 已回覆", conversationWith: "與 {0} 的對話",
|
|
105
111
|
stopping: "正在中止 {0}…", awaiting: "{0} 正在等你回應", approxNote: "從終端畫面判讀,僅供參考", elapsedTitle: "從這個頁面看到它開始工作起算(約略)",
|
|
106
112
|
dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
|
|
107
|
-
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerMore: "/ 叫出指令 · Esc 中止回覆", attachTitle: "
|
|
113
|
+
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerMore: "/ 叫出指令 · Esc 中止回覆", attachTitle: "附加檔案(任何類型,也可以貼上或拖曳)",
|
|
108
114
|
pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
|
|
109
115
|
pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
|
|
110
116
|
pvConfirm: "要允許預覽 HTML 嗎?\n\n這會套用到你登入的每一台裝置。預覽會在隔離的框架裡執行 agent 的 HTML。它無法使用你的登入,但可能把資料傳出去,也可能讓這個分頁變慢或卡住。每次仍需要你按「預覽」。",
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -322,6 +322,9 @@ function CommandCard({ name, lease }) {
|
|
|
322
322
|
aria-label=${t("chat.cmdDismiss")} onClick=${() => store.dismissCommand(name)}><${Icon} name="close" size=${14} /></button>`}</div>
|
|
323
323
|
${c.error ? html`<p class="cmd-text">${c.error}</p>` : null}
|
|
324
324
|
${c.text ? html`<pre class="cmd-text">${c.text}</pre>` : null}
|
|
325
|
+
${c.compact ? html`<p class="cmd-text cmd-phase">${c.compact === "running" ? t("chat.compactRunning")
|
|
326
|
+
: c.compact === "done" ? t("chat.compactDone") : c.compact === "timeout" ? t("chat.compactTimeout", "10")
|
|
327
|
+
: c.compact === "lost" ? t("chat.compactLost") : t("chat.compactUnknown")}</p>` : null}
|
|
325
328
|
${c.choices && c.choices.prompt ? html`<pre class="cmd-text">${c.choices.prompt}</pre>` : null}
|
|
326
329
|
${c.choices ? html`<div class="cmd-choices" role="group" aria-label=${t("chat.cmdChoose")}>${c.choices.options.map((o) => html`<button key=${o.id} type="button"
|
|
327
330
|
class=${`btn btn-sm${o.id === c.choices.current ? " current" : ""}`} aria-current=${o.id === c.choices.current ? "true" : undefined} onClick=${() => run(o.id)}>${o.label}</button>`)}</div>` : null}
|
|
@@ -434,7 +437,7 @@ function Composer({ name, lease, busy }) {
|
|
|
434
437
|
${files.length ? html`<div class="pending-files">${files.map((f, i) => html`<${FileChip} key=${`${f.name}-${i}`} f=${f} i=${i} name=${name} />`)}</div>` : null}
|
|
435
438
|
<div class="composer">
|
|
436
439
|
<button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
|
|
437
|
-
<input ref=${fileIn} type="file" multiple hidden
|
|
440
|
+
<input ref=${fileIn} type="file" multiple hidden
|
|
438
441
|
onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />
|
|
439
442
|
<textarea id="msgIn" ref=${input} rows="1" value=${value} placeholder=${t("chat.composerPlaceholder", name)} aria-label=${t("chat.composerPlaceholder", name)}
|
|
440
443
|
autocomplete="off" aria-controls=${palette ? "cmdPalette" : undefined} aria-expanded=${palette ? "true" : "false"}
|
|
@@ -10,7 +10,7 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
10
10
|
import { toast } from "/assets/ui-toast.js";
|
|
11
11
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
12
12
|
import { api, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
13
|
-
import { botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
13
|
+
import { adminUserIdError, adminUserIdPattern, botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
14
14
|
import { DiscordServerPicker } from "./settings-discord.js";
|
|
15
15
|
import { startOperation } from "./settings-apply.js";
|
|
16
16
|
import {
|
|
@@ -466,17 +466,18 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
466
466
|
*/
|
|
467
467
|
export function NewBotDialog({ ctx, onClose }) {
|
|
468
468
|
const lease = useLease("new-bot");
|
|
469
|
-
const [f, setF] = useState({ type: "discord", id: "", token: "", identity: null, guilds: [], group: "", general: "" });
|
|
469
|
+
const [f, setF] = useState({ type: "discord", id: "", token: "", identity: null, guilds: [], group: "", general: "", admin: "" });
|
|
470
470
|
const [plan, setPlan] = useState(null);
|
|
471
471
|
const [busy, setBusy] = useState(false);
|
|
472
472
|
const [err, setErr] = useState("");
|
|
473
473
|
// The form's revision (#1529 review): any change to the token, platform, id or group moves it; a Verify or a plan
|
|
474
474
|
// that began before is dropped when it lands — never restoring an old token, naming it, or planning for it.
|
|
475
475
|
const rev = useRef(0);
|
|
476
|
-
const body = (x) => ({ platform: x.type, connection_only: true, ...(x.id.trim() ? { channel_id: x.id.trim() } : {}),
|
|
476
|
+
const body = (x) => ({ platform: x.type, connection_only: true, admin_user_id: x.admin.trim(), ...(x.id.trim() ? { channel_id: x.id.trim() } : {}),
|
|
477
477
|
...(x.group.trim() ? (x.type === "discord" ? { guild_id: x.group.trim() } : { group_id: x.group.trim() }) : {}),
|
|
478
478
|
...(x.type === "discord" && x.general.trim() ? { general_channel_id: x.general.trim() } : {}) });
|
|
479
479
|
const planFor = async (x) => {
|
|
480
|
+
if (adminUserIdError(x.type, x.admin.trim())) { setPlan(null); return null; }
|
|
480
481
|
const at = rev.current;
|
|
481
482
|
const res = await api("/api/settings/quickstart/plan", { method: "POST", body: JSON.stringify(body(x)) }).catch(() => ({ ok: false, body: {} }));
|
|
482
483
|
if (!lease.current() || rev.current !== at) return null;
|
|
@@ -501,6 +502,8 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
501
502
|
};
|
|
502
503
|
const save = async () => {
|
|
503
504
|
if (!f.identity || !f.identity.valid) { setErr(tn("wizardNeedVerify")); return; }
|
|
505
|
+
const adminError = adminUserIdError(f.type, f.admin.trim());
|
|
506
|
+
if (adminError) { setErr(adminError); return; }
|
|
504
507
|
const x = f;
|
|
505
508
|
setBusy(true);
|
|
506
509
|
const p = await planFor(x);
|
|
@@ -514,7 +517,7 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
514
517
|
};
|
|
515
518
|
// A change of platform or id is another connection: what was verified and planned no longer applies. The server or
|
|
516
519
|
// group does not change the id or the token's name, so the plan (and its name under Advanced) stays.
|
|
517
|
-
const set = (k) => (v) => { rev.current++; setF((x) => ({ ...x, [k]: v, ...(k === "type" ? { identity: null, guilds: [], group: "", general: "" } : {}) })); if (k !== "group" && k !== "general") setPlan(null); };
|
|
520
|
+
const set = (k) => (v) => { rev.current++; setF((x) => ({ ...x, [k]: v, ...(k === "type" ? { identity: null, guilds: [], group: "", general: "", admin: "" } : {}) })); if (k !== "group" && k !== "general") setPlan(null); };
|
|
518
521
|
return html`<${Dialog} title=${tn("newBot")} onClose=${onClose} busy=${busy}
|
|
519
522
|
actions=${html`<button type="button" class="btn" disabled=${busy} onClick=${onClose}>${tn("cancel")}</button>
|
|
520
523
|
<button type="button" class="btn btn-primary" disabled=${busy || !f.identity || !f.identity.valid} onClick=${save}>${tn("save")}</button>`}>
|
|
@@ -528,6 +531,8 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
528
531
|
guild=${f.group} onGuild=${set("group")} channel=${f.general} onChannel=${set("general")} />`
|
|
529
532
|
: html`<div class="field"><label for="nb-group">${tn("groupIdField")}</label>
|
|
530
533
|
<input id="nb-group" type="text" value=${f.group} onInput=${(e) => set("group")(e.target.value.trim())} /></div>`}
|
|
534
|
+
<div class="field"><label for="nb-user">${tn("wizardAdminUser")}</label>
|
|
535
|
+
<input id="nb-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(f.type)} value=${f.admin} onInput=${(e) => set("admin")(e.target.value.trim())} /></div>
|
|
531
536
|
<${Drawer} title=${tn("advancedSection")}>
|
|
532
537
|
<div class="field"><label for="nb-id">${tn("connectionIdOptional")}</label><input id="nb-id" type="text" placeholder=${plan ? plan.channel_id : f.type}
|
|
533
538
|
value=${f.id} onInput=${(e) => set("id")(e.target.value)} onBlur=${() => { if (f.identity && f.identity.valid) planFor(f); }} /></div>
|
|
@@ -164,7 +164,9 @@ register("settings", {
|
|
|
164
164
|
wizardWaitingStart: "Waiting for a message in the group…",
|
|
165
165
|
wizardNoStartYet: "No group message yet — post one and press Detect again.",
|
|
166
166
|
wizardGeneralChannel: "General channel id",
|
|
167
|
-
wizardAdminUser: "Your user
|
|
167
|
+
wizardAdminUser: "Your user ID (required; becomes the first allowed user)",
|
|
168
|
+
wizardNeedDiscordAdmin: "Enter your Discord user ID: 17–20 digits, without a leading zero.",
|
|
169
|
+
wizardNeedTelegramAdmin: "Enter your Telegram user ID: 1–20 positive digits, without a leading zero.",
|
|
168
170
|
wizardNeedVerify: "Verify the token before continuing.",
|
|
169
171
|
wizardPlanLoading: "Building the plan…",
|
|
170
172
|
wizardWillWrite: "This will be written:",
|
|
@@ -535,7 +537,9 @@ register("settings", {
|
|
|
535
537
|
wizardWaitingStart: "等待群組訊息中…",
|
|
536
538
|
wizardNoStartYet: "還沒收到群組訊息 —— 發一則之後再按一次。",
|
|
537
539
|
wizardGeneralChannel: "General 頻道 id",
|
|
538
|
-
wizardAdminUser: "
|
|
540
|
+
wizardAdminUser: "你的使用者 ID(必填;會成為第一個允許使用者)",
|
|
541
|
+
wizardNeedDiscordAdmin: "請輸入 Discord 使用者 ID:17–20 位數字,開頭不能是 0。",
|
|
542
|
+
wizardNeedTelegramAdmin: "請輸入 Telegram 使用者 ID:1–20 位正整數字,開頭不能是 0。",
|
|
539
543
|
wizardNeedVerify: "請先驗證 token 再繼續。",
|
|
540
544
|
wizardPlanLoading: "正在產生方案…",
|
|
541
545
|
wizardWillWrite: "將寫入:",
|
|
@@ -10,6 +10,13 @@ import { api } from "./settings-confirm.js";
|
|
|
10
10
|
|
|
11
11
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
12
12
|
|
|
13
|
+
// UI feedback only: the plan and commit independently enforce this on the server.
|
|
14
|
+
export const adminUserIdPattern = (platform) => platform === "discord" ? "[1-9][0-9]{16,19}" : "[1-9][0-9]{0,19}";
|
|
15
|
+
export function adminUserIdError(platform, value) {
|
|
16
|
+
return typeof value === "string" && new RegExp(`^${adminUserIdPattern(platform)}$`).test(value)
|
|
17
|
+
? null : tn(platform === "discord" ? "wizardNeedDiscordAdmin" : "wizardNeedTelegramAdmin");
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/** Who this token is: { valid, username?, reason? } — one outbound check, the token sent once and not kept. */
|
|
14
21
|
export async function verifyBotToken(platform, token) {
|
|
15
22
|
const res = await api("/api/settings/quickstart/probe", { method: "POST", body: JSON.stringify({ action: "verify", platform, token }) })
|
|
@@ -10,7 +10,7 @@ import { api } from "./settings-confirm.js";
|
|
|
10
10
|
import { startOperation } from "./settings-apply.js";
|
|
11
11
|
import { BACKENDS, DEFAULT_SCHEMA, impactOf, toYaml } from "./settings-model.js";
|
|
12
12
|
import { Drawer, Select } from "./settings-dialogs.js";
|
|
13
|
-
import { botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
13
|
+
import { adminUserIdError, adminUserIdPattern, botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
14
14
|
import { DiscordServerPicker } from "./settings-discord.js";
|
|
15
15
|
|
|
16
16
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -90,6 +90,8 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
90
90
|
if (w.step === 2) { setW({ ...w, step: 3 }); return; }
|
|
91
91
|
if (w.step === 3) {
|
|
92
92
|
if (!w.identity || !w.identity.valid) { setErr(tn("wizardNeedVerify")); return; }
|
|
93
|
+
const adminError = adminUserIdError(w.platform, w.admin_user_id);
|
|
94
|
+
if (adminError) { setErr(adminError); return; }
|
|
93
95
|
const at = rev.current;
|
|
94
96
|
setBusy(tn("wizardPlanLoading"));
|
|
95
97
|
const res = await post("/api/settings/quickstart/plan", input());
|
|
@@ -146,12 +148,12 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
146
148
|
<${DiscordServerPicker} idPrefix="wz" token=${w.identity && w.identity.valid ? w.token : ""} bot=${w.identity && botHandle(w.identity.username)}
|
|
147
149
|
invite=${w.identity && w.identity.invite} portal=${w.identity && w.identity.portal} guilds=${w.guilds} onGuilds=${(g) => setW((x) => ({ ...x, guilds: g }))}
|
|
148
150
|
guild=${w.guild_id} onGuild=${set("guild_id")} channel=${w.general_channel_id} onChannel=${set("general_channel_id")} />
|
|
149
|
-
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" placeholder=${tn("discordUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`
|
|
151
|
+
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(w.platform)} placeholder=${tn("discordUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`
|
|
150
152
|
: html`
|
|
151
153
|
<div class="field"><label for="wz-group">${tn("groupIdField")}</label><input id="wz-group" type="text" placeholder="-1001234567890" value=${w.group_id} onInput=${(e) => set("group_id")(e.target.value.trim())} />
|
|
152
154
|
<p class="note">${tn("wizardDetectHint")}</p></div>
|
|
153
155
|
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm" disabled=${!!busy} onClick=${detect}>${tn("wizardDetect")}</button></div>
|
|
154
|
-
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" placeholder=${tn("telegramUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`}`;
|
|
156
|
+
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(w.platform)} placeholder=${tn("telegramUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`}`;
|
|
155
157
|
} else {
|
|
156
158
|
body = !w.plan ? html`<p class="note">${tn("wizardPlanLoading")}</p>` : html`
|
|
157
159
|
<p><strong>${tn("wizardWillWrite")}</strong></p>
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -1075,6 +1075,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1075
1075
|
.cmd-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
|
|
1076
1076
|
.cmd-name { font-weight: var(--fw-semibold); }
|
|
1077
1077
|
.cmd-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-ui); color: var(--text-2); }
|
|
1078
|
+
.cmd-phase { color: var(--text); font-weight: var(--fw-semibold); }
|
|
1078
1079
|
.cmd-choices { display: flex; flex-wrap: wrap; gap: var(--s-1); max-height: 180px; overflow-y: auto; }
|
|
1079
1080
|
.cmd-choices .btn.current { border-color: var(--accent); color: var(--accent); }
|
|
1080
1081
|
.quick-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); font-size: var(--fs-xs); color: var(--text-2); }
|
|
@@ -317,6 +317,8 @@ function EditProfile({ it, onClose }) {
|
|
|
317
317
|
const [f, setF] = useState({ display: it.display_name || "", role: it.role || "", desc: it.description || "" });
|
|
318
318
|
const [busy, setBusy] = useState(false);
|
|
319
319
|
const [msg, setMsg] = useState(null);
|
|
320
|
+
// #1630 follow-up: the uploaded picture is removed on Save (Cancel keeps it); a newly picked file wins over removing.
|
|
321
|
+
const [removePic, setRemovePic] = useState(false);
|
|
320
322
|
const file = useRef(null);
|
|
321
323
|
async function save() {
|
|
322
324
|
if (busy) return;
|
|
@@ -326,6 +328,7 @@ function EditProfile({ it, onClose }) {
|
|
|
326
328
|
const name = encodeURIComponent(it.instance_name);
|
|
327
329
|
const draft = { display_name: f.display, role: f.role, description: f.desc };
|
|
328
330
|
const picked = file.current && file.current.files && file.current.files[0];
|
|
331
|
+
const removing = removePic;
|
|
329
332
|
try {
|
|
330
333
|
const r = await fetch(`/api/profile/${name}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(draft) });
|
|
331
334
|
if (!lease.current()) return;
|
|
@@ -334,6 +337,9 @@ function EditProfile({ it, onClose }) {
|
|
|
334
337
|
if (picked) {
|
|
335
338
|
const ar = await fetch(`/api/avatar/${name}`, { method: "POST", headers: { "Content-Type": picked.type }, body: picked });
|
|
336
339
|
if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error(tn("avatarFailed", { error: e.error || ar.status })); }
|
|
340
|
+
} else if (removing) { // a newly picked file replaces it instead
|
|
341
|
+
const dr = await fetch(`/api/avatar/${name}`, { method: "DELETE" });
|
|
342
|
+
if (!dr.ok) { const e = await dr.json().catch(() => ({})); throw new Error(tn("avatarRemoveFailed", { error: e.error || dr.status })); }
|
|
337
343
|
}
|
|
338
344
|
if (!lease.current()) return;
|
|
339
345
|
setBusy(false);
|
|
@@ -348,6 +354,8 @@ function EditProfile({ it, onClose }) {
|
|
|
348
354
|
<label class="field"><span>${tn("role")}</span><input value=${f.role} onInput=${(e) => setF({ ...f, role: e.target.value })} /></label>
|
|
349
355
|
<label class="field"><span>${tn("description")}</span><textarea rows="3" value=${f.desc} onInput=${(e) => setF({ ...f, desc: e.target.value })}></textarea></label>
|
|
350
356
|
<label class="field"><span>${tn("avatar")}</span><input ref=${file} type="file" accept="image/png,image/jpeg,image/gif,image/webp" /></label>
|
|
357
|
+
${it.has_avatar ? html`<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${removePic ? "true" : "false"}
|
|
358
|
+
onClick=${() => setRemovePic(!removePic)}>${tn(removePic ? "keepPicture" : "removePicture")}</button>${removePic ? html`<span class="muted">${tn("removePictureNote")}</span>` : null}</div>` : null}
|
|
351
359
|
</fieldset></form>
|
|
352
360
|
${msg ? html`<p class="err" role="alert">${msg}${msg === tn("signInToSave") ? html` <a class="link" href=${signInHref()}>${t("app.signIn")}</a>` : null}</p>` : null}
|
|
353
361
|
</${Dialog}>`;
|
|
@@ -152,6 +152,10 @@ register("view", {
|
|
|
152
152
|
"paneFailed": "⚠ Could not load pane for {name} (HTTP {status}).",
|
|
153
153
|
"signInToSave": "Sign in to save — your session may have ended.",
|
|
154
154
|
"avatarFailed": "Avatar upload failed: {error}",
|
|
155
|
+
"avatarRemoveFailed": "Could not remove the picture: {error}",
|
|
156
|
+
"removePicture": "Remove picture",
|
|
157
|
+
"keepPicture": "Keep picture",
|
|
158
|
+
"removePictureNote": "Removed when you save. The bot's avatar, the persona emoji or the first letter shows instead.",
|
|
155
159
|
"saved": "Saved",
|
|
156
160
|
"saveFailed": "Save failed: {error}",
|
|
157
161
|
"signInToEdit": "Sign in to edit",
|
|
@@ -328,6 +332,10 @@ register("view", {
|
|
|
328
332
|
"paneFailed": "⚠ 無法載入 {name} 的 pane(HTTP {status})。",
|
|
329
333
|
"signInToSave": "請先登入才能儲存:你的 session 可能已結束。",
|
|
330
334
|
"avatarFailed": "上傳頭像失敗:{error}",
|
|
335
|
+
"avatarRemoveFailed": "無法移除圖片:{error}",
|
|
336
|
+
"removePicture": "移除圖片",
|
|
337
|
+
"keepPicture": "保留圖片",
|
|
338
|
+
"removePictureNote": "儲存後移除,改顯示 Bot 的頭像、persona emoji 或名稱第一個字。",
|
|
331
339
|
"saved": "已儲存",
|
|
332
340
|
"saveFailed": "儲存失敗:{error}",
|
|
333
341
|
"signInToEdit": "登入後才能編輯",
|
|
@@ -143,22 +143,42 @@ export interface ClaudeUsageScope {
|
|
|
143
143
|
* always; beside profiles (`besideProfiles`), its statusline fallback leaves the profiles' instances out.
|
|
144
144
|
*/
|
|
145
145
|
export declare function fetchClaudeUsage(storeHome?: string, scope?: ClaudeUsageScope): Promise<Omit<ProviderUsage, "id" | "name">>;
|
|
146
|
+
/**
|
|
147
|
+
* One quota bucket of `retrieveUserQuotaSummary` (agy's proto: QuotaSummaryBucket). `group` is not on the wire: it is
|
|
148
|
+
* the display name of the QuotaSummaryGroup the bucket came in, copied on by agyQuotaBuckets.
|
|
149
|
+
*/
|
|
146
150
|
interface AgyBucket {
|
|
147
151
|
bucketId?: string;
|
|
148
152
|
displayName?: string;
|
|
153
|
+
window?: string;
|
|
154
|
+
disabled?: boolean;
|
|
149
155
|
remainingFraction?: number;
|
|
150
156
|
resetTime?: string;
|
|
157
|
+
group?: string;
|
|
151
158
|
}
|
|
159
|
+
interface AgyQuotaSummary {
|
|
160
|
+
groups?: {
|
|
161
|
+
displayName?: string;
|
|
162
|
+
buckets?: AgyBucket[];
|
|
163
|
+
}[];
|
|
164
|
+
buckets?: AgyBucket[];
|
|
165
|
+
}
|
|
166
|
+
/** The summary's buckets, each tagged with its group's name. The top-level list (deprecated in the proto) only counts
|
|
167
|
+
* when no group carries any bucket. */
|
|
168
|
+
export declare function agyQuotaBuckets(body: AgyQuotaSummary): AgyBucket[];
|
|
152
169
|
/**
|
|
153
|
-
* Pool the summary's buckets into
|
|
170
|
+
* Pool the summary's buckets into one metric per pool and limit.
|
|
171
|
+
*
|
|
172
|
+
* Three payload shapes exist:
|
|
173
|
+
* - pooled ids (`gemini-5h`, `gemini-weekly`, `3p-5h`, `3p-weekly`), which map 1:1;
|
|
174
|
+
* - PER-MODEL buckets (`gemini-3.6-flash-high`, `claude-sonnet-4-6`, …), aggregated into a Gemini pool and an
|
|
175
|
+
* everything-else pool;
|
|
176
|
+
* - (#1898, agy 2026-10) named groups ("Gemini Models", "Claude and GPT Models") whose buckets are the group's
|
|
177
|
+
* five-hour and weekly limits. The group decides the pool; the bucket's window, id or name decides the limit.
|
|
154
178
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* `claude-sonnet-4-6`, `gpt-oss-120b-medium`, …) — those aggregate into a
|
|
159
|
-
* Gemini pool and an everything-else pool, keeping each pool's WORST remaining
|
|
160
|
-
* fraction, because eleven per-model meters is noise and the binding constraint
|
|
161
|
-
* is the one that runs out first.
|
|
179
|
+
* Every metric keeps the WORST remaining fraction of its buckets, because the binding constraint is the one that runs
|
|
180
|
+
* out first. Within one pool a bucket with a known limit is authoritative over per-model buckets; one pool never hides
|
|
181
|
+
* another. Disabled buckets (agy dims them, no number) are left out.
|
|
162
182
|
*/
|
|
163
183
|
export declare function agyPoolMetrics(buckets: AgyBucket[]): UsageMetric[];
|
|
164
184
|
interface AgyStoredToken {
|