@songsid/agend 2.2.0-beta.8 → 2.2.0
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/README.md +40 -24
- package/README.zh-TW.md +40 -24
- package/dist/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +7 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +15 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +33 -0
- package/dist/backend/antigravity.js +126 -18
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +3 -0
- package/dist/backend/claude-code.js +9 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +18 -1
- package/dist/backend/kiro.js +35 -8
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +17 -0
- package/dist/backend/muse.js +86 -16
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +40 -1
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +12 -0
- package/dist/channel/adapters/discord.js +49 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +18 -0
- package/dist/channel/adapters/telegram.js +24 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +16 -0
- package/dist/cli.js +124 -24
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +5 -2
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.d.ts +86 -0
- package/dist/compact-watch.js +288 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +39 -0
- package/dist/config-validator.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.d.ts +13 -1
- package/dist/daemon.js +160 -13
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -1
- package/dist/delivery-outbox.js +6 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +7 -0
- package/dist/fleet-manager.d.ts +160 -10
- package/dist/fleet-manager.js +837 -121
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +4 -3
- package/dist/forged-envelope-scan.js +4 -3
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/install-lock.d.ts +18 -1
- package/dist/install-lock.js +30 -3
- package/dist/install-lock.js.map +1 -1
- package/dist/instance-avatar.d.ts +5 -4
- package/dist/instance-avatar.js +12 -8
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-lifecycle.js +3 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.d.ts +38 -0
- package/dist/kiro-compact-pane.js +57 -0
- package/dist/kiro-compact-pane.js.map +1 -0
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- 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 +202 -30
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +10 -2
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.d.ts +26 -0
- package/dist/log-steps.js +215 -3
- package/dist/log-steps.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/outbound-handlers.d.ts +27 -0
- package/dist/outbound-handlers.js +120 -36
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +12 -0
- package/dist/outbound-schemas.js +15 -4
- package/dist/outbound-schemas.js.map +1 -1
- 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.d.ts +2 -0
- package/dist/public-web-gateway.js +11 -1
- 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/reply-buttons-controller.d.ts +86 -5
- package/dist/reply-buttons-controller.js +271 -7
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.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/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +42 -26
- package/dist/service-activation.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/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +68 -2
- package/dist/topic-commands.js +141 -27
- 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/transcript-sources.js +6 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-render.js +55 -10
- package/dist/ui/chat-store.js +69 -5
- package/dist/ui/chat-strings.js +28 -16
- package/dist/ui/chat-thread.js +132 -14
- package/dist/ui/chat-tour.js +17 -14
- package/dist/ui/panel-chat.js +16 -10
- package/dist/ui/panel-fleet.js +15 -2
- package/dist/ui/panel-log.js +55 -15
- package/dist/ui/panel-needs.js +10 -7
- package/dist/ui/panel-settings.js +58 -7
- package/dist/ui/settings-dialogs.js +11 -6
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-strings.js +24 -10
- package/dist/ui/settings-token.js +7 -0
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/app-i18n.js +1 -1
- package/dist/ui/shared/app-stream.js +4 -1
- package/dist/ui/shared/app.css +52 -10
- package/dist/ui/shared/header-tools.js +34 -14
- package/dist/ui/shared/instance-switch.js +5 -2
- package/dist/ui/shared/panel-view.js +15 -4
- package/dist/ui/shared/signin.js +15 -9
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/view-strings.js +10 -2
- package/dist/ui/signin.html +8 -3
- package/dist/unrecognised-prompt.js +2 -2
- package/dist/unrecognised-prompt.js.map +1 -1
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-launch.d.ts +48 -1
- package/dist/update-launch.js +159 -5
- package/dist/update-launch.js.map +1 -1
- 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.d.ts +44 -2
- package/dist/web-api.js +265 -57
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +26 -1
- package/dist/web-chat-history.js +47 -8
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +39 -3
- package/dist/web-commands.js +22 -5
- package/dist/web-commands.js.map +1 -1
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-prefs.d.ts +18 -0
- package/dist/web-prefs.js +36 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +10 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-upload.d.ts +119 -4
- package/dist/web-upload.js +252 -39
- package/dist/web-upload.js.map +1 -1
- package/launcher/install-admission.cjs +61 -6
- package/package.json +3 -3
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
|
@@ -17,7 +17,7 @@ import { appStore, useStore } from "/assets/app-store.js";
|
|
|
17
17
|
import { useLease } from "/assets/app-ctx.js";
|
|
18
18
|
import { PanelHeader, setTitle, statusClass, statusLabel, openDrawer } from "/assets/app-shell.js";
|
|
19
19
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
20
|
-
import { HeaderTools } from "/assets/header-tools.js";
|
|
20
|
+
import { HeaderSlot, HeaderTools } from "/assets/header-tools.js";
|
|
21
21
|
import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
|
|
22
22
|
import { Icon } from "/assets/ui-icons.js";
|
|
23
23
|
import { toast } from "/assets/ui-toast.js";
|
|
@@ -29,13 +29,22 @@ 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.",
|
|
36
|
+
kiro_not_running: "It is not running.",
|
|
37
|
+
kiro_v3: "Its engine (V3) keeps a history AgEnD does not read.",
|
|
38
|
+
kiro_engine_unknown: "Its engine is not known.",
|
|
39
|
+
kiro_legacy: "It launched without its own agent (legacy mode), so it continues the folder's newest conversation, not one of its own.",
|
|
40
|
+
kiro_fresh: "This launch began a new conversation, which kiro names only later.",
|
|
41
|
+
kiro_v2_root: "Where its engine (TUI) keeps conversations is inherited from tmux, not set by its launch, so it is not proven.",
|
|
42
|
+
kiro_no_profile: "Where its engine (classic) keeps conversations is proven only when it runs with its own credential profile.",
|
|
43
|
+
kiro_session_unobservable: "A /clear or /chat load typed in its terminal can switch conversations without AgEnD seeing it.",
|
|
36
44
|
notRunning: "Its transcript is found while it runs. Start it to see its steps; fleet events are shown meanwhile.",
|
|
37
45
|
noTranscript: "No transcript yet: steps appear once it has worked.",
|
|
38
46
|
empty: "Nothing here yet.", olderLoad: "Load older steps", olderStart: "The start of what can be read.", loading: "Loading…",
|
|
47
|
+
olderTrimmed: "Older steps were let go to keep this page light. Open the Log again to read further back.",
|
|
39
48
|
all: "All", tools: "Tools", errors: "Errors", filter: "Show", live: "Live", reset: "A new conversation began: showing it from its latest steps.",
|
|
40
49
|
user: "Asked", assistant: "Replied", error: "Error", errorCode: "Error {0}", turnEnd: "Turn ended", turnEndIn: "Turn ended · {0}",
|
|
41
50
|
compacted: "Conversation compacted", turn: "Turn",
|
|
@@ -58,13 +67,22 @@ register("log", {
|
|
|
58
67
|
"zh-TW": {
|
|
59
68
|
offTitle: "紀錄未開啟",
|
|
60
69
|
offBody: "開啟後可逐步看到每個 agent 的工作:收到的要求、回覆、每次工具呼叫的參數與結果、錯誤,以及 fleet 事件。只在這個分頁開著時讀取 agent 自己的對話紀錄,機密值會遮蔽。",
|
|
61
|
-
enable: "開啟紀錄", enabling: "等待 fleet admin
|
|
70
|
+
enable: "開啟紀錄", enabling: "等待 fleet admin 在聊天(General topic)或主機上確認…", enabled: "紀錄已開啟。",
|
|
62
71
|
enableFailed: "紀錄沒有開啟:{0}", offPublic: "fleet admin 可以在「設定 → 網頁」開啟。",
|
|
63
72
|
unsupported: "{0} 沒有 AgEnD 讀得到的對話紀錄,所以這裡只顯示 fleet 事件。「檢視」可以看它的畫面。",
|
|
64
|
-
|
|
73
|
+
kiroWhy: "只有在 AgEnD 能證明 kiro 目前所在的對話時,才會顯示它自己的步驟。{0}目前先顯示 fleet 事件。",
|
|
74
|
+
kiro_not_running: "它沒有在執行。",
|
|
75
|
+
kiro_v3: "它的引擎(V3)的紀錄 AgEnD 不讀取。",
|
|
76
|
+
kiro_engine_unknown: "不知道它用哪個引擎。",
|
|
77
|
+
kiro_legacy: "它啟動時沒有自己的 agent(舊模式),接續的是資料夾裡最新的對話,不是它自己的。",
|
|
78
|
+
kiro_fresh: "這次啟動開了新的對話,kiro 之後才會替它命名。",
|
|
79
|
+
kiro_v2_root: "它的引擎(TUI)存放對話的位置沿用 tmux 的設定,不是由啟動指定,因此無法證明。",
|
|
80
|
+
kiro_no_profile: "它的引擎(classic)存放對話的位置,只有使用自己的憑證設定檔時才能證明。",
|
|
81
|
+
kiro_session_unobservable: "在它的終端機輸入 /clear 或 /chat load 可能切換對話,而 AgEnD 看不到。",
|
|
65
82
|
notRunning: "它執行時才找得到對話紀錄。啟動它就能看到步驟;目前先顯示 fleet 事件。",
|
|
66
83
|
noTranscript: "還沒有對話紀錄:它開始工作後就會出現步驟。",
|
|
67
84
|
empty: "目前還沒有內容。", olderLoad: "載入更早的步驟", olderStart: "已經是能讀到的最早內容。", loading: "載入中…",
|
|
85
|
+
olderTrimmed: "為了讓頁面保持輕快,較早的步驟已移出。重新開啟「紀錄」就能往前讀。",
|
|
68
86
|
all: "全部", tools: "工具", errors: "錯誤", filter: "顯示", live: "即時", reset: "開始了新的對話:從它最新的步驟顯示。",
|
|
69
87
|
user: "收到要求", assistant: "回覆", error: "錯誤", errorCode: "錯誤 {0}", turnEnd: "回合結束", turnEndIn: "回合結束 · {0}",
|
|
70
88
|
compacted: "對話已壓縮", turn: "回合",
|
|
@@ -86,6 +104,8 @@ register("log", {
|
|
|
86
104
|
},
|
|
87
105
|
});
|
|
88
106
|
const tl = (key, ...a) => t(`log.${key}`, ...a);
|
|
107
|
+
/** Why a kiro Log shows fleet events only (kiro-log-gate.ts): each is worded above. */
|
|
108
|
+
export const KIRO_REASONS = new Set(["kiro_not_running", "kiro_v3", "kiro_engine_unknown", "kiro_legacy", "kiro_fresh", "kiro_v2_root", "kiro_no_profile", "kiro_session_unobservable"]);
|
|
89
109
|
|
|
90
110
|
/** The public link has no stream: the page asks this often while it is shown. */
|
|
91
111
|
export const POLL_MS = 4000;
|
|
@@ -121,6 +141,7 @@ const eventLabel = (type) => { const k = `log.ev_${type}`; const v = t(k); retur
|
|
|
121
141
|
*/
|
|
122
142
|
export function mergeSteps(list, incoming, older = false) {
|
|
123
143
|
const at = new Map(list.map((s, i) => [s.id, i]));
|
|
144
|
+
const fresh = new Map();
|
|
124
145
|
const next = list.slice();
|
|
125
146
|
const added = [];
|
|
126
147
|
for (const s of incoming) {
|
|
@@ -137,7 +158,10 @@ export function mergeSteps(list, incoming, older = false) {
|
|
|
137
158
|
const { tool, cat, subject, input, ts } = s;
|
|
138
159
|
next[i] = { ...next[i], tool, cat, ...(subject !== undefined ? { subject } : {}), ...(input !== undefined ? { input } : {}), ...(ts ? { ts } : {}) };
|
|
139
160
|
}
|
|
140
|
-
} else
|
|
161
|
+
} else if (fresh.has(s.id)) {
|
|
162
|
+
// The same step twice in one answer (never from the server's parser, whose ids are unique): the later one wins.
|
|
163
|
+
added[fresh.get(s.id)] = { ...added[fresh.get(s.id)], ...s };
|
|
164
|
+
} else { fresh.set(s.id, added.length); added.push(s); }
|
|
141
165
|
}
|
|
142
166
|
return older ? [...added, ...next] : [...next, ...added];
|
|
143
167
|
}
|
|
@@ -196,7 +220,9 @@ function LogView({ name, inst, navKey, exec, awaiting }) {
|
|
|
196
220
|
const [older, setOlder] = useState({ busy: false });
|
|
197
221
|
const [notice, setNotice] = useState("");
|
|
198
222
|
const [enabling, setEnabling] = useState(false);
|
|
199
|
-
|
|
223
|
+
// `epoch` moves whenever `before` stops being valid (a trim, a new conversation): an older page asked for before that
|
|
224
|
+
// is dropped when it comes back, so it never restores a cursor across steps that were let go.
|
|
225
|
+
const cursor = useRef({ key: null, after: null, skip: false, before: null, ev: 0, trimmed: false, epoch: 0 });
|
|
200
226
|
const reading = useRef(false), again = useRef(false);
|
|
201
227
|
const scroller = useRef(null), pinned = useRef(true);
|
|
202
228
|
const overPublicLink = globalThis.document?.body?.dataset?.publicLink === "1";
|
|
@@ -211,18 +237,26 @@ function LogView({ name, inst, navKey, exec, awaiting }) {
|
|
|
211
237
|
if (!r.ok || !j) throw new Error((j && j.error) || String(r.status));
|
|
212
238
|
return j;
|
|
213
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* At most PAGE_MAX steps, the newest kept (#1790 review; #1791 review: older pages too). The oldest are let go to keep
|
|
242
|
+
* the page light; older pages would resume at a cursor past them and leave them out, so older pages are no longer
|
|
243
|
+
* offered — the page says so (reopening reads from the newest).
|
|
244
|
+
*/
|
|
245
|
+
const capPage = (merged) => {
|
|
246
|
+
if (merged.length <= PAGE_MAX) return merged;
|
|
247
|
+
const c = cursor.current;
|
|
248
|
+
c.before = null; c.trimmed = true; c.epoch++;
|
|
249
|
+
return merged.slice(merged.length - PAGE_MAX);
|
|
250
|
+
};
|
|
214
251
|
const take = (j, { replace = false } = {}) => {
|
|
215
252
|
if (!j.enabled) { setState({ phase: "off" }); return; }
|
|
216
253
|
setInfo({ backend: j.backend, supported: j.supported, reason: j.reason || null });
|
|
217
254
|
const c = cursor.current;
|
|
218
255
|
if (j.key) c.key = j.key;
|
|
219
256
|
if (j.after !== null && j.after !== undefined) { c.after = j.after; c.skip = !!j.skip; }
|
|
220
|
-
if (replace) c.before = j.before ?? null;
|
|
257
|
+
if (replace) { c.before = j.before ?? null; c.trimmed = false; c.epoch++; }
|
|
221
258
|
if (typeof j.ev === "number") c.ev = Math.max(c.ev, j.ev);
|
|
222
|
-
setSteps((list) =>
|
|
223
|
-
const merged = replace ? j.steps || [] : mergeSteps(list, j.steps || []);
|
|
224
|
-
return merged.length > PAGE_MAX ? merged.slice(merged.length - PAGE_MAX) : merged;
|
|
225
|
-
});
|
|
259
|
+
setSteps((list) => capPage(replace ? j.steps || [] : mergeSteps(list, j.steps || [])));
|
|
226
260
|
if (j.events && j.events.length) setEvents((list) => (replace ? j.events : [...list, ...j.events.filter((e) => !list.some((x) => x.id === e.id))]));
|
|
227
261
|
else if (replace) setEvents([]);
|
|
228
262
|
if (j.reset) setNotice(tl("reset"));
|
|
@@ -261,14 +295,18 @@ function LogView({ name, inst, navKey, exec, awaiting }) {
|
|
|
261
295
|
const loadOlder = async () => {
|
|
262
296
|
const c = cursor.current;
|
|
263
297
|
if (c.before === null || older.busy) return;
|
|
298
|
+
const epoch = c.epoch;
|
|
264
299
|
setOlder({ busy: true });
|
|
265
300
|
try {
|
|
266
301
|
const j = await read(q({ before: String(c.before), ...(c.key ? { key: c.key } : {}) }));
|
|
267
302
|
if (j && j.enabled) {
|
|
303
|
+
// A trim or a new conversation meanwhile: this answer is stale — even one that says a new conversation began,
|
|
304
|
+
// which would replace what is shown now with an older snapshot and its cursor.
|
|
305
|
+
if (c.epoch !== epoch) return;
|
|
268
306
|
if (j.reset) { take(j, { replace: true }); return; }
|
|
269
307
|
c.before = j.before ?? null;
|
|
270
308
|
const el = scroller.current, from = el ? el.scrollHeight - el.scrollTop : 0;
|
|
271
|
-
setSteps((list) => mergeSteps(list, j.steps || [], true));
|
|
309
|
+
setSteps((list) => capPage(mergeSteps(list, j.steps || [], true)));
|
|
272
310
|
// Keep the reader where they were: what was at the top stays in view.
|
|
273
311
|
requestAnimationFrame(() => { if (el && lease.current()) el.scrollTop = el.scrollHeight - from; });
|
|
274
312
|
}
|
|
@@ -321,13 +359,15 @@ function LogView({ name, inst, navKey, exec, awaiting }) {
|
|
|
321
359
|
action=${overPublicLink ? html`<p class="note">${tl("offPublic")}</p>`
|
|
322
360
|
: html`<button type="button" class="btn btn-primary log-enable" disabled=${enabling} onClick=${enable}>${enabling ? tl("enabling") : tl("enable")}</button>`} /></div>`;
|
|
323
361
|
} else {
|
|
324
|
-
const why = info && info.reason ? (info.reason === "unsupported" ? tl("unsupported", info.backend)
|
|
362
|
+
const why = info && info.reason ? (info.reason === "unsupported" ? tl("unsupported", info.backend)
|
|
363
|
+
: KIRO_REASONS.has(info.reason) ? tl("kiroWhy", tl(info.reason))
|
|
325
364
|
: info.reason === "not_running" ? tl("notRunning") : info.reason === "no_transcript" ? tl("noTranscript") : "") : "";
|
|
326
365
|
content = html`
|
|
327
366
|
${why ? html`<p class="note log-why" role="status">${why}</p>` : null}
|
|
328
367
|
${notice ? html`<p class="note log-notice" role="status">${notice}</p>` : null}
|
|
329
368
|
${cursor.current.before !== null
|
|
330
369
|
? html`<div class="log-older"><button type="button" class="btn btn-sm" disabled=${older.busy} onClick=${loadOlder}>${older.busy ? tl("loading") : tl("olderLoad")}</button></div>`
|
|
370
|
+
: cursor.current.trimmed ? html`<p class="note log-start">${tl("olderTrimmed")}</p>`
|
|
331
371
|
: steps.length ? html`<p class="note log-start">${tl("olderStart")}</p>` : null}
|
|
332
372
|
${items.length ? html`<ol class="log-list" role="log" aria-label=${t("app.log")}>${items.map((x, i) => html`<${Row} key=${`${x.kind}:${x.id}`} x=${x} prev=${items[i - 1]} />`)}</ol>`
|
|
333
373
|
: html`<p class="note log-empty">${tl("empty")}</p>`}`;
|
|
@@ -336,7 +376,7 @@ function LogView({ name, inst, navKey, exec, awaiting }) {
|
|
|
336
376
|
${["all", "tools", "errors"].map((k) => html`<button key=${k} type="button" class=${`chip-btn${filter === k ? " on" : ""}`} aria-pressed=${filter === k ? "true" : "false"} onClick=${() => setFilter(k)}>${tl(k)}</button>`)}
|
|
337
377
|
<span class="grow"></span><span class="log-live" title=${tl("live")}><span class="dot running" aria-hidden="true"></span>${tl("live")}</span></div>` : null;
|
|
338
378
|
return html`<div class="panel p-log">
|
|
339
|
-
<${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="log" />`}><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
379
|
+
<${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="log" />`}><${HeaderTools} navKey=${navKey} /><${HeaderSlot} /></${PanelHeader}>
|
|
340
380
|
${tools}
|
|
341
381
|
<div class="panel-body log-body" ref=${scroller} onScroll=${onScroll}><div class="col log-col">${content}</div></div>
|
|
342
382
|
</div>`;
|
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();
|
|
@@ -293,7 +297,8 @@ export function DeviceSection() {
|
|
|
293
297
|
setTimeout(go, 0);
|
|
294
298
|
};
|
|
295
299
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
296
|
-
return html
|
|
300
|
+
return html`<${ProfileCard} />
|
|
301
|
+
<section class="card s-device s-synced"><h3>${tn("syncedTitle")}</h3>
|
|
297
302
|
<p class="sub">${publicLink ? tn("syncedPublicLink") : tn("syncedLead")}</p>
|
|
298
303
|
<${DevicePrefs} labels=${true} />
|
|
299
304
|
${P() ? html`<label class="check"><input type="checkbox" checked=${on} onChange=${(e) => { const want = e.target.checked; setPreviewOptIn(want).then(setOn); }} />
|
|
@@ -305,6 +310,52 @@ export function DeviceSection() {
|
|
|
305
310
|
</section>`;
|
|
306
311
|
}
|
|
307
312
|
|
|
313
|
+
/**
|
|
314
|
+
* #1827: who agents see when this person writes from the web. Signed in with a code sent to their Discord or Telegram
|
|
315
|
+
* account, they are that account — its user id, and its name unless they choose another (it follows them to every
|
|
316
|
+
* device, the public link included). Otherwise "web-user", as before. The host checks a name in full and says why it
|
|
317
|
+
* refuses one; this only shows what it says.
|
|
318
|
+
*/
|
|
319
|
+
const view = (b) => ({ phase: "ready", name: b.name || "", shown: b.shown || "", id: b.id || "", bound: b.bound === true, platform: b.platform_name || "", error: "" });
|
|
320
|
+
export function ProfileCard() {
|
|
321
|
+
const [state, setState] = useState({ phase: "loading", name: "", shown: "", id: "", bound: false, platform: "", error: "" });
|
|
322
|
+
const [draft, setDraft] = useState("");
|
|
323
|
+
const lease = useLease();
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
let live = true;
|
|
326
|
+
api("/ui/profile").then((r) => {
|
|
327
|
+
if (!live) return;
|
|
328
|
+
if (!r.ok || !r.body) { setState((s) => ({ ...s, phase: "unavailable" })); return; }
|
|
329
|
+
setState(view(r.body));
|
|
330
|
+
setDraft(r.body.name || "");
|
|
331
|
+
}).catch(() => { if (live) setState((s) => ({ ...s, phase: "unavailable" })); });
|
|
332
|
+
return () => { live = false; };
|
|
333
|
+
}, []);
|
|
334
|
+
if (state.phase === "unavailable") return null;
|
|
335
|
+
const save = (clear) => {
|
|
336
|
+
setState((s) => ({ ...s, phase: "saving", error: "" }));
|
|
337
|
+
api("/ui/profile", { method: "POST", body: JSON.stringify({ name: clear ? null : draft }) }).then((r) => {
|
|
338
|
+
if (lease && !lease.current()) return;
|
|
339
|
+
if (!r.ok) { setState((s) => ({ ...s, phase: "ready", error: (r.body && r.body.error) || tn("profileFailed") })); return; }
|
|
340
|
+
setState(view(r.body));
|
|
341
|
+
setDraft(r.body.name || "");
|
|
342
|
+
toast(tn("profileSaved", r.body.shown || "web-user"));
|
|
343
|
+
}).catch(() => setState((s) => ({ ...s, phase: "ready", error: tn("profileFailed") })));
|
|
344
|
+
};
|
|
345
|
+
const busy = state.phase !== "ready";
|
|
346
|
+
const changed = draft.trim() !== state.name;
|
|
347
|
+
return html`<section class="card s-device s-profile"><h3>${tn("profileTitle")}</h3>
|
|
348
|
+
<p class="sub">${state.bound ? tn("profileLeadBound") : tn("profileLeadUnbound")}</p>
|
|
349
|
+
<div class="field"><label for="profile-name">${tn("profileName")}</label>
|
|
350
|
+
<input id="profile-name" type="text" maxlength="200" autocomplete="nickname" value=${draft} disabled=${busy}
|
|
351
|
+
placeholder=${state.platform || "web-user"} aria-invalid=${state.error ? "true" : "false"} aria-describedby="profile-help"
|
|
352
|
+
onInput=${(e) => setDraft(e.target.value)} onKeyDown=${(e) => { if (e.key === "Enter" && changed && draft.trim()) save(false); }} />
|
|
353
|
+
<p class="sub" id="profile-help">${state.error ? html`<span class="err" role="alert">${state.error}</span>` : tn("profileHelp", state.shown || "web-user", state.id)}</p></div>
|
|
354
|
+
<div class="s-device-row"><button type="button" class="btn primary" disabled=${busy || !changed || !draft.trim()} onClick=${() => save(false)}>${tn("profileSave")}</button>
|
|
355
|
+
${state.name ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => save(true)}>${state.bound ? tn("profileUseAccount", state.platform || "web-user") : tn("profileClear")}</button>` : null}</div>
|
|
356
|
+
</section>`;
|
|
357
|
+
}
|
|
358
|
+
|
|
308
359
|
/** The operation in hand still owns Apply: its writes and job, or a restart of AgEnD from the press to its end. */
|
|
309
360
|
const opBusy = (op) => !!op && (["writing", "posting", "watching"].includes(op.phase) || op.restart === "busy");
|
|
310
361
|
function dropSecrets(staged) { for (const c of staged.values()) if (c.connectionSecret) c.connectionSecret.secret = ""; }
|
|
@@ -588,7 +639,7 @@ function General({ ctx }) {
|
|
|
588
639
|
backend: d.backend || "claude-code", model: d.model || "", locale: d.locale || "", auto: String(d.auto_pause_after ?? 0),
|
|
589
640
|
agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
|
|
590
641
|
visibility: visibilityDefault(d), reply: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
|
|
591
|
-
hangOn: hang.enabled !== false, hang: String(hang.timeout_minutes ??
|
|
642
|
+
hangOn: hang.enabled !== false, hang: String(hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES),
|
|
592
643
|
concurrency: String(startup.concurrency ?? 10), stagger: String(startup.stagger_delay_ms ?? 500),
|
|
593
644
|
classicBackend: c.backend || d.backend || "claude-code",
|
|
594
645
|
classicToolProgress: c.tool_progress ?? d.tool_progress ?? "off", classicToolProgressInherit: !hasOwn(c, "tool_progress"),
|
|
@@ -617,7 +668,7 @@ function General({ ctx }) {
|
|
|
617
668
|
backend: d.backend || "claude-code", model: d.model || undefined, locale: d.locale || null, auto_pause_after: d.auto_pause_after ?? 0,
|
|
618
669
|
agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
|
|
619
670
|
cross_instance_visibility: visibilityDefault(d), reply_completion_guard: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
|
|
620
|
-
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 },
|
|
621
672
|
startup: { concurrency: startup.concurrency ?? 10, stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
|
|
622
673
|
});
|
|
623
674
|
const oc = original.c;
|
|
@@ -710,7 +761,7 @@ function General({ ctx }) {
|
|
|
710
761
|
*/
|
|
711
762
|
export const WEB_READ_ONLY = [
|
|
712
763
|
"web.allowed_hosts", "web.view_access", "web.notify_login", "web.preview", "web.preview_port", "web.preview_origin", "web.preview_on_public_link",
|
|
713
|
-
"web.usage_panel", "web.provider_secrets", "web_terminal.bind", "web_terminal.tunnel",
|
|
764
|
+
"web.usage_panel", "web.provider_secrets", "web.upload_max_file_mb", "web.upload_max_total_mb", "web_terminal.bind", "web_terminal.tunnel",
|
|
714
765
|
];
|
|
715
766
|
function WebSection({ ctx, staged }) {
|
|
716
767
|
const { schema } = ctx;
|
|
@@ -10,11 +10,11 @@ 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 {
|
|
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"],
|
|
@@ -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>
|
|
@@ -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",
|