@songsid/agend 2.1.4-beta.9 → 2.1.4
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 +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-endpoint.js +1 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.d.ts +12 -8
- package/dist/backend/antigravity.js +189 -42
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +87 -23
- package/dist/backend/claude-code.js +361 -46
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +18 -0
- package/dist/backend/codex.js +139 -5
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.js +2 -2
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/grok.js +2 -2
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +52 -2
- package/dist/backend/kiro.js +267 -12
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode.js +17 -4
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +122 -3
- package/dist/backend/types.js +5 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-outage.d.ts +61 -0
- package/dist/backend-outage.js +71 -0
- package/dist/backend-outage.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +44 -2
- package/dist/channel/adapters/discord.js +523 -90
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +30 -0
- package/dist/channel/adapters/telegram.js +221 -19
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/agy-mcp-launcher.d.ts +2 -0
- package/dist/channel/agy-mcp-launcher.js +28 -0
- package/dist/channel/agy-mcp-launcher.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +2 -1
- package/dist/channel/ipc-bridge.js +33 -4
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/markdown-chunk.d.ts +42 -0
- package/dist/channel/markdown-chunk.js +205 -0
- package/dist/channel/markdown-chunk.js.map +1 -0
- package/dist/channel/mcp-server.js +34 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +5 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js +64 -8
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/types.d.ts +31 -1
- package/dist/classic-channel-manager.d.ts +65 -0
- package/dist/classic-channel-manager.js +172 -3
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +124 -83
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +13 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +12 -1
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +6 -0
- package/dist/cross-instance-envelope.js +30 -0
- package/dist/cross-instance-envelope.js.map +1 -0
- package/dist/daemon.d.ts +491 -14
- package/dist/daemon.js +2121 -276
- package/dist/daemon.js.map +1 -1
- package/dist/doctor.d.ts +41 -0
- package/dist/doctor.js +267 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fleet-context.d.ts +63 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-manager.d.ts +402 -11
- package/dist/fleet-manager.js +2594 -422
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-yaml-slim.d.ts +10 -0
- package/dist/fleet-yaml-slim.js +59 -0
- package/dist/fleet-yaml-slim.js.map +1 -0
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
- package/dist/general-knowledge/skills/session-management/SKILL.md +171 -8
- package/dist/instance-lifecycle.d.ts +114 -1
- package/dist/instance-lifecycle.js +404 -14
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +42 -4
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +1024 -320
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.d.ts +91 -0
- package/dist/login-flows.js +170 -0
- package/dist/login-flows.js.map +1 -0
- package/dist/login-manager.d.ts +63 -0
- package/dist/login-manager.js +134 -0
- package/dist/login-manager.js.map +1 -0
- package/dist/network-family.d.ts +18 -0
- package/dist/network-family.js +20 -0
- package/dist/network-family.js.map +1 -0
- package/dist/outbound-handlers.d.ts +16 -1
- package/dist/outbound-handlers.js +174 -40
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +10 -3
- package/dist/outbound-schemas.js +10 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-input-residue.d.ts +52 -0
- package/dist/pane-input-residue.js +107 -0
- package/dist/pane-input-residue.js.map +1 -0
- package/dist/process-memory.d.ts +6 -0
- package/dist/process-memory.js +45 -0
- package/dist/process-memory.js.map +1 -1
- package/dist/reply-dedup.d.ts +7 -8
- package/dist/reply-dedup.js +0 -0
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-progress.d.ts +2 -0
- package/dist/restart-progress.js +3 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/scheduler/db.d.ts +12 -0
- package/dist/scheduler/db.js +59 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/service-installer.d.ts +11 -0
- package/dist/service-installer.js +84 -18
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +1 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-wizard.js +2 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +30 -0
- package/dist/spawn-gate.js +79 -0
- package/dist/spawn-gate.js.map +1 -0
- package/dist/steer-capability.d.ts +1 -0
- package/dist/steer-capability.js +6 -0
- package/dist/steer-capability.js.map +1 -0
- package/dist/storm-window.d.ts +83 -0
- package/dist/storm-window.js +251 -0
- package/dist/storm-window.js.map +1 -0
- package/dist/tips.d.ts +44 -0
- package/dist/tips.js +355 -0
- package/dist/tips.js.map +1 -0
- package/dist/tmux-manager.d.ts +16 -0
- package/dist/tmux-manager.js +110 -25
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +31 -7
- package/dist/topic-commands.js +342 -70
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +35 -10
- package/dist/transcript-sources.js +190 -38
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/ui/dashboard.html +55 -32
- package/dist/ui/settings.html +200 -60
- package/dist/ui/view.html +147 -30
- package/dist/usage/format-rich.d.ts +1 -1
- package/dist/usage/format-rich.js +28 -24
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +7 -0
- package/dist/usage/i18n-keys.js +34 -0
- package/dist/usage/i18n-keys.js.map +1 -0
- package/dist/usage/i18n.d.ts +5 -0
- package/dist/usage/i18n.js +27 -0
- package/dist/usage/i18n.js.map +1 -0
- package/dist/usage/providers.d.ts +21 -0
- package/dist/usage/providers.js +153 -75
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +11 -3
- package/dist/usage/usage-api.js +61 -24
- package/dist/usage/usage-api.js.map +1 -1
- package/package.json +2 -2
package/dist/ui/settings.html
CHANGED
|
@@ -127,15 +127,15 @@
|
|
|
127
127
|
<body>
|
|
128
128
|
<header>
|
|
129
129
|
<h1 data-i18n="title">⚙️ AgEnD Settings</h1>
|
|
130
|
-
<span id="verBadge" title="What's New" style="font-size:12px;color:var(--muted);cursor:pointer;padding:2px 8px;border:1px solid var(--border);border-radius:12px">v…</span>
|
|
130
|
+
<span id="verBadge" data-i18n-title="whatsNew" title="What's New" style="font-size:12px;color:var(--muted);cursor:pointer;padding:2px 8px;border:1px solid var(--border);border-radius:12px">v…</span>
|
|
131
131
|
<div class="grow"></div>
|
|
132
|
-
<button class="help-btn" id="langBtn" title="Language">🌐</button>
|
|
133
|
-
<button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
|
|
134
|
-
<button id="advancedToggle" title="Show additional controls">Show advanced</button>
|
|
132
|
+
<button class="help-btn" id="langBtn" data-i18n-title="languageButton" title="Language">🌐</button>
|
|
133
|
+
<button class="help-btn" id="helpBtn" data-i18n-title="helpButton" title="Help">ℹ️</button>
|
|
134
|
+
<button id="advancedToggle" data-i18n-title="showAdditional" title="Show additional controls">Show advanced</button>
|
|
135
135
|
</header>
|
|
136
136
|
<div class="help-backdrop" id="helpBackdrop"></div>
|
|
137
137
|
<aside class="help-panel" id="helpPanel">
|
|
138
|
-
<button class="close" id="helpClose" title="Close">×</button>
|
|
138
|
+
<button class="close" id="helpClose" data-i18n-title="close" title="Close">×</button>
|
|
139
139
|
<div id="helpContent"></div>
|
|
140
140
|
</aside>
|
|
141
141
|
<div class="banner" id="banner"></div>
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
backend: "Backend", model: "Model", workingDir: "Working directory", channelBinding: "Channel binding",
|
|
195
195
|
description: "Description", systemPrompt: "System prompt", tags: "Tags", generalTopicField: "General topic (fleet dispatcher)",
|
|
196
196
|
runtimeResources: "Runtime & Resources", autoPause: "Auto-pause after", minutes: "minutes", inheritDefault: "Inherit from defaults",
|
|
197
|
-
detection: "Detection", startup: "Startup", backendSection: "Backend", advancedSection: "Advanced & Access", hangEnabled: "Hang detector enabled", hangTimeout: "Stuck timeout", agentMode: "Agent mode", toolSet: "Tool set", logLevel: "Log level", concurrency: "Startup concurrency", staggerDelay: "Stagger delay", lightweight: "Lightweight mode", modelFailover: "Fallback model", displayName: "Display name", pause: "Pause", wake: "Wake", accessMode: "Access mode", affected: "Affected instances",
|
|
197
|
+
detection: "Detection", startup: "Startup", backendSection: "Backend", advancedSection: "Advanced & Access", hangEnabled: "Hang detector enabled", hangTimeout: "Stuck timeout", agentMode: "Agent mode", toolSet: "Tool set", toolProgress: "Tool progress", logLevel: "Log level", concurrency: "Startup concurrency", staggerDelay: "Stagger delay", lightweight: "Lightweight mode", modelFailover: "Fallback model", displayName: "Display name", pause: "Pause", wake: "Wake", accessMode: "Access mode", affected: "Affected instances",
|
|
198
198
|
language: "Language (bot replies)", defaultBackend: "Default backend", defaultModel: "Default model", allowedUsers: "Allowed users (primary bot)",
|
|
199
199
|
classicAdmins: "ClassicBot admins", allowedGuilds: "Allowed guilds", type: "Type", adapterId: "Adapter id",
|
|
200
200
|
tokenEnv: "Bot token env var", groupId: "Group / guild id", nameField: "Name",
|
|
@@ -202,7 +202,11 @@
|
|
|
202
202
|
noAgents: "No agents yet — click + New.", noBots: "No bots configured.", online: "✅ Online",
|
|
203
203
|
currentVersion: "Current version",
|
|
204
204
|
helpTitle: "📖 Help", tipReload: "💡 Click [↻ Reload Fleet] after changes to apply them.",
|
|
205
|
-
wnIntro: "Recent highlights:", releaseNotes: "📄 View full Release Notes →",
|
|
205
|
+
wnIntro: "Recent highlights:", releaseNotes: "📄 View full Release Notes →", languageButton: "Language", helpButton: "Help", showAdditional: "Show additional controls", hideAdvanced: "Hide advanced", showAdvanced: "Show advanced",
|
|
206
|
+
saved: "Saved", failed: "Failed", actionFailed: "{0} failed", noAgentMatch: "No agents match \"{0}\".", deleteAgent: "Delete agent \"{0}\"? This will be applied only after you confirm the pending changes.", nameRequired: "name required", tokenEnvRequired: "token env required", removeBot: "Remove bot \"{0}\"? (the token in .env is left untouched)",
|
|
207
|
+
changesPending: "{0} change(s) not applied", impactNow: "⚡ Immediately", impactAgent: "🔄 Restart this Agent", impactFleet: "🔄🔄 Restart AgEnD", queued: "Queued: {0}", applyFailed: "Apply failed", changesApplied: "✓ Changes applied. AgEnD is reconciling affected Agents.", reviewChanges: "Review changes", readyApply: "Ready to apply", copied: "Copied", expectedMapping: "expected a mapping", configLoadFailed: "Failed to load config — check the token in the URL.",
|
|
208
|
+
confirmOpenAccess: "Open access allows anyone in this channel to operate the bot. Continue?", confirmLockedEmpty: "Locked access has no allowed users. This may leave nobody able to administer the bot. Continue?", accessLockedEmpty: "Locked mode has no allowed users; add at least one administrator to avoid lockout.", accessChangeCancelled: "Access mode change cancelled",
|
|
209
|
+
mustNonNegative: "Must be 0 or greater", mustPositive: "Must be greater than 0", nonnegativeInteger: "Must be a non-negative integer", failoverEmpty: "Empty list disables failover", concurrencyRange: "Must be an integer from 1 to 20", concurrencyWarning: "May exceed CPU capacity on smaller hosts", staggerRange: "Must be between 0 and 30000", staggerWarning: "Very low values can cause startup spikes",
|
|
206
210
|
},
|
|
207
211
|
"zh-TW": {
|
|
208
212
|
title: "⚙️ AgEnD 設定", reloadFleet: "↻ 重新載入", myAgents: "🤖 我的 Agent", bots: "🔌 機器人", classicChannels: "🎮 ClassicBot 頻道", noClassic: "沒有 ClassicBot 頻道 — 在聊天中用 /start 建立。",
|
|
@@ -212,7 +216,7 @@
|
|
|
212
216
|
backend: "後端", model: "模型", workingDir: "工作目錄", channelBinding: "頻道綁定",
|
|
213
217
|
description: "描述", systemPrompt: "System prompt", tags: "標籤", generalTopicField: "General topic(fleet 分派器)",
|
|
214
218
|
runtimeResources: "執行環境與資源", autoPause: "閒置後自動暫停", minutes: "分鐘", inheritDefault: "沿用全域預設值",
|
|
215
|
-
detection: "偵測", startup: "啟動", backendSection: "後端", advancedSection: "進階與存取", hangEnabled: "啟用卡住偵測", hangTimeout: "卡住逾時", agentMode: "Agent 模式", toolSet: "工具集", logLevel: "Log 等級", concurrency: "啟動併發數", staggerDelay: "錯開延遲", lightweight: "輕量模式", modelFailover: "備援模型", displayName: "顯示名稱", pause: "暫停", wake: "喚醒", accessMode: "存取模式", affected: "受影響 instances",
|
|
219
|
+
detection: "偵測", startup: "啟動", backendSection: "後端", advancedSection: "進階與存取", hangEnabled: "啟用卡住偵測", hangTimeout: "卡住逾時", agentMode: "Agent 模式", toolSet: "工具集", toolProgress: "工具進度", logLevel: "Log 等級", concurrency: "啟動併發數", staggerDelay: "錯開延遲", lightweight: "輕量模式", modelFailover: "備援模型", displayName: "顯示名稱", pause: "暫停", wake: "喚醒", accessMode: "存取模式", affected: "受影響 instances",
|
|
216
220
|
language: "語言(bot 回覆)", defaultBackend: "預設後端", defaultModel: "預設模型", allowedUsers: "允許使用者(主 bot)",
|
|
217
221
|
classicAdmins: "ClassicBot 管理員", allowedGuilds: "允許的伺服器", type: "類型", adapterId: "Adapter id",
|
|
218
222
|
tokenEnv: "Bot token 環境變數", groupId: "群組 / guild id", nameField: "名稱",
|
|
@@ -220,7 +224,11 @@
|
|
|
220
224
|
noAgents: "還沒有 agent — 點 + 新增。", noBots: "尚未設定機器人。", online: "✅ 上線",
|
|
221
225
|
currentVersion: "目前版本",
|
|
222
226
|
helpTitle: "📖 使用說明", tipReload: "💡 修改後點 [↻ 重新載入] 讓設定生效。",
|
|
223
|
-
wnIntro: "近期更新重點:", releaseNotes: "📄 查看完整 Release Notes →",
|
|
227
|
+
wnIntro: "近期更新重點:", releaseNotes: "📄 查看完整 Release Notes →", languageButton: "語言", helpButton: "使用說明", showAdditional: "顯示更多控制項", hideAdvanced: "隱藏進階設定", showAdvanced: "顯示進階設定",
|
|
228
|
+
saved: "已儲存", failed: "失敗", actionFailed: "{0}失敗", noAgentMatch: "沒有符合「{0}」的 Agent。", deleteAgent: "確定刪除 Agent「{0}」嗎?只有在確認套用待處理變更後才會生效。", nameRequired: "必須填寫名稱", tokenEnvRequired: "必須填寫 token 環境變數", removeBot: "確定移除機器人「{0}」嗎?(.env 中的 token 會保留)",
|
|
229
|
+
changesPending: "有 {0} 項變更尚未套用", impactNow: "⚡ 立即生效", impactAgent: "🔄 重新啟動此 Agent", impactFleet: "🔄🔄 重新啟動 AgEnD", queued: "已加入待套用:{0}", applyFailed: "套用失敗", changesApplied: "✓ 變更已套用,AgEnD 正在協調受影響的 Agent。", reviewChanges: "檢查變更", readyApply: "可套用變更", copied: "已複製", expectedMapping: "預期為 mapping", configLoadFailed: "載入設定失敗 — 請檢查網址中的 token。",
|
|
230
|
+
confirmOpenAccess: "Open 模式會允許此頻道中的所有人操作 bot,確定要繼續嗎?", confirmLockedEmpty: "Locked 模式目前沒有允許的使用者,可能導致沒有人能管理 bot,確定要繼續嗎?", accessLockedEmpty: "Locked 模式沒有允許的使用者;建議至少加入一位管理員,避免無法操作。", accessChangeCancelled: "已取消 Access Mode 變更",
|
|
231
|
+
mustNonNegative: "必須為 0 或更大的數值", mustPositive: "必須大於 0", nonnegativeInteger: "必須是非負整數", failoverEmpty: "空白清單會停用模型備援", concurrencyRange: "必須是 1 到 20 的整數", concurrencyWarning: "在較小的主機上可能超出 CPU 負荷", staggerRange: "必須介於 0 到 30000", staggerWarning: "數值太低可能造成啟動尖峰",
|
|
224
232
|
},
|
|
225
233
|
};
|
|
226
234
|
const HELP = {
|
|
@@ -242,9 +250,11 @@
|
|
|
242
250
|
let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
|
|
243
251
|
if (!I18N[lang]) lang = "en";
|
|
244
252
|
const t = (k) => (I18N[lang] && I18N[lang][k]) || I18N.en[k] || k;
|
|
253
|
+
const tf = (k, ...values) => values.reduce((text, value, index) => text.replaceAll(`{${index}}`, String(value)), t(k));
|
|
245
254
|
function applyStaticI18n() {
|
|
246
255
|
document.querySelectorAll("[data-i18n]").forEach(e => { e.textContent = t(e.getAttribute("data-i18n")); });
|
|
247
256
|
document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", t(e.getAttribute("data-i18n-ph"))); });
|
|
257
|
+
document.querySelectorAll("[data-i18n-title]").forEach(e => { e.setAttribute("title", t(e.getAttribute("data-i18n-title"))); });
|
|
248
258
|
// toggle labels that carry expand/collapse state
|
|
249
259
|
for (const [id, body] of [["advToggle", "advBody"]]) {
|
|
250
260
|
const b = $(body); if (b) $(id).textContent = b.classList.contains("open") ? t("collapse") : t("expand");
|
|
@@ -292,7 +302,7 @@
|
|
|
292
302
|
const b = res.body || {}; let html = "";
|
|
293
303
|
if (b.errors && b.errors.length) html += b.errors.map(e => `<span class="e">✗ ${esc(e.path ? e.path + ": " : "")}${esc(e.message)}</span>`).join(" ");
|
|
294
304
|
if (b.warnings && b.warnings.length) html += " " + b.warnings.map(w => `<span class="w">⚠ ${esc(w.message)}</span>`).join(" ");
|
|
295
|
-
if (res.ok) html = `<span class="s">✓
|
|
305
|
+
if (res.ok) html = `<span class="s">✓ ${esc(t("saved"))}</span>` + (html ? " " + html : "");
|
|
296
306
|
else if (!html) html = `<span class="e">✗ ${esc((b && b.error) || ("HTTP " + res.status))}</span>`;
|
|
297
307
|
elm.innerHTML = html;
|
|
298
308
|
}
|
|
@@ -322,37 +332,52 @@
|
|
|
322
332
|
redraw(); return box;
|
|
323
333
|
}
|
|
324
334
|
function select(value, options) { const s = el("select"); for (const o of options) { const val = typeof o === "string" ? o : o.value; const label = typeof o === "string" ? o : o.label; const opt = el("option", {}, label); opt.value = val; if (val === value) opt.selected = true; s.append(opt); } return s; }
|
|
325
|
-
const
|
|
335
|
+
const impactText = (kind) => t(kind === "now" ? "impactNow" : kind === "fleet" ? "impactFleet" : "impactAgent");
|
|
336
|
+
const impact = (kind) => el("span", { class: "impact" }, impactText(kind));
|
|
326
337
|
function updatePendingBar() {
|
|
327
338
|
const n = state.pending.size;
|
|
328
339
|
$("pendingBar").classList.toggle("open", n > 0);
|
|
329
|
-
$("pendingCount").textContent =
|
|
340
|
+
$("pendingCount").textContent = tf("changesPending", n);
|
|
330
341
|
const kinds = new Set([...state.pending.values()].map(change => change.impact));
|
|
331
|
-
$("pendingImpact").textContent = [...kinds].map(
|
|
342
|
+
$("pendingImpact").textContent = [...kinds].map(impactText).join(" · ");
|
|
332
343
|
}
|
|
333
344
|
function stageChange(key, change) {
|
|
334
345
|
state.pending.set(key, change);
|
|
335
346
|
updatePendingBar();
|
|
336
|
-
showBanner(
|
|
347
|
+
showBanner(tf("queued", change.label));
|
|
337
348
|
}
|
|
338
349
|
async function applyPendingChanges() {
|
|
339
350
|
const button = $("applyChanges"); button.disabled = true;
|
|
351
|
+
// Complete every safety confirmation before the first write. Otherwise a
|
|
352
|
+
// cancelled access-mode change could leave earlier staged settings applied
|
|
353
|
+
// and later ones pending (a surprising partial apply).
|
|
354
|
+
for (const change of state.pending.values()) {
|
|
355
|
+
if (change.confirm && !change.confirm()) {
|
|
356
|
+
showBanner(change.cancelMessage || t("accessChangeCancelled"), true);
|
|
357
|
+
button.disabled = false; return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
340
360
|
for (const [key, change] of [...state.pending]) {
|
|
341
361
|
let result;
|
|
342
362
|
try { result = await change.apply(); }
|
|
343
363
|
catch (error) { result = { ok: false, body: { error: error.message } }; }
|
|
344
364
|
if (!result?.ok) {
|
|
345
|
-
showBanner(`${change.label}: ${result?.body?.error || "
|
|
365
|
+
showBanner(`${change.label}: ${result?.body?.error || t("applyFailed")}`, true);
|
|
346
366
|
button.disabled = false; return;
|
|
347
367
|
}
|
|
348
368
|
state.pending.delete(key); updatePendingBar();
|
|
349
369
|
}
|
|
350
370
|
await api("/api/settings/reload", { method: "POST" });
|
|
351
371
|
await reload();
|
|
352
|
-
showBanner("
|
|
372
|
+
showBanner(t("changesApplied"));
|
|
353
373
|
button.disabled = false;
|
|
354
374
|
}
|
|
355
375
|
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
376
|
+
const HOT_FIELDS = new Set(["tool_progress", "mcp_proxy_reply", "auto_pause_after", "warm_cap", "display_name", "description", "tags", "log_level"]);
|
|
377
|
+
const sameValue = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
378
|
+
function changedFields(next, baseline) {
|
|
379
|
+
return Object.fromEntries(Object.entries(next).filter(([key, value]) => !sameValue(value, baseline[key])));
|
|
380
|
+
}
|
|
356
381
|
function overrideControl(inst, key, input, inheritedValue, impactKind = "instance") {
|
|
357
382
|
const inherited = !hasOwn(inst, key);
|
|
358
383
|
const toggle = el("input", { type: "checkbox" }); toggle.checked = inherited;
|
|
@@ -370,6 +395,15 @@
|
|
|
370
395
|
feedback.textContent = error ? `✗ ${error}` : warning ? `⚠ ${warning}` : "";
|
|
371
396
|
return !error;
|
|
372
397
|
}
|
|
398
|
+
function confirmAccessChange(previousAccess, nextMode, nextUsers) {
|
|
399
|
+
const previousMode = previousAccess?.mode || "locked";
|
|
400
|
+
const previousUsers = previousAccess?.allowed_users || [];
|
|
401
|
+
if (previousMode !== "open" && nextMode === "open" && !confirm(t("confirmOpenAccess"))) return false;
|
|
402
|
+
const newlyEmptyLocked = nextMode === "locked" && nextUsers.length === 0
|
|
403
|
+
&& (previousMode !== "locked" || previousUsers.length > 0);
|
|
404
|
+
if (newlyEmptyLocked && !confirm(t("confirmLockedEmpty"))) return false;
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
373
407
|
|
|
374
408
|
// ── My Agents ──
|
|
375
409
|
let editing = null;
|
|
@@ -385,12 +419,23 @@
|
|
|
385
419
|
const STATUS_TEXT = { running: "🟢 Running", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped" };
|
|
386
420
|
const EXECUTION_TEXT = { idle: "🟢 Idle", working: "🔵 Working", stuck: "🔴 Stuck", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped", running: "🟢 Running" };
|
|
387
421
|
const STATUS_ORDER = { running: 0, paused: 1, crashed: 2, stopped: 3 };
|
|
422
|
+
const EFFORT_BACKENDS = new Set(["claude-code", "codex", "kiro-cli", "grok", "antigravity"]);
|
|
388
423
|
const groupOf = (inst) => (inst.tags && inst.tags[0]) || "Other";
|
|
424
|
+
function effectiveSummary(name, config, classic = false) {
|
|
425
|
+
const live = state.live[name] || {};
|
|
426
|
+
const defaults = classic ? (state.classic?.defaults || {}) : (state.fleet?.defaults || {});
|
|
427
|
+
const backend = live.backend || config.backend || defaults.backend || state.fleet?.defaults?.backend || "claude-code";
|
|
428
|
+
const model = live.model_display || config.model || defaults.model || state.fleet?.defaults?.model || "default";
|
|
429
|
+
const effortSupported = live.effort_supported ?? EFFORT_BACKENDS.has(backend);
|
|
430
|
+
const effort = effortSupported ? (live.effort || config.effort || state.fleet?.defaults?.effort || "auto") : "—";
|
|
431
|
+
return { backend, model, effort };
|
|
432
|
+
}
|
|
389
433
|
function compactRow(name, inst) {
|
|
390
434
|
const st = state.live[name]?.status || "stopped";
|
|
391
435
|
const running = st === "running";
|
|
392
436
|
const paused = st === "paused";
|
|
393
437
|
const short = shortName(name, inst);
|
|
438
|
+
const summary = effectiveSummary(name, inst);
|
|
394
439
|
const chTag = agentChannelLabel(inst);
|
|
395
440
|
const item = el("div", { class: "item" });
|
|
396
441
|
// Single fixed-height line: dot + short name + backend + channel tag + actions.
|
|
@@ -398,7 +443,8 @@
|
|
|
398
443
|
item.append(el("div", { class: "item-row" },
|
|
399
444
|
el("span", { class: "dot " + st, title: STATUS_TEXT[st] || st }),
|
|
400
445
|
el("span", { class: "name", title: name }, short),
|
|
401
|
-
el("span", { class: "sub", style: "margin:0"
|
|
446
|
+
el("span", { class: "sub", style: "margin:0", title: summary.backend }, summary.model),
|
|
447
|
+
el("span", { class: "tag" }, `effort: ${summary.effort}`),
|
|
402
448
|
chTag ? el("span", { class: "tag" + (chTag.includes("persona") ? " persona" : chTag.includes("telegram") ? " tg" : "") }, chTag) : null,
|
|
403
449
|
el("span", { class: "spacer" }),
|
|
404
450
|
el("button", { class: "sm ghost", onclick: () => { editing = editing === name ? null : name; renderAgents(); } }, editing === name ? t("close") : t("edit")),
|
|
@@ -419,7 +465,7 @@
|
|
|
419
465
|
const filter = ($("agentSearch")?.value || "").trim().toLowerCase();
|
|
420
466
|
let names = Object.keys(insts);
|
|
421
467
|
if (filter) names = names.filter(n => n.toLowerCase().includes(filter) || shortName(n, insts[n]).toLowerCase().includes(filter));
|
|
422
|
-
if (!names.length) { host.className = "card empty"; host.textContent = "
|
|
468
|
+
if (!names.length) { host.className = "card empty"; host.textContent = tf("noAgentMatch", filter); return; }
|
|
423
469
|
host.className = "card";
|
|
424
470
|
// Group by first tag; "Other" sorts last. Within a group: Running → Crashed
|
|
425
471
|
// → Stopped, then name.
|
|
@@ -455,6 +501,7 @@
|
|
|
455
501
|
const hang = overrideControl(hangHolder, "value", el("input", { type: "number", min: "0.1", step: "0.5", value: hangRaw.timeout_minutes ?? defaults.hang_detector?.timeout_minutes ?? 15 }), defaults.hang_detector?.timeout_minutes ?? 15);
|
|
456
502
|
const agentMode = overrideControl(inst, "agent_mode", select(inst.agent_mode || defaults.agent_mode || "mcp", ["mcp", "cli"]), defaults.agent_mode || "mcp");
|
|
457
503
|
const toolSet = overrideControl(inst, "tool_set", select(inst.tool_set || defaults.tool_set || "full", ["full", "standard", "minimal"]), defaults.tool_set || "full");
|
|
504
|
+
const toolProgress = overrideControl(inst, "tool_progress", select(inst.tool_progress || defaults.tool_progress || "off", ["off", "standard", "verbose"]), defaults.tool_progress || "off", "now");
|
|
458
505
|
const logLevel = overrideControl(inst, "log_level", select(inst.log_level || defaults.log_level || "info", ["trace", "debug", "info", "warn", "error"]), defaults.log_level || "info");
|
|
459
506
|
const fLightweight = el("input", { type: "checkbox" }); fLightweight.checked = inst.lightweight ?? defaults.lightweight ?? false;
|
|
460
507
|
const lightweight = overrideControl(inst, "lightweight", fLightweight, defaults.lightweight ?? false);
|
|
@@ -468,18 +515,18 @@
|
|
|
468
515
|
const saveBtn = el("button", { class: "primary sm" }, t("save"));
|
|
469
516
|
const validate = () => {
|
|
470
517
|
const autoValue = Number(auto.input.value), hangValue = Number(hang.input.value);
|
|
471
|
-
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? "
|
|
472
|
-
const validHang = setValidation(hang.input, hangFeedback, !hang.toggle.checked && (!Number.isFinite(hangValue) || hangValue <= 0) ? "
|
|
473
|
-
setValidation(failover.input, failoverFeedback, "", !failover.toggle.checked && !failover.input.value.trim() ? "
|
|
518
|
+
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? t("mustNonNegative") : "");
|
|
519
|
+
const validHang = setValidation(hang.input, hangFeedback, !hang.toggle.checked && (!Number.isFinite(hangValue) || hangValue <= 0) ? t("mustPositive") : "");
|
|
520
|
+
setValidation(failover.input, failoverFeedback, "", !failover.toggle.checked && !failover.input.value.trim() ? t("failoverEmpty") : "");
|
|
474
521
|
saveBtn.disabled = !(validAuto && validHang);
|
|
475
522
|
return !saveBtn.disabled;
|
|
476
523
|
};
|
|
477
|
-
for (const ctl of [auto, hang, agentMode, toolSet, logLevel, lightweight, failover, displayName]) {
|
|
524
|
+
for (const ctl of [auto, hang, agentMode, toolSet, toolProgress, logLevel, lightweight, failover, displayName]) {
|
|
478
525
|
ctl.input.addEventListener("input", validate); ctl.input.addEventListener("change", validate); ctl.toggle.addEventListener("change", validate);
|
|
479
526
|
}
|
|
480
527
|
saveBtn.onclick = async () => {
|
|
481
528
|
if (!validate()) return;
|
|
482
|
-
const
|
|
529
|
+
const nextPatch = {
|
|
483
530
|
backend: fBackend.value, model: fModel.value.trim() || null,
|
|
484
531
|
working_directory: fWd.value, channel_id: fChan.value || undefined,
|
|
485
532
|
description: fDesc.value || undefined, systemPrompt: fSys.value || undefined,
|
|
@@ -488,11 +535,31 @@
|
|
|
488
535
|
hang_detector: { timeout_minutes: hang.toggle.checked ? null : Number(hang.input.value) },
|
|
489
536
|
agent_mode: agentMode.toggle.checked ? null : agentMode.input.value,
|
|
490
537
|
tool_set: toolSet.toggle.checked ? null : toolSet.input.value,
|
|
538
|
+
tool_progress: toolProgress.toggle.checked ? null : toolProgress.input.value,
|
|
491
539
|
log_level: logLevel.toggle.checked ? null : logLevel.input.value,
|
|
492
540
|
lightweight: lightweight.toggle.checked ? null : lightweight.input.checked,
|
|
493
541
|
model_failover: failover.toggle.checked ? null : (failover.input.value.trim() ? [failover.input.value.trim()] : []),
|
|
494
542
|
display_name: displayName.toggle.checked ? null : displayName.input.value.trim(),
|
|
495
543
|
};
|
|
544
|
+
const patch = changedFields(nextPatch, {
|
|
545
|
+
backend: inst.backend || defaults.backend || "claude-code",
|
|
546
|
+
model: inst.model?.trim() || null,
|
|
547
|
+
working_directory: inst.working_directory || "",
|
|
548
|
+
channel_id: inst.channel_id || undefined,
|
|
549
|
+
description: inst.description || undefined,
|
|
550
|
+
systemPrompt: inst.systemPrompt || undefined,
|
|
551
|
+
tags: inst.tags?.length ? inst.tags : undefined,
|
|
552
|
+
general_topic: !!inst.general_topic,
|
|
553
|
+
auto_pause_after: hasOwn(inst, "auto_pause_after") ? inst.auto_pause_after : null,
|
|
554
|
+
hang_detector: { timeout_minutes: hasOwn(hangRaw, "timeout_minutes") ? hangRaw.timeout_minutes : null },
|
|
555
|
+
agent_mode: hasOwn(inst, "agent_mode") ? inst.agent_mode : null,
|
|
556
|
+
tool_set: hasOwn(inst, "tool_set") ? inst.tool_set : null,
|
|
557
|
+
tool_progress: hasOwn(inst, "tool_progress") ? inst.tool_progress : null,
|
|
558
|
+
log_level: hasOwn(inst, "log_level") ? inst.log_level : null,
|
|
559
|
+
lightweight: hasOwn(inst, "lightweight") ? inst.lightweight : null,
|
|
560
|
+
model_failover: hasOwn(inst, "model_failover") ? inst.model_failover : null,
|
|
561
|
+
display_name: hasOwn(inst, "display_name") ? inst.display_name : null,
|
|
562
|
+
});
|
|
496
563
|
state.fleet.instances[name] = { ...inst, ...patch };
|
|
497
564
|
for (const [key, value] of Object.entries(patch)) if (value === null) delete state.fleet.instances[name][key];
|
|
498
565
|
if (patch.hang_detector?.timeout_minutes === null) {
|
|
@@ -501,7 +568,7 @@
|
|
|
501
568
|
}
|
|
502
569
|
stageChange(`agent:${name}`, {
|
|
503
570
|
label: `Update ${shortName(name, state.fleet.instances[name])}`,
|
|
504
|
-
impact: "instance",
|
|
571
|
+
impact: Object.keys(patch).every(key => HOT_FIELDS.has(key)) ? "now" : "instance",
|
|
505
572
|
apply: () => api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
|
506
573
|
});
|
|
507
574
|
editing = null; renderAgents();
|
|
@@ -515,7 +582,7 @@
|
|
|
515
582
|
el("div", {}, el("label", {}, t("model"), impact("instance")), fModel)),
|
|
516
583
|
el("label", {}, t("workingDir"), impact("instance")), fWd,
|
|
517
584
|
el("div", { class: "grid2" },
|
|
518
|
-
el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("
|
|
585
|
+
el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("now")), auto.row, autoFeedback)),
|
|
519
586
|
el("div", { class: "advanced-only" },
|
|
520
587
|
el("div", { class: "sub", style: "margin:14px 0 6px;font-weight:600" }, "Advanced Agent settings"),
|
|
521
588
|
el("div", { class: "grid2" },
|
|
@@ -523,7 +590,8 @@
|
|
|
523
590
|
el("div", {}, el("label", {}, `${t("hangTimeout")} (${t("minutes")})`), hang.row, hangFeedback),
|
|
524
591
|
el("div", {}, el("label", {}, t("agentMode")), agentMode.row),
|
|
525
592
|
el("div", {}, el("label", {}, t("toolSet")), toolSet.row),
|
|
526
|
-
el("div", {}, el("label", {}, t("
|
|
593
|
+
el("div", {}, el("label", {}, t("toolProgress"), impact("now")), toolProgress.row),
|
|
594
|
+
el("div", {}, el("label", {}, t("logLevel"), impact("now")), logLevel.row),
|
|
527
595
|
el("div", {}, el("label", {}, t("lightweight")), lightweight.row),
|
|
528
596
|
el("div", {}, el("label", {}, t("modelFailover")), failover.row, failoverFeedback)),
|
|
529
597
|
el("label", {}, t("systemPrompt"), impact("instance")), fSys,
|
|
@@ -544,11 +612,11 @@
|
|
|
544
612
|
}
|
|
545
613
|
async function pauseWakeAgent(name, action) {
|
|
546
614
|
const res = await api(`/api/settings/instances/${encodeURIComponent(name)}/${action}`, { method: "POST" });
|
|
547
|
-
if (!res.ok) alert(res.body?.error ||
|
|
615
|
+
if (!res.ok) alert(res.body?.error || tf("actionFailed", action));
|
|
548
616
|
await reloadLive();
|
|
549
617
|
}
|
|
550
618
|
async function delAgent(name) {
|
|
551
|
-
if (!confirm(
|
|
619
|
+
if (!confirm(tf("deleteAgent", name))) return;
|
|
552
620
|
delete state.fleet.instances[name];
|
|
553
621
|
stageChange(`agent:${name}`, {
|
|
554
622
|
label: `Delete ${name}`, impact: "fleet",
|
|
@@ -569,7 +637,7 @@
|
|
|
569
637
|
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("nameField")), fName), el("div", {}, el("label", {}, t("backend")), fBackend)),
|
|
570
638
|
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("workingDir")), fWd), el("div", {}, el("label", {}, t("channelBinding")), fChan)),
|
|
571
639
|
el("div", { class: "actions" }, el("button", { class: "primary sm", onclick: async () => {
|
|
572
|
-
const name = fName.value.trim(); if (!name) { msg.innerHTML = `<span class="e">✗
|
|
640
|
+
const name = fName.value.trim(); if (!name) { msg.innerHTML = `<span class="e">✗ ${esc(t("nameRequired"))}</span>`; return; }
|
|
573
641
|
const body = { working_directory: fWd.value, backend: fBackend.value, channel_id: fChan.value || undefined };
|
|
574
642
|
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "POST", body: JSON.stringify(body) });
|
|
575
643
|
resultMsg(msg, res); if (res.ok) { state.newAgentOpen = false; $("newAgentBtn").textContent = t("new"); renderNewAgent(false); await reload(); }
|
|
@@ -604,16 +672,21 @@
|
|
|
604
672
|
function botEditForm(ch, i) {
|
|
605
673
|
ch.access = ch.access || { mode: "locked", allowed_users: [] };
|
|
606
674
|
ch.access.allowed_users = ch.access.allowed_users || [];
|
|
675
|
+
const previousAccess = structuredClone(ch.access);
|
|
607
676
|
const box = el("div", { class: "edit" });
|
|
608
677
|
const fMode = select(ch.access.mode || "locked", ["open", "locked", "pairing"]);
|
|
609
678
|
const users = [...ch.access.allowed_users];
|
|
610
679
|
const msg = el("span", { class: "msg" });
|
|
680
|
+
const accessFeedback = el("div", { class: "feedback" });
|
|
681
|
+
const validateAccess = () => setValidation(fMode, accessFeedback, "", fMode.value === "locked" && users.length === 0 ? t("accessLockedEmpty") : "");
|
|
682
|
+
fMode.addEventListener("change", validateAccess);
|
|
611
683
|
box.append(
|
|
612
684
|
el("div", { class: "grid2" },
|
|
613
|
-
el("div", {}, el("label", {}, t("accessMode"), impact("fleet")), fMode),
|
|
614
|
-
el("div", {}, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(users,
|
|
685
|
+
el("div", {}, el("label", {}, t("accessMode"), impact("fleet")), fMode, accessFeedback),
|
|
686
|
+
el("div", {}, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(users, validateAccess, "user id"))),
|
|
615
687
|
el("div", { class: "actions" },
|
|
616
688
|
el("button", { class: "primary sm", onclick: async () => {
|
|
689
|
+
if (!confirmAccessChange(previousAccess, fMode.value, users)) return;
|
|
617
690
|
const chs = channels();
|
|
618
691
|
chs[i] = { ...chs[i], access: { ...chs[i].access, mode: fMode.value, allowed_users: users } };
|
|
619
692
|
const res = await saveChannels(msg);
|
|
@@ -625,6 +698,7 @@
|
|
|
625
698
|
} }, t("save")),
|
|
626
699
|
el("button", { class: "sm ghost", onclick: () => { editingBot = null; renderBots(); } }, t("cancel")), msg),
|
|
627
700
|
);
|
|
701
|
+
validateAccess();
|
|
628
702
|
return box;
|
|
629
703
|
}
|
|
630
704
|
|
|
@@ -655,8 +729,8 @@
|
|
|
655
729
|
const validate = () => {
|
|
656
730
|
const autoValue = Number(auto.input.value);
|
|
657
731
|
const contextValue = Number(fContext.value);
|
|
658
|
-
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? "
|
|
659
|
-
const validContext = setValidation(fContext, contextFeedback, !Number.isInteger(contextValue) || contextValue < 0 ? "
|
|
732
|
+
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? t("mustNonNegative") : "");
|
|
733
|
+
const validContext = setValidation(fContext, contextFeedback, !Number.isInteger(contextValue) || contextValue < 0 ? t("nonnegativeInteger") : "");
|
|
660
734
|
saveBtn.disabled = !(validAuto && validContext);
|
|
661
735
|
return !saveBtn.disabled;
|
|
662
736
|
};
|
|
@@ -719,11 +793,14 @@
|
|
|
719
793
|
const runtime = classicRuntime(c.instanceName);
|
|
720
794
|
const st = runtime.status;
|
|
721
795
|
const adapter = c.adapterId || "discord";
|
|
796
|
+
const summary = effectiveSummary(c.instanceName, c, true);
|
|
797
|
+
const displayName = shortName(c.name || c.instanceName || "?", { display_name: c.display_name });
|
|
722
798
|
const item = el("div", { class: "item" });
|
|
723
799
|
item.append(el("div", { class: "item-row" },
|
|
724
800
|
el("span", { class: "dot " + runtime.execution, title: EXECUTION_TEXT[runtime.execution] || runtime.execution }),
|
|
725
|
-
el("span", { class: "name", title: c.instanceName || "" },
|
|
726
|
-
el("span", { class: "sub", style: "margin:0"
|
|
801
|
+
el("span", { class: "name", title: c.instanceName || "" }, displayName),
|
|
802
|
+
el("span", { class: "sub", style: "margin:0", title: summary.backend }, summary.model),
|
|
803
|
+
el("span", { class: "tag" }, `effort: ${summary.effort}`),
|
|
727
804
|
el("span", { class: "tag" + (adapter !== "discord" ? " persona" : "") }, adapter),
|
|
728
805
|
c.collab ? el("span", { class: "tag", style: "background:#ecfdf5;color:#065f46" }, "collab") : null,
|
|
729
806
|
el("span", { class: "spacer" }),
|
|
@@ -744,10 +821,10 @@
|
|
|
744
821
|
}
|
|
745
822
|
async function delBot(i) {
|
|
746
823
|
const chs = channels(); const ch = chs[i];
|
|
747
|
-
if (!confirm(
|
|
824
|
+
if (!confirm(tf("removeBot", `${ch.type} (${chLabel(i)})`))) return;
|
|
748
825
|
chs.splice(i, 1);
|
|
749
826
|
state.fleet.channels = chs; delete state.fleet.channel;
|
|
750
|
-
const res = await saveChannels(); if (res.ok) await reload(); else alert((res.body && res.body.error) || "
|
|
827
|
+
const res = await saveChannels(); if (res.ok) await reload(); else alert((res.body && res.body.error) || t("failed"));
|
|
751
828
|
}
|
|
752
829
|
function renderNewBot(open) {
|
|
753
830
|
const host = $("newBot"); host.innerHTML = ""; if (!open) return;
|
|
@@ -762,7 +839,7 @@
|
|
|
762
839
|
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("tokenEnv")), fEnv), el("div", {}, el("label", {}, t("groupId")), fGroup)),
|
|
763
840
|
el("div", { class: "hint" }, t("envHint")),
|
|
764
841
|
el("div", { class: "actions" }, el("button", { class: "primary sm", onclick: async () => {
|
|
765
|
-
if (!fEnv.value.trim()) { msg.innerHTML = `<span class="e">✗
|
|
842
|
+
if (!fEnv.value.trim()) { msg.innerHTML = `<span class="e">✗ ${esc(t("tokenEnvRequired"))}</span>`; return; }
|
|
766
843
|
const chs = channels();
|
|
767
844
|
chs.push({ id: fId.value.trim() || undefined, type: fType.value, bot_token_env: fEnv.value.trim(), group_id: fGroup.value.trim() || undefined, access: { mode: "locked", allowed_users: [] } });
|
|
768
845
|
state.fleet.channels = chs; delete state.fleet.channel;
|
|
@@ -778,11 +855,16 @@
|
|
|
778
855
|
state.fleet.defaults = state.fleet.defaults || {};
|
|
779
856
|
state.classic.defaults = state.classic.defaults || {};
|
|
780
857
|
const d = state.fleet.defaults, c = state.classic.defaults;
|
|
858
|
+
// The controls below mutate their arrays in place. Keep raw snapshots so
|
|
859
|
+
// changing one hot default does not also stage untouched access settings as
|
|
860
|
+
// a fleet restart.
|
|
861
|
+
const originalChannels = structuredClone(channels());
|
|
862
|
+
const originalClassicDefaults = structuredClone(c);
|
|
781
863
|
const hang = d.hang_detector || {};
|
|
782
864
|
const startup = d.startup || {};
|
|
783
865
|
const primary = channels()[0];
|
|
784
|
-
|
|
785
|
-
const allowedUsers =
|
|
866
|
+
const originalPrimaryAccess = structuredClone(primary?.access || { mode: "locked", allowed_users: [] });
|
|
867
|
+
const allowedUsers = [...(originalPrimaryAccess.allowed_users || [])];
|
|
786
868
|
c.admin_users = c.admin_users || []; c.allowed_guilds = c.allowed_guilds || [];
|
|
787
869
|
|
|
788
870
|
const fBackend = select(d.backend || "claude-code", BACKENDS);
|
|
@@ -790,6 +872,8 @@
|
|
|
790
872
|
const fModel = el("input", { type: "text", value: d.model || "", placeholder: "e.g. claude-opus-4-8" });
|
|
791
873
|
const fAgentMode = select(d.agent_mode || "mcp", ["mcp", "cli"]);
|
|
792
874
|
const fToolSet = select(d.tool_set || "full", ["full", "standard", "minimal"]);
|
|
875
|
+
const fToolProgress = select(d.tool_progress || "off", ["off", "standard", "verbose"]);
|
|
876
|
+
const fAccessMode = select(originalPrimaryAccess.mode || "locked", ["locked", "open", "pairing"]);
|
|
793
877
|
const fLogLevel = select(d.log_level || "info", ["trace", "debug", "info", "warn", "error"]);
|
|
794
878
|
const fLocale = el("select");
|
|
795
879
|
const fAutoPause = el("input", { type: "number", min: "0", step: "1", value: d.auto_pause_after ?? 0, placeholder: "0 = disabled" });
|
|
@@ -803,41 +887,84 @@
|
|
|
803
887
|
const hangFeedback = el("div", { class: "feedback" });
|
|
804
888
|
const concurrencyFeedback = el("div", { class: "feedback" });
|
|
805
889
|
const staggerFeedback = el("div", { class: "feedback" });
|
|
890
|
+
const accessFeedback = el("div", { class: "feedback" });
|
|
806
891
|
const saveBtn = el("button", { class: "primary" }, t("save"));
|
|
807
892
|
const validate = () => {
|
|
808
893
|
const auto = Number(fAutoPause.value), timeout = Number(fHangTimeout.value);
|
|
809
894
|
const concurrency = Number(fConcurrency.value), stagger = Number(fStagger.value);
|
|
810
|
-
const validAuto = setValidation(fAutoPause, autoFeedback, !Number.isFinite(auto) || auto < 0 ? "
|
|
811
|
-
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? "
|
|
812
|
-
const validConcurrency = setValidation(fConcurrency, concurrencyFeedback, !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 20 ? "
|
|
813
|
-
const validStagger = setValidation(fStagger, staggerFeedback, !Number.isFinite(stagger) || stagger < 0 || stagger > 30000 ? "
|
|
895
|
+
const validAuto = setValidation(fAutoPause, autoFeedback, !Number.isFinite(auto) || auto < 0 ? t("mustNonNegative") : "");
|
|
896
|
+
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? t("mustPositive") : "");
|
|
897
|
+
const validConcurrency = setValidation(fConcurrency, concurrencyFeedback, !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 20 ? t("concurrencyRange") : "", concurrency > 4 ? t("concurrencyWarning") : "");
|
|
898
|
+
const validStagger = setValidation(fStagger, staggerFeedback, !Number.isFinite(stagger) || stagger < 0 || stagger > 30000 ? t("staggerRange") : "", stagger < 100 ? t("staggerWarning") : "");
|
|
899
|
+
if (primary) setValidation(fAccessMode, accessFeedback, "", fAccessMode.value === "locked" && allowedUsers.length === 0 ? t("accessLockedEmpty") : "");
|
|
814
900
|
saveBtn.disabled = !(validAuto && validHang && validConcurrency && validStagger);
|
|
815
901
|
return !saveBtn.disabled;
|
|
816
902
|
};
|
|
817
903
|
for (const input of [fAutoPause, fHangTimeout, fConcurrency, fStagger]) {
|
|
818
904
|
input.addEventListener("input", validate); input.addEventListener("change", validate);
|
|
819
905
|
}
|
|
820
|
-
|
|
906
|
+
fAccessMode.addEventListener("change", validate);
|
|
907
|
+
saveBtn.textContent = t("reviewChanges");
|
|
821
908
|
saveBtn.onclick = () => {
|
|
822
909
|
if (!validate()) return;
|
|
823
|
-
const
|
|
910
|
+
const nextDefaults = {
|
|
824
911
|
backend: fBackend.value,
|
|
825
912
|
model: fModel.value || undefined,
|
|
826
913
|
locale: fLocale.value,
|
|
827
914
|
auto_pause_after: Number(fAutoPause.value),
|
|
828
915
|
agent_mode: fAgentMode.value,
|
|
829
916
|
tool_set: fToolSet.value,
|
|
917
|
+
tool_progress: fToolProgress.value,
|
|
830
918
|
log_level: fLogLevel.value,
|
|
831
919
|
hang_detector: { enabled: fHangEnabled.checked, timeout_minutes: Number(fHangTimeout.value) },
|
|
832
920
|
startup: { concurrency: Number(fConcurrency.value), stagger_delay_ms: Number(fStagger.value) },
|
|
833
921
|
};
|
|
834
|
-
const
|
|
922
|
+
const defaultsPatch = changedFields(nextDefaults, {
|
|
923
|
+
backend: d.backend || "claude-code",
|
|
924
|
+
model: d.model || undefined,
|
|
925
|
+
locale: d.locale || "",
|
|
926
|
+
auto_pause_after: d.auto_pause_after ?? 0,
|
|
927
|
+
agent_mode: d.agent_mode || "mcp",
|
|
928
|
+
tool_set: d.tool_set || "full",
|
|
929
|
+
tool_progress: d.tool_progress || "off",
|
|
930
|
+
log_level: d.log_level || "info",
|
|
931
|
+
hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? 15 },
|
|
932
|
+
startup: { concurrency: startup.concurrency ?? 10, stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
|
|
933
|
+
});
|
|
934
|
+
const classicPatch = changedFields(
|
|
935
|
+
{ backend: fClassicBackend.value, admin_users: c.admin_users, allowed_guilds: c.allowed_guilds },
|
|
936
|
+
{
|
|
937
|
+
backend: originalClassicDefaults.backend || d.backend || "claude-code",
|
|
938
|
+
admin_users: originalClassicDefaults.admin_users || [],
|
|
939
|
+
allowed_guilds: originalClassicDefaults.allowed_guilds || [],
|
|
940
|
+
},
|
|
941
|
+
);
|
|
835
942
|
state.fleet.defaults = { ...d, ...defaultsPatch };
|
|
836
943
|
state.classic.defaults = { ...c, ...classicPatch };
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
944
|
+
if (Object.keys(defaultsPatch).length > 0) {
|
|
945
|
+
stageChange("defaults:fleet", { label: "Update Agent defaults", impact: Object.keys(defaultsPatch).every(key => HOT_FIELDS.has(key)) ? "now" : "instance", apply: () => api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) }) });
|
|
946
|
+
}
|
|
947
|
+
if (primary) {
|
|
948
|
+
const stagedAccessMode = fAccessMode.value;
|
|
949
|
+
const stagedAllowedUsers = [...allowedUsers];
|
|
950
|
+
const nextChannels = structuredClone(originalChannels);
|
|
951
|
+
nextChannels[0] = { ...nextChannels[0], access: { ...nextChannels[0].access, mode: stagedAccessMode, allowed_users: stagedAllowedUsers } };
|
|
952
|
+
if (!sameValue(nextChannels, originalChannels)) {
|
|
953
|
+
stageChange("connections:primary", {
|
|
954
|
+
label: "Update primary connection access",
|
|
955
|
+
impact: "fleet",
|
|
956
|
+
// Confirm the immutable staged snapshot, not live form controls
|
|
957
|
+
// that the user may edit again before pressing global Apply.
|
|
958
|
+
confirm: () => confirmAccessChange(originalPrimaryAccess, stagedAccessMode, stagedAllowedUsers),
|
|
959
|
+
cancelMessage: t("accessChangeCancelled"),
|
|
960
|
+
apply: () => api("/api/settings/fleet/channels", { method: "PUT", body: JSON.stringify(nextChannels) }),
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (Object.keys(classicPatch).length > 0) {
|
|
965
|
+
stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
|
|
966
|
+
}
|
|
967
|
+
msg.innerHTML = `<span class="s">${esc(t("readyApply"))}</span>`;
|
|
841
968
|
};
|
|
842
969
|
|
|
843
970
|
const section = (title, open, ...children) => {
|
|
@@ -851,7 +978,7 @@
|
|
|
851
978
|
el("div", { class: "field" }, el("label", {}, t("language"), impact("now")), fLocale),
|
|
852
979
|
el("div", { class: "field" }, el("label", {}, t("defaultBackend"), impact("instance")), fBackend),
|
|
853
980
|
el("div", { class: "field" }, el("label", {}, t("defaultModel"), impact("instance")), fModel),
|
|
854
|
-
el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("
|
|
981
|
+
el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("now")), fAutoPause, autoFeedback))),
|
|
855
982
|
section("ClassicBot", true,
|
|
856
983
|
el("div", { class: "grid2" },
|
|
857
984
|
el("div", { class: "field" }, el("label", {}, "Classic default backend", impact("instance")), fClassicBackend)),
|
|
@@ -863,7 +990,8 @@
|
|
|
863
990
|
section(t("runtimeResources"), false,
|
|
864
991
|
el("div", { class: "grid2" },
|
|
865
992
|
el("div", { class: "field" }, el("label", {}, t("toolSet"), impact("instance")), fToolSet),
|
|
866
|
-
el("div", { class: "field" }, el("label", {}, t("
|
|
993
|
+
el("div", { class: "field" }, el("label", {}, t("toolProgress"), impact("now")), fToolProgress),
|
|
994
|
+
el("div", { class: "field" }, el("label", {}, t("logLevel"), impact("now")), fLogLevel),
|
|
867
995
|
el("div", { class: "field" }, el("label", {}, t("agentMode"), impact("instance")), fAgentMode))),
|
|
868
996
|
section(t("detection"), false,
|
|
869
997
|
el("div", { class: "grid2" },
|
|
@@ -874,7 +1002,11 @@
|
|
|
874
1002
|
el("div", { class: "field" }, el("label", {}, t("concurrency"), impact("fleet")), fConcurrency, concurrencyFeedback),
|
|
875
1003
|
el("div", { class: "field" }, el("label", {}, `${t("staggerDelay")} (ms)`, impact("fleet")), fStagger, staggerFeedback))),
|
|
876
1004
|
section("Advanced & Access", false,
|
|
877
|
-
|
|
1005
|
+
primary
|
|
1006
|
+
? el("div", { class: "grid2" },
|
|
1007
|
+
el("div", { class: "field" }, el("label", {}, t("accessMode"), impact("fleet")), fAccessMode, accessFeedback),
|
|
1008
|
+
el("div", { class: "field" }, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(allowedUsers, validate, "user id")))
|
|
1009
|
+
: el("div", { class: "field hint" }, t("noBots")))),
|
|
878
1010
|
el("div", { class: "field" }, el("div", { class: "actions" }, saveBtn, msg)),
|
|
879
1011
|
);
|
|
880
1012
|
validate();
|
|
@@ -906,7 +1038,7 @@
|
|
|
906
1038
|
el("button", { class: "sm" + (fmt === "yaml" ? " on" : ""), onclick: () => { state.advFmt = "yaml"; renderAdvanced(); } }, "YAML"),
|
|
907
1039
|
el("button", { class: "sm" + (fmt === "json" ? " on" : ""), onclick: () => { state.advFmt = "json"; renderAdvanced(); } }, "JSON")),
|
|
908
1040
|
el("button", { class: "sm", onclick: () => { state.advEdit = !state.advEdit; renderAdvanced(); } }, state.advEdit ? "View" : "Edit"),
|
|
909
|
-
el("button", { class: "sm", onclick: () => navigator.clipboard.writeText(ser).then(() => { msg.innerHTML = `<span class="s"
|
|
1041
|
+
el("button", { class: "sm", onclick: () => navigator.clipboard.writeText(ser).then(() => { msg.innerHTML = `<span class="s">${esc(t("copied"))}</span>`; }) }, "Copy"),
|
|
910
1042
|
el("button", { class: "sm", onclick: () => { const blob = new Blob([toYaml(fleetModel())], { type: "text/plain" }); const a = el("a", { href: URL.createObjectURL(blob), download: "fleet.yaml" }); a.click(); URL.revokeObjectURL(a.href); } }, "⬇ fleet.yaml"),
|
|
911
1043
|
);
|
|
912
1044
|
host.append(toolbar);
|
|
@@ -922,7 +1054,7 @@
|
|
|
922
1054
|
}
|
|
923
1055
|
// Apply the edited full model: PUT defaults + channels, and diff instances.
|
|
924
1056
|
async function applyFullModel(m, msg) {
|
|
925
|
-
if (!m || typeof m !== "object") { msg.innerHTML = `<span class="e">✗
|
|
1057
|
+
if (!m || typeof m !== "object") { msg.innerHTML = `<span class="e">✗ ${esc(t("expectedMapping"))}</span>`; return; }
|
|
926
1058
|
const results = [];
|
|
927
1059
|
if (m.defaults) results.push(await api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(m.defaults) }));
|
|
928
1060
|
const chs = m.channels || (m.channel ? [m.channel] : null);
|
|
@@ -947,7 +1079,15 @@
|
|
|
947
1079
|
state.fleetUp = f.ok;
|
|
948
1080
|
state.live = {}; state.classicLive = {};
|
|
949
1081
|
if (f.ok && f.body?.instances) for (const i of f.body.instances) {
|
|
950
|
-
state.live[i.name] = {
|
|
1082
|
+
state.live[i.name] = {
|
|
1083
|
+
status: i.status,
|
|
1084
|
+
state: i.state,
|
|
1085
|
+
backend: i.backend,
|
|
1086
|
+
model: i.model,
|
|
1087
|
+
model_display: i.model_display,
|
|
1088
|
+
effort: i.effort,
|
|
1089
|
+
effort_supported: i.effort_supported,
|
|
1090
|
+
};
|
|
951
1091
|
if (i.classic) state.classicLive[i.name] = { status: i.status, state: i.state };
|
|
952
1092
|
}
|
|
953
1093
|
// /api/fleet carries live tri-state for Classic instances. Profiles remain
|
|
@@ -960,7 +1100,7 @@
|
|
|
960
1100
|
async function reload() {
|
|
961
1101
|
// Forms edit the user-authored config, not defaults-expanded runtime values.
|
|
962
1102
|
const f = await api("/api/settings/fleet/raw");
|
|
963
|
-
if (!f.ok) { $("banner").style.display = "block"; $("banner").style.background = "#fef2f2"; $("banner").style.color = "var(--red)"; $("banner").textContent = "
|
|
1103
|
+
if (!f.ok) { $("banner").style.display = "block"; $("banner").style.background = "#fef2f2"; $("banner").style.color = "var(--red)"; $("banner").textContent = t("configLoadFailed"); return; }
|
|
964
1104
|
state.fleet = f.body || {};
|
|
965
1105
|
const c = await api("/api/settings/classic"); state.classic = c.body || {};
|
|
966
1106
|
await reloadLive();
|
|
@@ -979,12 +1119,12 @@
|
|
|
979
1119
|
$("advToggle").onclick = () => { const b = $("advBody"); const open = !b.classList.contains("open"); b.classList.toggle("open", open); $("advToggle").textContent = open ? t("collapse") : t("expand"); };
|
|
980
1120
|
const advancedEnabled = localStorage.getItem("agend_settings_advanced") === "true";
|
|
981
1121
|
document.body.classList.toggle("show-advanced", advancedEnabled);
|
|
982
|
-
$("advancedToggle").textContent = advancedEnabled ? "
|
|
1122
|
+
$("advancedToggle").textContent = advancedEnabled ? t("hideAdvanced") : t("showAdvanced");
|
|
983
1123
|
$("advancedToggle").onclick = () => {
|
|
984
1124
|
const enabled = !document.body.classList.contains("show-advanced");
|
|
985
1125
|
document.body.classList.toggle("show-advanced", enabled);
|
|
986
1126
|
localStorage.setItem("agend_settings_advanced", String(enabled));
|
|
987
|
-
$("advancedToggle").textContent = enabled ? "
|
|
1127
|
+
$("advancedToggle").textContent = enabled ? t("hideAdvanced") : t("showAdvanced");
|
|
988
1128
|
};
|
|
989
1129
|
$("applyChanges").onclick = applyPendingChanges;
|
|
990
1130
|
$("discardChanges").onclick = async () => { state.pending.clear(); updatePendingBar(); await reload(); };
|