@songsid/agend 2.2.0-beta.12 → 2.2.0-beta.13
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 +12 -6
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.js +21 -2
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +0 -1
- package/dist/fleet-manager.js +20 -36
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +48 -22
- package/dist/locale.js.map +1 -1
- package/dist/restart-progress.d.ts +0 -2
- package/dist/restart-progress.js +0 -2
- package/dist/restart-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +4 -0
- package/dist/topic-commands.js +9 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.js +6 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/ui/chat-strings.js +10 -10
- package/dist/ui/chat-tour.js +17 -14
- package/dist/ui/panel-chat.js +9 -9
- package/dist/ui/panel-fleet.js +15 -2
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +10 -7
- package/dist/ui/panel-settings.js +9 -5
- package/dist/ui/settings-dialogs.js +2 -2
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-strings.js +8 -8
- package/dist/ui/shared/app.css +3 -2
- package/dist/ui/shared/header-tools.js +5 -4
- package/dist/ui/shared/signin.js +15 -9
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/signin.html +8 -3
- package/dist/update-launch.js +12 -6
- package/dist/update-launch.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/chat-strings.js
CHANGED
|
@@ -12,14 +12,14 @@ register("chat", {
|
|
|
12
12
|
cmd_wake: "Wake a paused instance", cmd_save: "Save the conversation to a file",
|
|
13
13
|
tourTitle: "Welcome to web chat", tourStep: "{0} of {1}", tourBack: "Back", tourNext: "Next", tourDone: "Got it", tourSkip: "Skip",
|
|
14
14
|
tour1: "Pick an instance in the list to chat with it. On a phone, ☰ opens the list.",
|
|
15
|
-
tour2: "
|
|
15
|
+
tour2: "This is the agent's conversation. If it also has a Telegram or Discord chat, web echo (Settings → Web) decides whether what you send here shows there too.",
|
|
16
16
|
tour3: "Attach files and images with 📎, or paste or drop them on the chat.",
|
|
17
17
|
tour4: "While the agent works, Stop reply appears in the message box: it interrupts the current reply. The instance itself keeps running.",
|
|
18
18
|
tour5: "A “needs you” badge means the agent is waiting on its terminal, for example a permission question. Answer it from the buttons in its chat.",
|
|
19
19
|
a11yWorking: "{0} started working", a11yIdle: "{0} finished", a11yReplied: "{0} replied", conversationWith: "Conversation with {0}",
|
|
20
20
|
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
21
|
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
|
|
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 (images, PDF, text — or paste / drop them)",
|
|
23
23
|
pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
|
|
24
24
|
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
|
|
25
25
|
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.",
|
|
@@ -71,12 +71,12 @@ register("chat", {
|
|
|
71
71
|
promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptFor: "{0}: {1}",
|
|
72
72
|
working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
|
|
73
73
|
file_empty: "empty file", file_too_large: "over 10 MB", file_too_many: "at most 5 files per message", file_too_much: "the files together would be over 25 MB", removeFile: "Remove", notSent: "Not sent:", putBack: "Put back", discard: "Discard",
|
|
74
|
-
noMessages: "
|
|
75
|
-
pick: "
|
|
74
|
+
noMessages: "Type a task below.",
|
|
75
|
+
pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
|
|
76
76
|
details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
77
77
|
started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
|
|
78
78
|
deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it. Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
|
|
79
|
-
loadFailed: "Failed to load", tour: "
|
|
79
|
+
loadFailed: "Failed to load", tour: "Chat tour", tourBtnTitle: "Show the chat tour on an agent's chat", tourNoAgent: "Choose an agent before opening the chat tour.",
|
|
80
80
|
dName: "Name", dStatus: "Status", dDisplay: "Display name", dDescription: "Description", dDirectory: "Directory", dBackend: "Backend", dModel: "Model", dEffort: "Effort",
|
|
81
81
|
dCost: "Cost", dCostNote: "(session accumulated)", dContext: "Context", dRate5: "Rate 5h", dRate7: "Rate 7d", dActivity: "Recent activity", dInstance: "Instance", dRuntime: "Runtime",
|
|
82
82
|
// #1523 N2: Details (the Fleet side of one instance).
|
|
@@ -97,14 +97,14 @@ register("chat", {
|
|
|
97
97
|
cmd_wake: "喚醒已暫停的 instance", cmd_save: "把對話存成檔案",
|
|
98
98
|
tourTitle: "歡迎使用網頁聊天", tourStep: "{0} / {1}", tourBack: "上一步", tourNext: "下一步", tourDone: "知道了", tourSkip: "略過",
|
|
99
99
|
tour1: "在清單裡選一個 instance,就能跟它對話。手機上請按 ☰ 打開清單。",
|
|
100
|
-
tour2: "
|
|
100
|
+
tour2: "這是這個 Agent 的對話。如果它也有 Telegram 或 Discord 聊天,在這裡送出的訊息是否也出現在那裡,由「設定 → 網頁」的網頁轉發決定。",
|
|
101
101
|
tour3: "按 📎 附上檔案和圖片,也可以直接貼上或拖到聊天裡。",
|
|
102
102
|
tour4: "Agent 工作時,輸入框會出現「中止回覆」:中斷目前的回覆,instance 本身會繼續運行。",
|
|
103
103
|
tour5: "看到「等你回應」標記,表示 Agent 在終端機上等你,例如權限詢問。到它的聊天裡按按鈕回應即可。",
|
|
104
104
|
a11yWorking: "{0} 開始工作", a11yIdle: "{0} 已完成", a11yReplied: "{0} 已回覆", conversationWith: "與 {0} 的對話",
|
|
105
105
|
stopping: "正在中止 {0}…", awaiting: "{0} 正在等你回應", approxNote: "從終端畫面判讀,僅供參考", elapsedTitle: "從這個頁面看到它開始工作起算(約略)",
|
|
106
106
|
dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
|
|
107
|
-
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行
|
|
107
|
+
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerMore: "/ 叫出指令 · Esc 中止回覆", attachTitle: "附加檔案(圖片、PDF、文字檔,也可以貼上或拖曳)",
|
|
108
108
|
pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
|
|
109
109
|
pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
|
|
110
110
|
pvConfirm: "要允許預覽 HTML 嗎?\n\n這會套用到你登入的每一台裝置。預覽會在隔離的框架裡執行 agent 的 HTML。它無法使用你的登入,但可能把資料傳出去,也可能讓這個分頁變慢或卡住。每次仍需要你按「預覽」。",
|
|
@@ -155,12 +155,12 @@ register("chat", {
|
|
|
155
155
|
promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptFor: "{0}:{1}",
|
|
156
156
|
working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
|
|
157
157
|
file_empty: "空檔案", file_too_large: "超過 10 MB", file_too_many: "每則訊息最多 5 個檔案", file_too_much: "檔案合計會超過 25 MB", removeFile: "移除", notSent: "未送出:", putBack: "放回輸入框", discard: "捨棄",
|
|
158
|
-
noMessages: "
|
|
159
|
-
pick: "選一個
|
|
158
|
+
noMessages: "直接在下方輸入任務。",
|
|
159
|
+
pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
|
|
160
160
|
details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
|
161
161
|
started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
|
|
162
162
|
deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet 移除並停止它,工作目錄會保留。請輸入「delete {0}」確認。", deleteGo: "刪除", cancel: "取消",
|
|
163
|
-
loadFailed: "載入失敗", tour: "
|
|
163
|
+
loadFailed: "載入失敗", tour: "聊天導覽", tourBtnTitle: "在 Agent 的聊天上開啟聊天導覽", tourNoAgent: "請先選一個 Agent,再開啟聊天導覽。",
|
|
164
164
|
dName: "名稱", dStatus: "狀態", dDisplay: "顯示名稱", dDescription: "說明", dDirectory: "目錄", dBackend: "Backend", dModel: "模型", dEffort: "Effort",
|
|
165
165
|
dCost: "花費", dCostNote: "(本次 session 累計)", dContext: "Context", dRate5: "5 小時額度", dRate7: "7 天額度", dActivity: "最近活動", dInstance: "Instance", dRuntime: "執行環境",
|
|
166
166
|
dConfig: "設定", dBinding: "綁定", dBindingGeneral: "{0} 的 General topic", dBindingTopic: "{0},topic {1}", dBindingNoTopic: "{0},尚未綁定 topic",
|
package/dist/ui/chat-tour.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// #1366: the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// #1366: the chat tour, in the app shell (#1408). Since #1854 (rank 6) it never starts by itself: a first visit opens
|
|
2
|
+
// on a quiet page, and Settings → This device → Chat tour starts it on an agent's chat. It walks only the steps whose
|
|
3
|
+
// target is on the page. Got it, Skip or Esc ends it and records it (agend_tour_done, kept as before: no new key). A
|
|
4
|
+
// card that outlines one thing at a time: no overlay, nothing blocked, and no style attribute — the outline is a class
|
|
5
|
+
// (#1300).
|
|
5
6
|
//
|
|
6
7
|
// Esc order: the phone drawer covers the card, so it closes first (the shell); then the tour; only then may Esc reach
|
|
7
8
|
// the chat and stop a reply. The Esc that closed the tour is spent: its key repeats (a held key) must not go on to stop
|
|
@@ -24,6 +25,8 @@ const STEPS = [
|
|
|
24
25
|
: document.querySelector(".badge-await") || byId("instanceList")) },
|
|
25
26
|
];
|
|
26
27
|
let at = -1, spot = null, returnTo = null, escHeld = false, installed = false;
|
|
28
|
+
/** The steps of the running tour: those whose target was on the page when it started (#1854: target-aware). */
|
|
29
|
+
let steps = STEPS;
|
|
27
30
|
|
|
28
31
|
export function tourDone() { try { return localStorage.getItem(TOUR_KEY) === "1"; } catch { return true; } }
|
|
29
32
|
export function tourStep() { return at; }
|
|
@@ -32,13 +35,15 @@ export function startTour() {
|
|
|
32
35
|
// Replayed from the drawer: the drawer closes first (focus goes back to ☰), so the card is seen.
|
|
33
36
|
closeDrawer();
|
|
34
37
|
if (at < 0) returnTo = document.activeElement;
|
|
38
|
+
steps = STEPS.filter((s) => s.spot());
|
|
39
|
+
if (!steps.length) return;
|
|
35
40
|
show(0);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/** The current step's outline follows a redraw (a new badge, Send swapped for Stop reply) — same card, same focus. */
|
|
39
44
|
export function refreshTourSpot() {
|
|
40
45
|
if (at < 0) return;
|
|
41
|
-
const next =
|
|
46
|
+
const next = steps[at].spot() || null;
|
|
42
47
|
if (next === spot && (!next || next.classList.contains("tour-spot"))) return;
|
|
43
48
|
if (spot) spot.classList.remove("tour-spot");
|
|
44
49
|
spot = next;
|
|
@@ -46,10 +51,10 @@ export function refreshTourSpot() {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
function show(i) {
|
|
49
|
-
if (i < 0 || i >=
|
|
54
|
+
if (i < 0 || i >= steps.length) return;
|
|
50
55
|
at = i;
|
|
51
56
|
if (spot) spot.classList.remove("tour-spot");
|
|
52
|
-
spot =
|
|
57
|
+
spot = steps[i].spot() || null;
|
|
53
58
|
if (spot) spot.classList.add("tour-spot");
|
|
54
59
|
let card = byId("tour");
|
|
55
60
|
if (!card) {
|
|
@@ -60,18 +65,18 @@ function show(i) {
|
|
|
60
65
|
}
|
|
61
66
|
card.textContent = "";
|
|
62
67
|
const title = document.createElement("h2"); title.id = "tourTitle"; title.textContent = t("chat.tourTitle");
|
|
63
|
-
const text = document.createElement("p"); text.className = "tour-text"; text.setAttribute("aria-live", "polite"); text.textContent = t(
|
|
68
|
+
const text = document.createElement("p"); text.className = "tour-text"; text.setAttribute("aria-live", "polite"); text.textContent = t(steps[i].text);
|
|
64
69
|
const foot = document.createElement("div"); foot.className = "tour-foot";
|
|
65
|
-
const count = document.createElement("span"); count.className = "tour-count"; count.textContent = t("chat.tourStep", i + 1,
|
|
70
|
+
const count = document.createElement("span"); count.className = "tour-count"; count.textContent = t("chat.tourStep", i + 1, steps.length);
|
|
66
71
|
const button = (label, act, arg, cls) => {
|
|
67
72
|
const b = document.createElement("button"); b.type = "button"; b.className = `btn btn-sm ${cls}`; b.dataset.tour = act;
|
|
68
73
|
if (arg != null) b.dataset.arg = String(arg);
|
|
69
74
|
b.textContent = t(label); return b;
|
|
70
75
|
};
|
|
71
76
|
foot.append(count);
|
|
72
|
-
if (i <
|
|
77
|
+
if (i < steps.length - 1) foot.append(button("chat.tourSkip", "end", null, "btn-ghost"));
|
|
73
78
|
if (i > 0) foot.append(button("chat.tourBack", "move", -1, "btn-ghost"));
|
|
74
|
-
const next = i <
|
|
79
|
+
const next = i < steps.length - 1 ? button("chat.tourNext", "move", 1, "btn-primary") : button("chat.tourDone", "end", null, "btn-primary");
|
|
75
80
|
foot.append(next);
|
|
76
81
|
card.append(title, text, foot);
|
|
77
82
|
next.focus();
|
|
@@ -105,12 +110,10 @@ export function onTourKey(e) {
|
|
|
105
110
|
if (at >= 0) { e.preventDefault(); e.stopPropagation(); endTour(); escHeld = true; }
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
/** Once per page: the key handler
|
|
113
|
+
/** Once per page: the key handler and the language follow-up. Nothing is shown: the tour is started by a person (#1854). */
|
|
109
114
|
export function installTour() {
|
|
110
115
|
if (installed) return;
|
|
111
116
|
installed = true;
|
|
112
117
|
document.addEventListener("keydown", onTourKey, true);
|
|
113
118
|
onLang(() => { if (at >= 0) show(at); });
|
|
114
|
-
// Shown first on the chat (it points at the chat's own controls), never over View or another panel.
|
|
115
|
-
if (!tourDone() && /^\/ui(\/chat\/|$)/.test(location.pathname)) setTimeout(startTour, 0);
|
|
116
119
|
}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -33,7 +33,6 @@ import { createMentionIndex } from "./chat-mentions.js";
|
|
|
33
33
|
import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVersion, shownKey } from "./preview-panel.js";
|
|
34
34
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
35
35
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
36
|
-
import { FirstRunCard } from "./first-run.js";
|
|
37
36
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
38
37
|
|
|
39
38
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -148,14 +147,15 @@ export function ChatPanel({ route, navKey }) {
|
|
|
148
147
|
}, [name, !!inst]);
|
|
149
148
|
useEffect(() => { setTitle(name || t("app.chat")); }, [name, navKey]);
|
|
150
149
|
if (!name) {
|
|
151
|
-
// #
|
|
150
|
+
// #1854 (rank 7): one instruction — pick an agent, or with none at all, the one way to make one. Connecting a chat
|
|
151
|
+
// app (the first-run card, #1519 P7) is offered in Settings, not here: it competed with the first agent.
|
|
152
152
|
const none = app.ready && !app.instances.length;
|
|
153
153
|
return html`<div class="panel p-chat"><${PanelHeader} title=${t("app.chat")} />
|
|
154
|
-
<div class="panel-body center"
|
|
155
|
-
${none ? html`<${Empty} icon="bot" title=${t("
|
|
156
|
-
action=${html`<button type="button" class="btn btn-primary" onClick=${requestNewInstance}><${Icon} name="plus" size=${16} />${t("
|
|
157
|
-
: html`<${Empty} icon="chat" title=${t("chat.pick")}
|
|
158
|
-
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} />`}</div></div
|
|
154
|
+
<div class="panel-body center">
|
|
155
|
+
${none ? html`<${Empty} icon="bot" title=${t("chat.noAgents")}
|
|
156
|
+
action=${html`<button type="button" class="btn btn-primary" onClick=${requestNewInstance}><${Icon} name="plus" size=${16} />${t("chat.newAgent")}</button>`} />`
|
|
157
|
+
: html`<${Empty} icon="chat" title=${t("chat.pick")}
|
|
158
|
+
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} />`}</div></div>`;
|
|
159
159
|
}
|
|
160
160
|
if (!app.ready) return html`<div class="panel p-chat"><${PanelHeader} title=${name} /><div class="panel-body"><${Skeleton} lines=${5} /></div></div>`;
|
|
161
161
|
if (!inst) return html`<${NotFound} name=${name} />`;
|
|
@@ -288,7 +288,7 @@ function Thread({ name, th }) {
|
|
|
288
288
|
}, [name]);
|
|
289
289
|
return html`<div class="scroller" ref=${scroller}>
|
|
290
290
|
<div class="thread" ref=${list} role="log" aria-live="off" aria-label=${t("chat.conversationWith", name)}></div>
|
|
291
|
-
${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")}
|
|
291
|
+
${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
292
292
|
${jump.show ? html`<button type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
293
293
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}
|
|
294
294
|
</div>`;
|
|
@@ -307,7 +307,7 @@ function Dock({ name, inst, lease, exec, awaiting }) {
|
|
|
307
307
|
<button type="button" class="btn btn-primary btn-sm" onClick=${() => store.putBack(name)}>${t("chat.putBack")}</button>
|
|
308
308
|
<button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
|
|
309
309
|
<${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
|
|
310
|
-
<p class="hint">${t("chat.composerHint")}</p>
|
|
310
|
+
<p class="hint" title=${t("chat.composerMore")}>${t("chat.composerHint")}</p>
|
|
311
311
|
</div></div>`;
|
|
312
312
|
}
|
|
313
313
|
|
package/dist/ui/panel-fleet.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// here re-reads anything). Every read is under the navigation's lease: a list that arrives after the person moved on
|
|
6
6
|
// is dropped (#1408 §4).
|
|
7
7
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
8
|
-
import { t, register } from "/assets/app-i18n.js";
|
|
8
|
+
import { t, register, lang } from "/assets/app-i18n.js";
|
|
9
9
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
10
|
import { useLease } from "/assets/app-ctx.js";
|
|
11
11
|
import { PanelHeader, setTitle } from "/assets/app-shell.js";
|
|
@@ -27,6 +27,7 @@ register("fleet", {
|
|
|
27
27
|
hostMemory_elevated: "Host memory pressure: elevated — new agents start one at a time, at least 5 s apart",
|
|
28
28
|
hostMemory_critical: "Host memory pressure: critical — new agents wait for memory to recover",
|
|
29
29
|
title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", config: "Config",
|
|
30
|
+
docs: "Docs", docsNewTab: "Docs (opens in a new tab)",
|
|
30
31
|
newTask: "New task", newSchedule: "New schedule", newTeam: "New team",
|
|
31
32
|
noTasks: "No tasks yet", noTasksHint: "Create one to coordinate fleet work.", noSchedules: "No schedules yet", noSchedulesHint: "Automate recurring work with cron.",
|
|
32
33
|
noTeams: "No teams yet", noTeamsHint: "Group instances for coordinated work.", countTasks: "{0} tasks", countSchedules: "{0} schedules", countTeams: "{0} teams", countTask: "1 task", countSchedule: "1 schedule", countTeam: "1 team",
|
|
@@ -57,6 +58,7 @@ register("fleet", {
|
|
|
57
58
|
hostMemory_elevated: "主機記憶體壓力偏高:新的 agent 一次啟動一個,至少間隔 5 秒",
|
|
58
59
|
hostMemory_critical: "主機記憶體壓力嚴重:新的 agent 會等記憶體恢復再啟動",
|
|
59
60
|
title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", config: "設定",
|
|
61
|
+
docs: "文件", docsNewTab: "文件(在新分頁開啟)",
|
|
60
62
|
newTask: "新增 task", newSchedule: "新增排程", newTeam: "新增 team",
|
|
61
63
|
noTasks: "還沒有 task", noTasksHint: "建立一個來協調 fleet 的工作。", noSchedules: "還沒有排程", noSchedulesHint: "用 cron 自動執行例行工作。",
|
|
62
64
|
noTeams: "還沒有 team", noTeamsHint: "把 instance 分組一起工作。", countTasks: "{0} 個 task", countSchedules: "{0} 個排程", countTeams: "{0} 個 team", countTask: "1 個 task", countSchedule: "1 個排程", countTeam: "1 個 team",
|
|
@@ -84,6 +86,11 @@ register("fleet", {
|
|
|
84
86
|
},
|
|
85
87
|
});
|
|
86
88
|
|
|
89
|
+
// The project's docs site (website/, GitHub Pages), in the dashboard's language — its own pages, not the app's.
|
|
90
|
+
const DOCS_SITE = "https://songsid.github.io/AgEnD";
|
|
91
|
+
export function docsUrl(language = lang()) {
|
|
92
|
+
return `${DOCS_SITE}${language === "zh-TW" ? "/zh-tw" : ""}/docs/web-guide/`;
|
|
93
|
+
}
|
|
87
94
|
const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", cache: "chart", config: "sliders" };
|
|
88
95
|
const BACKENDS = ["claude-code", "codex", "opencode", "kiro-cli", "antigravity", "grok", "muse"];
|
|
89
96
|
|
|
@@ -128,13 +135,19 @@ export function FleetPanel({ route, navKey }) {
|
|
|
128
135
|
const { hostMemory } = useStore(appStore);
|
|
129
136
|
const sub = hostMemory ? html`<span class=${`status host-mem ${hostMemory}`} role="status"><span class=${`dot ${hostMemory === "critical" ? "bad" : "warn"}`} aria-hidden="true"></span>${t(`fleet.hostMemory_${hostMemory}`)}</span>` : null;
|
|
130
137
|
return html`<div class="panel p-fleet">
|
|
131
|
-
<${PanelHeader} title=${t("fleet.title")} sub=${sub}><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
138
|
+
<${PanelHeader} title=${t("fleet.title")} sub=${sub}><${DocsLink} /><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
132
139
|
<nav class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
133
140
|
aria-current=${k === tab ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${t(`fleet.${k}`)}</span></a>`)}</nav>
|
|
134
141
|
<div class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
135
142
|
</div>`;
|
|
136
143
|
}
|
|
137
144
|
|
|
145
|
+
/** #1844: the project's docs, from the overview — a link out (a new tab, no opener), not a Fleet tab; in front of the shared controls. */
|
|
146
|
+
function DocsLink() {
|
|
147
|
+
return html`<a class="btn btn-ghost btn-sm hd-docs" href=${docsUrl()} target="_blank" rel="noopener noreferrer" aria-label=${t("fleet.docsNewTab")} title=${t("fleet.docsNewTab")}>
|
|
148
|
+
<${Icon} name="book" size=${16} /><span class="hide-narrow" aria-hidden="true">${t("fleet.docs")}</span><${Icon} name="external" size=${12} cls="hd-ext" /></a>`;
|
|
149
|
+
}
|
|
150
|
+
|
|
138
151
|
function ListHead({ count, label, action }) {
|
|
139
152
|
return html`<div class="list-head"><span>${count}</span>${action ? html`<button type="button" class="btn btn-primary" onClick=${action.onClick}><${Icon} name="plus" size=${16} />${label}</button>` : null}</div>`;
|
|
140
153
|
}
|
package/dist/ui/panel-log.js
CHANGED
|
@@ -29,7 +29,7 @@ register("log", {
|
|
|
29
29
|
en: {
|
|
30
30
|
offTitle: "The Log is off",
|
|
31
31
|
offBody: "Turn it on to see each agent's work step by step: what it was asked, its replies, every tool call with its arguments and result, errors, and fleet events. It reads the agent's own transcript only while this tab is open, and masks secret values.",
|
|
32
|
-
enable: "Turn on the Log", enabling: "Waiting for a fleet admin to confirm it in General…", enabled: "The Log is on.",
|
|
32
|
+
enable: "Turn on the Log", enabling: "Waiting for a fleet admin to confirm it in chat (General topic) or on the host…", enabled: "The Log is on.",
|
|
33
33
|
enableFailed: "The Log was not turned on: {0}", offPublic: "A fleet admin can turn it on in Settings → Web.",
|
|
34
34
|
unsupported: "{0} has no transcript AgEnD can read, so this Log shows fleet events only. View shows its screen.",
|
|
35
35
|
kiroWhy: "kiro's own steps are shown only when AgEnD can prove which conversation it is in now. {0} Fleet events are shown meanwhile.",
|
|
@@ -67,7 +67,7 @@ register("log", {
|
|
|
67
67
|
"zh-TW": {
|
|
68
68
|
offTitle: "紀錄未開啟",
|
|
69
69
|
offBody: "開啟後可逐步看到每個 agent 的工作:收到的要求、回覆、每次工具呼叫的參數與結果、錯誤,以及 fleet 事件。只在這個分頁開著時讀取 agent 自己的對話紀錄,機密值會遮蔽。",
|
|
70
|
-
enable: "開啟紀錄", enabling: "等待 fleet admin
|
|
70
|
+
enable: "開啟紀錄", enabling: "等待 fleet admin 在聊天(General topic)或主機上確認…", enabled: "紀錄已開啟。",
|
|
71
71
|
enableFailed: "紀錄沒有開啟:{0}", offPublic: "fleet admin 可以在「設定 → 網頁」開啟。",
|
|
72
72
|
unsupported: "{0} 沒有 AgEnD 讀得到的對話紀錄,所以這裡只顯示 fleet 事件。「檢視」可以看它的畫面。",
|
|
73
73
|
kiroWhy: "只有在 AgEnD 能證明 kiro 目前所在的對話時,才會顯示它自己的步驟。{0}目前先顯示 fleet 事件。",
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -23,25 +23,25 @@ import { notifyStore, notifySupport, reasonText, refreshNotify, setNotify } from
|
|
|
23
23
|
|
|
24
24
|
register("needs", {
|
|
25
25
|
en: {
|
|
26
|
-
title: "Needs you", empty: "Nothing needs you right now", emptyHint: "When an agent waits for you, it shows here — and in
|
|
26
|
+
title: "Needs you", empty: "Nothing needs you right now", emptyHint: "When an agent waits for you, it shows here — and in your chat app's General.", emptyHintWeb: "When an agent waits for you, it shows here.",
|
|
27
27
|
open: "Open chat", acknowledge: "Acknowledge", acknowledging: "Acknowledging…", answering: "Answering…",
|
|
28
28
|
ageNow: "just now", ageMin: "{0} min", ageHour: "{0} h", ageDay: "{0} d",
|
|
29
29
|
notifyTitle: "Notify me on this device", notifyOn: "Notifications are on for this browser while a tab is open.",
|
|
30
30
|
notifyOff: "Get a desktop notification when something new needs you while this tab is in the background.",
|
|
31
31
|
notifyDenied: "This browser blocked notifications for AgEnD. Allow them in the site settings to turn this on.",
|
|
32
|
-
notifyMobile: "On a phone, rely on
|
|
32
|
+
notifyMobile: "On a phone, rely on your chat app's notification: a new item is a new post in General.", notifyMobileWeb: "On a phone, open this page to see what needs you.",
|
|
33
33
|
notifyInsecure: "Notifications need a secure address (localhost or the HTTPS public link), not a plain-HTTP LAN address.",
|
|
34
34
|
notifyUnsupported: "This browser cannot show notifications.", turnOn: "Turn on", turnOff: "Turn off",
|
|
35
35
|
typePrompt: "Fleet prompt", typeAwaiting: "At its terminal", typeInstance: "Instance", typeDelivery: "Delivery",
|
|
36
36
|
},
|
|
37
37
|
"zh-TW": {
|
|
38
|
-
title: "等你處理", empty: "目前沒有需要你處理的事", emptyHint: "agent
|
|
38
|
+
title: "等你處理", empty: "目前沒有需要你處理的事", emptyHint: "agent 在等你時,會出現在這裡——也會出現在聊天 app 的 General。", emptyHintWeb: "agent 在等你時,會出現在這裡。",
|
|
39
39
|
open: "開啟聊天", acknowledge: "確認", acknowledging: "確認中…", answering: "回覆中…",
|
|
40
40
|
ageNow: "剛剛", ageMin: "{0} 分鐘", ageHour: "{0} 小時", ageDay: "{0} 天",
|
|
41
41
|
notifyTitle: "在這台裝置通知我", notifyOn: "這個瀏覽器的通知已開啟(需要開著分頁)。",
|
|
42
42
|
notifyOff: "分頁在背景時,有新的事等你處理就跳出桌面通知。",
|
|
43
43
|
notifyDenied: "這個瀏覽器封鎖了 AgEnD 的通知。請在網站設定中允許後再開啟。",
|
|
44
|
-
notifyMobile: "
|
|
44
|
+
notifyMobile: "在手機上請以聊天 app 的通知為主:每個新項目都會在 General 發一則新訊息。", notifyMobileWeb: "在手機上請打開這個頁面查看等你處理的事。",
|
|
45
45
|
notifyInsecure: "通知需要安全的網址(localhost 或 HTTPS 公開連結),區網的一般 HTTP 網址不行。",
|
|
46
46
|
notifyUnsupported: "這個瀏覽器無法顯示通知。", turnOn: "開啟", turnOff: "關閉",
|
|
47
47
|
typePrompt: "Fleet 提示", typeAwaiting: "在終端機上", typeInstance: "Instance", typeDelivery: "傳送",
|
|
@@ -108,7 +108,9 @@ export function groupNeeds(items) {
|
|
|
108
108
|
|
|
109
109
|
export function NeedsPanel({ navKey }) {
|
|
110
110
|
const lease = useLease(navKey);
|
|
111
|
-
const { needs, chatOwner: owner, instances } = useStore(appStore);
|
|
111
|
+
const { needs, chatOwner: owner, instances, connections } = useStore(appStore);
|
|
112
|
+
// #1854: a fleet with no chat connection (web only, or not known yet) has no General to point at.
|
|
113
|
+
const chatApp = connections > 0;
|
|
112
114
|
const names = useStore(namesStore);
|
|
113
115
|
// #1630/#1722: a group is headed by the instance's leading name (the "Names" preference), as the sidebar shows it.
|
|
114
116
|
const nameFor = (instance) => { const it = (instances || []).find((i) => i && i.name === instance); return it ? namesOf(it, names.primary).primary : instance; };
|
|
@@ -124,7 +126,7 @@ export function NeedsPanel({ navKey }) {
|
|
|
124
126
|
return html`<div class="panel p-needs">
|
|
125
127
|
<${PanelHeader} title=${tn("title")} sub=${items.length ? t("app.needsCount", items.length) : null} />
|
|
126
128
|
<div class="panel-body"><div class="col">
|
|
127
|
-
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn("emptyHint")} />`
|
|
129
|
+
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
|
|
128
130
|
: groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
|
|
129
131
|
<h2 class="n-group-head"><a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a></h2>
|
|
130
132
|
${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
|
|
@@ -196,7 +198,8 @@ function NotifyToggle() {
|
|
|
196
198
|
useEffect(() => { refreshNotify(); }, []);
|
|
197
199
|
const on = state === "on";
|
|
198
200
|
const toggle = () => { setNotify(!on); };
|
|
199
|
-
const
|
|
201
|
+
const { connections } = useStore(appStore);
|
|
202
|
+
const note = support === "mobile" ? tn(connections > 0 ? "notifyMobile" : "notifyMobileWeb") : support === "insecure" ? tn("notifyInsecure")
|
|
200
203
|
: support !== "ok" ? tn("notifyUnsupported") : state === "denied" ? tn("notifyDenied") : on ? tn("notifyOn") : tn("notifyOff");
|
|
201
204
|
return html`<section class="card n-notify"><h3>${tn("notifyTitle")}</h3>
|
|
202
205
|
<p class="note">${note}</p>
|
|
@@ -24,7 +24,7 @@ import "./settings-strings.js";
|
|
|
24
24
|
import { api, attach, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
25
25
|
import { dismissOperation, operationActive, restartFleet, startOperation, takeLeftover } from "./settings-apply.js";
|
|
26
26
|
import {
|
|
27
|
-
ACCESS_MODES, AGENT_MODES, DEFAULT_SCHEMA, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
|
|
27
|
+
ACCESS_MODES, AGENT_MODES, DEFAULT_SCHEMA, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
|
|
28
28
|
changedFields, channelId, channelsOf, chLabel, connectionState, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
|
|
29
29
|
hasOwn, impactOf, localeOptions, defaultsImpact, requestImpact, nonNegative, positive, replyGuardSupported, shortName, STATUS_ORDER, toYaml,
|
|
30
30
|
visibilityDefault, worstImpact,
|
|
@@ -270,7 +270,11 @@ export function DeviceSection() {
|
|
|
270
270
|
const replay = () => {
|
|
271
271
|
let last = null;
|
|
272
272
|
try { last = localStorage.getItem("agend_last_instance"); } catch { /* none */ }
|
|
273
|
-
|
|
273
|
+
// #1854: the tour is about an agent's chat — with no agent to open (none remembered, or it is gone), say so instead
|
|
274
|
+
// of opening an empty chat that waits ~3 s and shows nothing.
|
|
275
|
+
const { instances } = appStore.get();
|
|
276
|
+
if (!last || !(instances || []).some((i) => i && i.name === last)) { toast(t("chat.tourNoAgent"), false); return; }
|
|
277
|
+
navigate(chatPath(last));
|
|
274
278
|
// This replay belongs to the navigation it just made — that one, by its sequence number, not by URL (A → B → A, or
|
|
275
279
|
// the same path again, is a new navigation). It did not happen (a leave guard said no) or it is not the chat:
|
|
276
280
|
// nothing. Left meanwhile (another page, another navigation): nothing (#1609 review).
|
|
@@ -285,7 +289,7 @@ export function DeviceSection() {
|
|
|
285
289
|
let tries = 0;
|
|
286
290
|
const go = () => {
|
|
287
291
|
if (!current()) return;
|
|
288
|
-
if (!document.getElementById("sendBtn")) { if (++tries <= 30) setTimeout(go, 100); return; }
|
|
292
|
+
if (!document.getElementById("sendBtn")) { if (++tries <= 30) setTimeout(go, 100); else toast(t("chat.tourNoAgent"), false); return; }
|
|
289
293
|
const here = document.getElementById("msgIn") || document.getElementById("main");
|
|
290
294
|
if (here && typeof here.focus === "function") here.focus();
|
|
291
295
|
if (current()) startTour();
|
|
@@ -635,7 +639,7 @@ function General({ ctx }) {
|
|
|
635
639
|
backend: d.backend || "claude-code", model: d.model || "", locale: d.locale || "", auto: String(d.auto_pause_after ?? 0),
|
|
636
640
|
agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
|
|
637
641
|
visibility: visibilityDefault(d), reply: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
|
|
638
|
-
hangOn: hang.enabled !== false, hang: String(hang.timeout_minutes ??
|
|
642
|
+
hangOn: hang.enabled !== false, hang: String(hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES),
|
|
639
643
|
concurrency: String(startup.concurrency ?? 10), stagger: String(startup.stagger_delay_ms ?? 500),
|
|
640
644
|
classicBackend: c.backend || d.backend || "claude-code",
|
|
641
645
|
classicToolProgress: c.tool_progress ?? d.tool_progress ?? "off", classicToolProgressInherit: !hasOwn(c, "tool_progress"),
|
|
@@ -664,7 +668,7 @@ function General({ ctx }) {
|
|
|
664
668
|
backend: d.backend || "claude-code", model: d.model || undefined, locale: d.locale || null, auto_pause_after: d.auto_pause_after ?? 0,
|
|
665
669
|
agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
|
|
666
670
|
cross_instance_visibility: visibilityDefault(d), reply_completion_guard: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
|
|
667
|
-
hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ??
|
|
671
|
+
hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES },
|
|
668
672
|
startup: { concurrency: startup.concurrency ?? 10, stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
|
|
669
673
|
});
|
|
670
674
|
const oc = original.c;
|
|
@@ -14,7 +14,7 @@ import { botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-
|
|
|
14
14
|
import { DiscordServerPicker } from "./settings-discord.js";
|
|
15
15
|
import { startOperation } from "./settings-apply.js";
|
|
16
16
|
import {
|
|
17
|
-
ACCESS_MODES, AGENT_FIELDS, AGENT_MODES, BOT_FIELDS, CH_TYPES, CLASSIC_FIELDS, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
17
|
+
ACCESS_MODES, AGENT_FIELDS, AGENT_MODES, BOT_FIELDS, CH_TYPES, CLASSIC_FIELDS, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
18
18
|
VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
|
|
19
19
|
chLabel, hasOwn, impactOf, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
20
20
|
webEchoIsGroup, worstImpact,
|
|
@@ -242,7 +242,7 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
|
|
|
242
242
|
<div class="field">${L(`${tn("hangTimeout")} (${tn("minutes")})`, "instance.hang_detector", "ag-hang")}
|
|
243
243
|
<${Override} inherit=${f.inherit.hang} onInherit=${setInherit("hang")}>
|
|
244
244
|
<input id="ag-hang" type="number" min="0.1" step="0.5" class=${errors.hang ? "invalid" : ""} disabled=${f.inherit.hang}
|
|
245
|
-
value=${shown("hang", f.hang, String((defaults.hang_detector && defaults.hang_detector.timeout_minutes) ??
|
|
245
|
+
value=${shown("hang", f.hang, String((defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? HANG_TIMEOUT_DEFAULT_MINUTES))} onInput=${(e) => set("hang")(e.target.value)} /></${Override}>
|
|
246
246
|
<${Feedback} error=${errors.hang} /></div>
|
|
247
247
|
${[["agent_mode", "agentMode", AGENT_MODES, defaults.agent_mode || "mcp"], ["tool_set", "toolSet", TOOL_SETS, defaults.tool_set || "full"],
|
|
248
248
|
["tool_progress", "toolProgress", TOOL_PROGRESS, defaults.tool_progress ?? "off"],
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Ported from settings.html with their reasons: every rule here was a fix (the review or issue named beside it).
|
|
3
3
|
// No DOM, no fetch; the panel (panel-settings.js) and the tests call these directly.
|
|
4
4
|
|
|
5
|
+
/** #1854: the stuck timeout when none is set — the daemon's DEFAULT_STUCK_TIMEOUT_MS (src/daemon.ts), in minutes. */
|
|
6
|
+
export const HANG_TIMEOUT_DEFAULT_MINUTES = 10;
|
|
5
7
|
export const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "antigravity", "grok", "muse"];
|
|
6
8
|
export const CH_TYPES = ["discord", "telegram"];
|
|
7
9
|
export const LOCALES = [["en", "English"], ["zh-TW", "繁體中文 (zh-TW)"]];
|
|
@@ -166,7 +168,7 @@ export function agentForm(inst, defaults) {
|
|
|
166
168
|
description: inst.description || "", systemPrompt: inst.systemPrompt || "", tags: [...(inst.tags || [])],
|
|
167
169
|
general_topic: !!inst.general_topic,
|
|
168
170
|
auto_pause_after: String(inst.auto_pause_after ?? defaults.auto_pause_after ?? 0),
|
|
169
|
-
hang: String(hang.timeout_minutes ?? (defaults.hang_detector && defaults.hang_detector.timeout_minutes) ??
|
|
171
|
+
hang: String(hang.timeout_minutes ?? (defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? HANG_TIMEOUT_DEFAULT_MINUTES),
|
|
170
172
|
agent_mode: inst.agent_mode || defaults.agent_mode || "mcp",
|
|
171
173
|
tool_set: inst.tool_set || defaults.tool_set || "full",
|
|
172
174
|
tool_progress: inst.tool_progress ?? defaults.tool_progress ?? "off",
|
|
@@ -10,7 +10,7 @@ register("settings", {
|
|
|
10
10
|
classicChannels: "ClassicBot Channels",
|
|
11
11
|
noClassic: "No ClassicBot channels — start one with /start in a chat.",
|
|
12
12
|
publicLink: "Offer temporary public web links",
|
|
13
|
-
publicLinkHint: "Explicit admin consent on every request. Cloudflare handles TLS; link users have full web-admin access.
|
|
13
|
+
publicLinkHint: "Explicit admin consent on every request. Cloudflare handles TLS; link users have full web-admin access. HTML previews get a second temporary tunnel unless web.preview_on_public_link is false.",
|
|
14
14
|
publicTtl: "Public link minutes (1–480)",
|
|
15
15
|
publicProtocol: "Tunnel protocol",
|
|
16
16
|
webEcho: "Web echo",
|
|
@@ -176,7 +176,7 @@ register("settings", {
|
|
|
176
176
|
deleteBot: "Delete this bot",
|
|
177
177
|
noAllowedUsers: "no allowed users",
|
|
178
178
|
searchAll: "Search agents, bots, channels…",
|
|
179
|
-
accessMovedHint: "Who may use the bot is set on the connection itself — open Settings on \"{0}\" under Connections
|
|
179
|
+
accessMovedHint: "Who may use the bot is set on the connection itself — open Settings on \"{0}\" under Connections.",
|
|
180
180
|
applyRestartNeeded: "Saved — restart AgEnD to apply",
|
|
181
181
|
applyNoChange: "no change needed",
|
|
182
182
|
applyRestartHint: "This change needs AgEnD itself restarted. You can restart it here, or run /restart in your chat channel.",
|
|
@@ -286,7 +286,7 @@ register("settings", {
|
|
|
286
286
|
accessTitle: "Access",
|
|
287
287
|
webChat: "Web chat",
|
|
288
288
|
sec_web: "Web",
|
|
289
|
-
webLead: "How the web dashboard behaves. A change that loosens access (longer sessions, the browser terminal back on or for longer, the public link) waits for a fleet admin to confirm it in General; one that tightens it applies at once.",
|
|
289
|
+
webLead: "How the web dashboard behaves. A change that loosens access (longer sessions, the browser terminal back on or for longer, the public link) waits for a fleet admin to confirm it in chat (General topic) or on the host; one that tightens it applies at once.",
|
|
290
290
|
webPublicRefused: "Web settings can't be changed over a temporary public link. Open the dashboard at its own address to change them.",
|
|
291
291
|
webSessionTitle: "Sign-in sessions",
|
|
292
292
|
webSessionHint: "For sign-ins at the dashboard's own address (this computer, the LAN, a tailnet). A temporary public link always keeps 4 h / 30 min. Shortening ends the sessions it now outlives; lengthening applies to new sign-ins and to an existing one's idle time on its next use. Empty = the default.",
|
|
@@ -370,7 +370,7 @@ register("settings", {
|
|
|
370
370
|
help1: "Agents: each agent's settings, Start/Stop, Pause/Wake, and New agent.",
|
|
371
371
|
help2: "Connections: who may use each bot, its binding, its status emojis, a new token; and provider API keys.",
|
|
372
372
|
help3: "ClassicBot: each room's backend, model and options.",
|
|
373
|
-
help4: "General: defaults for every agent
|
|
373
|
+
help4: "General: defaults for every agent and ClassicBot defaults. Web: the dashboard's sign-in sessions, browser terminal, web echo, Log and public link. Review changes stages them.",
|
|
374
374
|
help5: "Changes are staged, then applied together with Apply changes. Each shows what it costs: immediately, an agent restart, or an AgEnD restart.",
|
|
375
375
|
help6: "A change to access, secrets or connections waits for a fleet admin to confirm it in chat before it applies.",
|
|
376
376
|
},
|
|
@@ -406,7 +406,7 @@ register("settings", {
|
|
|
406
406
|
classicChannels: "ClassicBot 頻道",
|
|
407
407
|
noClassic: "沒有 ClassicBot 頻道 — 在聊天中用 /start 建立。",
|
|
408
408
|
publicLink: "提供臨時公開網頁連結",
|
|
409
|
-
publicLinkHint: "每次都需管理員明確啟用。Cloudflare 終止 TLS
|
|
409
|
+
publicLinkHint: "每次都需管理員明確啟用。Cloudflare 終止 TLS;登入者具有完整網頁管理權限。HTML 預覽會另開一條臨時通道,除非設定 web.preview_on_public_link: false。",
|
|
410
410
|
publicTtl: "公開連結分鐘數(1–480)",
|
|
411
411
|
publicProtocol: "Tunnel 協定",
|
|
412
412
|
webEcho: "Web 同步",
|
|
@@ -547,7 +547,7 @@ register("settings", {
|
|
|
547
547
|
deleteBot: "刪除這個機器人",
|
|
548
548
|
noAllowedUsers: "沒有允許的使用者",
|
|
549
549
|
searchAll: "搜尋 agent、機器人、頻道…",
|
|
550
|
-
accessMovedHint: "誰可以使用 bot 是在連線本身設定 ——
|
|
550
|
+
accessMovedHint: "誰可以使用 bot 是在連線本身設定 —— 請到「連線」開啟「{0}」的設定。",
|
|
551
551
|
applyRestartNeeded: "已儲存 —— 需要重新啟動 AgEnD 才會生效",
|
|
552
552
|
applyNoChange: "無需變更",
|
|
553
553
|
applyRestartHint: "此變更需要重新啟動 AgEnD 本身。可以在這裡重新啟動,或在聊天頻道執行 /restart。",
|
|
@@ -657,7 +657,7 @@ register("settings", {
|
|
|
657
657
|
accessTitle: "存取",
|
|
658
658
|
webChat: "網頁聊天",
|
|
659
659
|
sec_web: "網頁",
|
|
660
|
-
webLead: "網頁儀表板的行為。放寬存取的變更(延長 session、重新開啟或延長瀏覽器終端機、公開連結)需要 fleet admin
|
|
660
|
+
webLead: "網頁儀表板的行為。放寬存取的變更(延長 session、重新開啟或延長瀏覽器終端機、公開連結)需要 fleet admin 在聊天(General topic)或主機上確認;收緊的變更會立即套用。",
|
|
661
661
|
webPublicRefused: "無法透過臨時公開連結變更網頁設定。請從儀表板本身的網址開啟後再變更。",
|
|
662
662
|
webSessionTitle: "登入 session",
|
|
663
663
|
webSessionHint: "適用於從儀表板本身網址登入的 session(本機、區域網路、tailnet)。臨時公開連結固定為 4 小時 / 30 分鐘。縮短會讓已超過新限制的 session 結束;延長會套用到新的登入,以及既有 session 下次使用時的閒置時間。留空 = 預設值。",
|
|
@@ -741,7 +741,7 @@ register("settings", {
|
|
|
741
741
|
help1: "Agent:每個 agent 的設定、啟動/停止、暫停/喚醒,以及新增 agent。",
|
|
742
742
|
help2: "連線:誰可以使用各個 bot、它的綁定、狀態表情、新的 token;以及供應商 API key。",
|
|
743
743
|
help3: "ClassicBot:每個房間的 backend、model 與選項。",
|
|
744
|
-
help4: "一般:所有 agent
|
|
744
|
+
help4: "一般:所有 agent 的預設值與 ClassicBot 預設值。網頁:儀表板的登入 session、瀏覽器終端機、網頁轉發、紀錄與公開連結。「檢查變更」會把它們暫存。",
|
|
745
745
|
help5: "變更會先暫存,再用「套用變更」一起套用。每項都會標示代價:立即生效、重新啟動 agent,或重新啟動 AgEnD。",
|
|
746
746
|
help6: "存取、密鑰或連線的變更,要等 fleet 管理員在聊天中確認後才會套用。",
|
|
747
747
|
},
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -277,8 +277,7 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
277
277
|
.empty-title { color: var(--text); font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
|
|
278
278
|
.empty-hint { font-size: var(--fs-sm); }
|
|
279
279
|
.empty .btn { margin-top: var(--s-3); }
|
|
280
|
-
/* #1519 P7: the first-run card (a fleet with no chat connection), above
|
|
281
|
-
.first-run-stack { width: 100%; max-width: var(--col); }
|
|
280
|
+
/* #1519 P7: the first-run card (a fleet with no chat connection), above a Settings list (#1854: no longer on Chat). */
|
|
282
281
|
.first-run h3 { display: flex; align-items: center; gap: var(--s-2); }
|
|
283
282
|
.first-run p { color: var(--text-2); font-size: var(--fs-sm); }
|
|
284
283
|
.error-state { display: flex; flex-direction: column; align-items: center; gap: var(--s-3); padding: var(--s-8) var(--s-4); color: var(--danger); text-align: center; }
|
|
@@ -738,6 +737,8 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
738
737
|
.hd-text-names > span { grid-area: 1 / 1; visibility: hidden; }
|
|
739
738
|
.hd-text-names > .on { visibility: visible; }
|
|
740
739
|
.hd-slot { visibility: hidden; pointer-events: none; }
|
|
740
|
+
/* #1844 Fleet overview → Docs: the arrow says "opens a new tab". */
|
|
741
|
+
.hd-docs .hd-ext { color: var(--text-3); }
|
|
741
742
|
.v-term { flex: 1; min-height: 0; overflow: auto; scrollbar-gutter: stable; padding: 5px 6px; background: #000000; position: relative; }
|
|
742
743
|
.v-pre { margin: 0; min-width: 100%; min-height: 100%; font-family: var(--font-mono); line-height: 1.2; white-space: pre; color: #d0d0d0; }
|
|
743
744
|
.v-pre .ansi[data-b] { font-weight: var(--fw-bold); }
|
|
@@ -80,14 +80,15 @@ export function usageOffered() {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// #1673: the rail. Wide enough for it beside the sidebar and the page: RAIL_MEDIA (followed as it changes). Shown or
|
|
83
|
-
// hidden is this device's choice (agend_usage_rail; "
|
|
83
|
+
// hidden is this device's choice (agend_usage_rail; "1" = shown, "0" = hidden) — a layout choice, not synced (#1665).
|
|
84
|
+
// #1854 (rank 7): a device that never chose starts with it hidden — a quiet first page; ◔ shows it (and remembers).
|
|
84
85
|
export const RAIL_MEDIA = "(min-width: 1200px)";
|
|
85
86
|
export const RAIL_KEY = "agend_usage_rail";
|
|
86
87
|
/** The pages whose header has ◔ — the rail is theirs (Log's too: one frame on every tab of an instance). */
|
|
87
88
|
export const RAIL_PANELS = ["chat", "view", "log", "details", "fleet"];
|
|
88
|
-
export const usageRailStore = createStore({ open:
|
|
89
|
-
/** A page load: this device's choice. */
|
|
90
|
-
export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY)
|
|
89
|
+
export const usageRailStore = createStore({ open: false });
|
|
90
|
+
/** A page load: this device's choice; none made yet = hidden. */
|
|
91
|
+
export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY) === "1" }); }
|
|
91
92
|
export function setUsageRail(open) { write(RAIL_KEY, open ? "1" : "0"); usageRailStore.set({ open: !!open }); }
|
|
92
93
|
const railQuery = () => { try { return typeof matchMedia === "function" ? matchMedia(RAIL_MEDIA) : null; } catch { return null; } };
|
|
93
94
|
/** Whether the screen is wide enough for the rail, now and as it changes. */
|