@songsid/agend 2.1.0-beta.3 → 2.1.0-beta.30
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 -0
- package/README.zh-TW.md +2 -1
- package/dist/access-path.js +3 -3
- package/dist/access-path.js.map +1 -1
- package/dist/agent-cli.js +1 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +8 -0
- package/dist/agent-endpoint.js +36 -8
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.js +3 -2
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +4 -4
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/factory.js +4 -1
- package/dist/backend/factory.js.map +1 -1
- package/dist/backend/grok.d.ts +51 -0
- package/dist/backend/grok.js +282 -0
- package/dist/backend/grok.js.map +1 -0
- package/dist/backend/kiro.js +3 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +12 -2
- package/dist/backend/types.js +1 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +20 -0
- package/dist/channel/adapters/discord.js +85 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.js +4 -1
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +17 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +14 -1
- package/dist/classic-channel-manager.js +65 -4
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +108 -16
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +68 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +13 -1
- package/dist/config.js.map +1 -1
- package/dist/context-guardian.d.ts +2 -0
- package/dist/context-guardian.js +10 -2
- package/dist/context-guardian.js.map +1 -1
- package/dist/daemon-entry.js +2 -1
- package/dist/daemon-entry.js.map +1 -1
- package/dist/daemon.d.ts +76 -6
- package/dist/daemon.js +451 -113
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +4 -0
- package/dist/fleet-manager.d.ts +88 -7
- package/dist/fleet-manager.js +707 -129
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +1 -0
- package/dist/instance-lifecycle.d.ts +11 -1
- package/dist/instance-lifecycle.js +64 -22
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +24 -2
- package/dist/locale.js.map +1 -1
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +29 -10
- package/dist/logger.js.map +1 -1
- package/dist/outbound-handlers.d.ts +3 -0
- package/dist/outbound-handlers.js +203 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +40 -1
- package/dist/outbound-schemas.js +36 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pause-marker.d.ts +5 -0
- package/dist/pause-marker.js +36 -0
- package/dist/pause-marker.js.map +1 -0
- package/dist/settings-api.d.ts +20 -2
- package/dist/settings-api.js +213 -9
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-wizard.js +8 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/statusline-watcher.d.ts +1 -1
- package/dist/statusline-watcher.js +3 -2
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/tmux-control.d.ts +7 -0
- package/dist/tmux-control.js +22 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +1 -1
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +19 -1
- package/dist/topic-commands.js +132 -23
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +2 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/ui/dashboard.html +2 -2
- package/dist/ui/settings.html +453 -74
- package/dist/web-api.d.ts +1 -0
- package/dist/web-api.js +24 -15
- package/dist/web-api.js.map +1 -1
- package/dist/workflow-templates/default.md +0 -4
- package/package.json +2 -1
package/dist/ui/settings.html
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
9
|
:root {
|
|
10
10
|
--bg:#ffffff; --card:#f9fafb; --border:#e5e7eb; --fg:#111827; --muted:#6b7280;
|
|
11
|
-
--accent:#2563eb; --accent-hover:#1d4ed8; --green:#059669; --red:#dc2626;
|
|
11
|
+
--accent:#2563eb; --accent-hover:#1d4ed8; --green:#059669; --red:#dc2626; --yellow:#b45309;
|
|
12
12
|
--radius:8px; --radius-sm:6px;
|
|
13
13
|
}
|
|
14
|
-
body { background: var(--bg); color: var(--fg); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; line-height: 1.5; padding-bottom:
|
|
14
|
+
body { background: var(--bg); color: var(--fg); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; line-height: 1.5; padding-bottom: 88px; }
|
|
15
15
|
header { display: flex; align-items: center; gap: 12px; padding: 16px 24px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg); z-index: 20; }
|
|
16
16
|
header h1 { font-size: 18px; font-weight: 700; }
|
|
17
17
|
.grow { flex: 1; }
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
.item .del-btn { visibility: hidden; }
|
|
33
33
|
.item:hover .del-btn { visibility: visible; }
|
|
34
34
|
.dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; background: #9ca3af; }
|
|
35
|
-
.dot.running { background: var(--green); } .dot.crashed { background: var(--red); } .dot.stopped { background: #9ca3af; }
|
|
35
|
+
.dot.running, .dot.idle { background: var(--green); } .dot.working { background:var(--accent); } .dot.paused { background:#f59e0b; } .dot.crashed, .dot.stuck { background: var(--red); } .dot.stopped { background: #9ca3af; }
|
|
36
36
|
.name { font-weight: 600; }
|
|
37
37
|
.tag { font-size: 12px; color: var(--muted); background: #eef2ff; color: #3730a3; padding: 1px 7px; border-radius: 10px; }
|
|
38
38
|
.tag.persona { background: #fdf2f8; color: #9d174d; }
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
button.danger:hover { background: #fef2f2; }
|
|
50
50
|
button.sm { padding: 4px 9px; font-size: 12px; }
|
|
51
51
|
label { display: block; font-size: 12px; font-weight: 500; color: var(--muted); margin: 10px 0 4px; }
|
|
52
|
-
input[type=text], select { width: 100%; font-family: inherit; font-size: 13px; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff; color: var(--fg); }
|
|
52
|
+
input[type=text], input[type=number], select { width: 100%; font-family: inherit; font-size: 13px; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff; color: var(--fg); }
|
|
53
53
|
input:focus, select:focus { outline: 2px solid #bfdbfe; border-color: var(--accent); }
|
|
54
54
|
.edit { padding: 14px 16px; background: #fff; border-top: 1px solid var(--border); }
|
|
55
55
|
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
@@ -63,8 +63,33 @@
|
|
|
63
63
|
.msg { font-size: 12px; margin-left: 4px; }
|
|
64
64
|
.msg .e { color: var(--red); } .msg .w { color: #b45309; } .msg .s { color: var(--green); }
|
|
65
65
|
.hint { font-size: 12px; color: var(--muted); margin-top: 6px; }
|
|
66
|
+
.impact { display:inline-block; font-size:11px; color:var(--muted); margin-left:6px; font-weight:400; }
|
|
67
|
+
.feedback { min-height:17px; font-size:12px; margin-top:3px; }
|
|
68
|
+
.feedback.error { color:var(--red); } .feedback.warning { color:var(--yellow); }
|
|
69
|
+
input.invalid, select.invalid { border-color:var(--red); background:#fef2f2; }
|
|
70
|
+
button:disabled { opacity:.5; cursor:not-allowed; }
|
|
71
|
+
.settings-section { border-bottom:1px solid var(--border); background:#fff; }
|
|
72
|
+
.settings-section:last-child { border-bottom:none; }
|
|
73
|
+
.settings-section > summary { cursor:pointer; list-style:none; padding:12px 16px; font-weight:650; background:#f9fafb; }
|
|
74
|
+
.settings-section > summary::-webkit-details-marker { display:none; }
|
|
75
|
+
.settings-section > summary::before { content:"▶"; display:inline-block; width:18px; color:var(--muted); font-size:10px; }
|
|
76
|
+
.settings-section[open] > summary::before { content:"▼"; }
|
|
77
|
+
.section-body { padding:0 0 2px; }
|
|
78
|
+
.inherit-row { display:flex; gap:8px; align-items:center; margin-top:6px; color:var(--muted); font-size:12px; }
|
|
66
79
|
.adv-body { display: none; padding: 14px 16px; }
|
|
67
80
|
.adv-body.open { display: block; }
|
|
81
|
+
.advanced-only { display:none !important; }
|
|
82
|
+
body.show-advanced .advanced-only { display:block !important; }
|
|
83
|
+
.more-menu { position:relative; }
|
|
84
|
+
.more-menu summary { list-style:none; cursor:pointer; padding:4px 9px; color:var(--muted); }
|
|
85
|
+
.more-menu summary::-webkit-details-marker { display:none; }
|
|
86
|
+
.more-menu .menu { position:absolute; right:0; top:30px; z-index:10; background:#fff; border:1px solid var(--border); border-radius:var(--radius-sm); box-shadow:0 8px 24px rgba(0,0,0,.12); padding:5px; min-width:130px; }
|
|
87
|
+
.more-menu .menu button { width:100%; text-align:left; border:0; }
|
|
88
|
+
.pending-bar { position:fixed; left:0; right:0; bottom:0; display:none; align-items:center; gap:12px; padding:12px max(16px, calc((100vw - 860px) / 2 + 24px)); background:#111827; color:#fff; z-index:35; box-shadow:0 -4px 18px rgba(0,0,0,.18); }
|
|
89
|
+
.pending-bar.open { display:flex; }
|
|
90
|
+
.pending-bar .grow { flex:1; }
|
|
91
|
+
.pending-bar button.primary { background:#fff; color:#111827; border-color:#fff; }
|
|
92
|
+
.level-label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.4px; margin-bottom:8px; }
|
|
68
93
|
.toolbar { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
|
69
94
|
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
|
|
70
95
|
.seg button { border: none; border-radius: 0; }
|
|
@@ -84,6 +109,19 @@
|
|
|
84
109
|
.help-panel li { font-size: 13px; margin: 3px 0; }
|
|
85
110
|
.help-panel .close { position: absolute; top: 14px; right: 16px; cursor: pointer; color: var(--muted); font-size: 22px; line-height: 1; background: none; border: none; }
|
|
86
111
|
.help-panel .tip { margin-top: 16px; padding: 10px 12px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 13px; }
|
|
112
|
+
@media (max-width: 640px) {
|
|
113
|
+
header { padding:10px 12px; flex-wrap:wrap; } header h1 { font-size:16px; }
|
|
114
|
+
main { padding:12px 10px; } section { margin-bottom:18px; }
|
|
115
|
+
.grid2 { grid-template-columns:1fr; gap:0; }
|
|
116
|
+
.item { padding:9px 10px; } .item-row { flex-wrap:wrap; }
|
|
117
|
+
.item-row .spacer { flex-basis:100%; height:0; }
|
|
118
|
+
.sub { margin-left:0; } .field { padding:10px 12px; }
|
|
119
|
+
#agentSearch { width:130px !important; margin-right:0 !important; }
|
|
120
|
+
button { min-height:36px; } button.sm { min-height:32px; }
|
|
121
|
+
.pending-bar { padding:10px 12px; flex-wrap:wrap; }
|
|
122
|
+
.pending-bar .grow { display:none; }
|
|
123
|
+
.pending-bar button { flex:1; }
|
|
124
|
+
}
|
|
87
125
|
</style>
|
|
88
126
|
</head>
|
|
89
127
|
<body>
|
|
@@ -93,7 +131,7 @@
|
|
|
93
131
|
<div class="grow"></div>
|
|
94
132
|
<button class="help-btn" id="langBtn" title="Language">🌐</button>
|
|
95
133
|
<button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
|
|
96
|
-
<button id="
|
|
134
|
+
<button id="advancedToggle" title="Show additional controls">Show advanced</button>
|
|
97
135
|
</header>
|
|
98
136
|
<div class="help-backdrop" id="helpBackdrop"></div>
|
|
99
137
|
<aside class="help-panel" id="helpPanel">
|
|
@@ -114,7 +152,7 @@
|
|
|
114
152
|
</section>
|
|
115
153
|
|
|
116
154
|
<section>
|
|
117
|
-
<div class="sec-head"><h2
|
|
155
|
+
<div class="sec-head"><h2>🔌 Connections & Bots</h2><div class="grow"></div><button class="sm advanced-only" id="newBotBtn" data-i18n="add">+ Add</button></div>
|
|
118
156
|
<div id="newBot"></div>
|
|
119
157
|
<div class="card" id="bots"></div>
|
|
120
158
|
</section>
|
|
@@ -125,15 +163,18 @@
|
|
|
125
163
|
</section>
|
|
126
164
|
|
|
127
165
|
<section>
|
|
128
|
-
<div class="
|
|
129
|
-
<div class="
|
|
130
|
-
</section>
|
|
131
|
-
|
|
132
|
-
<section>
|
|
133
|
-
<div class="sec-head"><h2 data-i18n="advanced">📋 Advanced</h2><div class="grow"></div><button class="sm" id="advToggle" data-i18n="expand">▶ Expand</button></div>
|
|
166
|
+
<div class="level-label">Developer · Level 3</div>
|
|
167
|
+
<div class="sec-head"><h2>📋 Developer YAML</h2><div class="grow"></div><button class="sm" id="advToggle" data-i18n="expand">▶ Expand</button></div>
|
|
134
168
|
<div class="card"><div class="adv-body" id="advBody"></div></div>
|
|
135
169
|
</section>
|
|
136
170
|
</main>
|
|
171
|
+
<div class="pending-bar" id="pendingBar">
|
|
172
|
+
<strong id="pendingCount">0 changes not applied</strong>
|
|
173
|
+
<span id="pendingImpact">⚡ Immediately · 🔄 Restart this Agent · 🔄🔄 Restart AgEnD</span>
|
|
174
|
+
<span class="grow"></span>
|
|
175
|
+
<button id="discardChanges">Discard</button>
|
|
176
|
+
<button class="primary" id="applyChanges">Apply changes</button>
|
|
177
|
+
</div>
|
|
137
178
|
<script>
|
|
138
179
|
(() => {
|
|
139
180
|
"use strict";
|
|
@@ -141,7 +182,7 @@
|
|
|
141
182
|
const $ = (id) => document.getElementById(id);
|
|
142
183
|
const el = (tag, attrs = {}, ...kids) => { const e = document.createElement(tag); for (const k in attrs) { if (k === "class") e.className = attrs[k]; else if (k === "html") e.innerHTML = attrs[k]; else if (k.startsWith("on")) e.addEventListener(k.slice(2), attrs[k]); else e.setAttribute(k, attrs[k]); } for (const c of kids) if (c != null) e.append(c); return e; };
|
|
143
184
|
const esc = (s) => String(s).replace(/[&<>]/g, c => ({ "&":"&","<":"<",">":">" }[c]));
|
|
144
|
-
const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "gemini-cli", "antigravity"];
|
|
185
|
+
const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "gemini-cli", "antigravity", "grok"];
|
|
145
186
|
|
|
146
187
|
// ── i18n ──
|
|
147
188
|
const I18N = {
|
|
@@ -152,6 +193,8 @@
|
|
|
152
193
|
edit: "Edit", close: "Close", save: "Save", cancel: "Cancel", create: "Create", start: "Start", stop: "Stop",
|
|
153
194
|
backend: "Backend", model: "Model", workingDir: "Working directory", channelBinding: "Channel binding",
|
|
154
195
|
description: "Description", systemPrompt: "System prompt", tags: "Tags", generalTopicField: "General topic (fleet dispatcher)",
|
|
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",
|
|
155
198
|
language: "Language (bot replies)", defaultBackend: "Default backend", defaultModel: "Default model", allowedUsers: "Allowed users (primary bot)",
|
|
156
199
|
classicAdmins: "ClassicBot admins", allowedGuilds: "Allowed guilds", type: "Type", adapterId: "Adapter id",
|
|
157
200
|
tokenEnv: "Bot token env var", groupId: "Group / guild id", nameField: "Name",
|
|
@@ -168,6 +211,8 @@
|
|
|
168
211
|
edit: "編輯", close: "關閉", save: "儲存", cancel: "取消", create: "建立", start: "啟動", stop: "停止",
|
|
169
212
|
backend: "後端", model: "模型", workingDir: "工作目錄", channelBinding: "頻道綁定",
|
|
170
213
|
description: "描述", systemPrompt: "System prompt", tags: "標籤", generalTopicField: "General topic(fleet 分派器)",
|
|
214
|
+
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",
|
|
171
216
|
language: "語言(bot 回覆)", defaultBackend: "預設後端", defaultModel: "預設模型", allowedUsers: "允許使用者(主 bot)",
|
|
172
217
|
classicAdmins: "ClassicBot 管理員", allowedGuilds: "允許的伺服器", type: "類型", adapterId: "Adapter id",
|
|
173
218
|
tokenEnv: "Bot token 環境變數", groupId: "群組 / guild id", nameField: "名稱",
|
|
@@ -201,7 +246,7 @@
|
|
|
201
246
|
document.querySelectorAll("[data-i18n]").forEach(e => { e.textContent = t(e.getAttribute("data-i18n")); });
|
|
202
247
|
document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", t(e.getAttribute("data-i18n-ph"))); });
|
|
203
248
|
// toggle labels that carry expand/collapse state
|
|
204
|
-
for (const [id, body] of [["advToggle", "advBody"]
|
|
249
|
+
for (const [id, body] of [["advToggle", "advBody"]]) {
|
|
205
250
|
const b = $(body); if (b) $(id).textContent = b.classList.contains("open") ? t("collapse") : t("expand");
|
|
206
251
|
}
|
|
207
252
|
renderHelp();
|
|
@@ -251,9 +296,17 @@
|
|
|
251
296
|
else if (!html) html = `<span class="e">✗ ${esc((b && b.error) || ("HTTP " + res.status))}</span>`;
|
|
252
297
|
elm.innerHTML = html;
|
|
253
298
|
}
|
|
299
|
+
function showBanner(text, error = false) {
|
|
300
|
+
const b = $("banner");
|
|
301
|
+
b.style.display = "block";
|
|
302
|
+
b.style.background = error ? "#fef2f2" : "#ecfdf5";
|
|
303
|
+
b.style.color = error ? "var(--red)" : "var(--green)";
|
|
304
|
+
b.textContent = text;
|
|
305
|
+
setTimeout(() => { b.style.display = "none"; }, 6000);
|
|
306
|
+
}
|
|
254
307
|
|
|
255
308
|
// ── State ──
|
|
256
|
-
const state = { fleet: {}, classic: {}, classicStatus: {}, live: {}, fleetUp: false, advFmt: "yaml", advEdit: false, collapsedGroups: new Set(), version: "", newAgentOpen: false, newBotOpen: false };
|
|
309
|
+
const state = { fleet: {}, classic: {}, classicStatus: {}, classicLive: {}, live: {}, fleetUp: false, advFmt: "yaml", advEdit: false, collapsedGroups: new Set(), version: "", newAgentOpen: false, newBotOpen: false, pending: new Map() };
|
|
257
310
|
const channels = () => state.fleet.channels || (state.fleet.channel ? [state.fleet.channel] : []);
|
|
258
311
|
const channelIds = () => channels().map(c => c.id || c.type).filter(Boolean);
|
|
259
312
|
const chLabel = (i) => i === 0 ? "primary" : "persona";
|
|
@@ -268,7 +321,55 @@
|
|
|
268
321
|
box.append(chips, el("div", { class: "chip-add" }, inp, el("button", { class: "sm", onclick: add }, "+ Add")));
|
|
269
322
|
redraw(); return box;
|
|
270
323
|
}
|
|
271
|
-
function select(value, options) { const s = el("select"); for (const o of options) { const opt = el("option", {},
|
|
324
|
+
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 impact = (kind) => el("span", { class: "impact" }, kind === "now" ? "⚡ Immediately" : kind === "fleet" ? "🔄🔄 Restart AgEnD" : "🔄 Restart this Agent");
|
|
326
|
+
function updatePendingBar() {
|
|
327
|
+
const n = state.pending.size;
|
|
328
|
+
$("pendingBar").classList.toggle("open", n > 0);
|
|
329
|
+
$("pendingCount").textContent = `${n} change${n === 1 ? "" : "s"} not applied`;
|
|
330
|
+
const kinds = new Set([...state.pending.values()].map(change => change.impact));
|
|
331
|
+
$("pendingImpact").textContent = [...kinds].map(kind => kind === "now" ? "⚡ Immediately" : kind === "fleet" ? "🔄🔄 Restart AgEnD" : "🔄 Restart this Agent").join(" · ");
|
|
332
|
+
}
|
|
333
|
+
function stageChange(key, change) {
|
|
334
|
+
state.pending.set(key, change);
|
|
335
|
+
updatePendingBar();
|
|
336
|
+
showBanner(`Queued: ${change.label}`);
|
|
337
|
+
}
|
|
338
|
+
async function applyPendingChanges() {
|
|
339
|
+
const button = $("applyChanges"); button.disabled = true;
|
|
340
|
+
for (const [key, change] of [...state.pending]) {
|
|
341
|
+
let result;
|
|
342
|
+
try { result = await change.apply(); }
|
|
343
|
+
catch (error) { result = { ok: false, body: { error: error.message } }; }
|
|
344
|
+
if (!result?.ok) {
|
|
345
|
+
showBanner(`${change.label}: ${result?.body?.error || "Apply failed"}`, true);
|
|
346
|
+
button.disabled = false; return;
|
|
347
|
+
}
|
|
348
|
+
state.pending.delete(key); updatePendingBar();
|
|
349
|
+
}
|
|
350
|
+
await api("/api/settings/reload", { method: "POST" });
|
|
351
|
+
await reload();
|
|
352
|
+
showBanner("✓ Changes applied. AgEnD is reconciling affected Agents.");
|
|
353
|
+
button.disabled = false;
|
|
354
|
+
}
|
|
355
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
356
|
+
function overrideControl(inst, key, input, inheritedValue, impactKind = "instance") {
|
|
357
|
+
const inherited = !hasOwn(inst, key);
|
|
358
|
+
const toggle = el("input", { type: "checkbox" }); toggle.checked = inherited;
|
|
359
|
+
input.disabled = inherited;
|
|
360
|
+
if (inherited && inheritedValue !== undefined) {
|
|
361
|
+
if (input.type === "checkbox") input.checked = !!inheritedValue;
|
|
362
|
+
else input.value = inheritedValue;
|
|
363
|
+
}
|
|
364
|
+
toggle.onchange = () => { input.disabled = toggle.checked; };
|
|
365
|
+
return { input, toggle, row: el("div", {}, input, el("label", { class: "inherit-row" }, toggle, el("span", {}, t("inheritDefault")), impact(impactKind))) };
|
|
366
|
+
}
|
|
367
|
+
function setValidation(input, feedback, error, warning) {
|
|
368
|
+
input.classList.toggle("invalid", !!error);
|
|
369
|
+
feedback.className = "feedback" + (error ? " error" : warning ? " warning" : "");
|
|
370
|
+
feedback.textContent = error ? `✗ ${error}` : warning ? `⚠ ${warning}` : "";
|
|
371
|
+
return !error;
|
|
372
|
+
}
|
|
272
373
|
|
|
273
374
|
// ── My Agents ──
|
|
274
375
|
let editing = null;
|
|
@@ -281,12 +382,14 @@
|
|
|
281
382
|
}
|
|
282
383
|
// Display name: profile display_name → else strip the -t<topicId> suffix.
|
|
283
384
|
const shortName = (name, inst) => (inst && inst.display_name) || name.replace(/-t\d+$/, "");
|
|
284
|
-
const STATUS_TEXT = { running: "🟢 Running", crashed: "🔴 Crashed", stopped: "⚪ Stopped" };
|
|
285
|
-
const
|
|
385
|
+
const STATUS_TEXT = { running: "🟢 Running", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped" };
|
|
386
|
+
const EXECUTION_TEXT = { idle: "🟢 Idle", working: "🔵 Working", stuck: "🔴 Stuck", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped", running: "🟢 Running" };
|
|
387
|
+
const STATUS_ORDER = { running: 0, paused: 1, crashed: 2, stopped: 3 };
|
|
286
388
|
const groupOf = (inst) => (inst.tags && inst.tags[0]) || "Other";
|
|
287
389
|
function compactRow(name, inst) {
|
|
288
390
|
const st = state.live[name]?.status || "stopped";
|
|
289
391
|
const running = st === "running";
|
|
392
|
+
const paused = st === "paused";
|
|
290
393
|
const short = shortName(name, inst);
|
|
291
394
|
const chTag = agentChannelLabel(inst);
|
|
292
395
|
const item = el("div", { class: "item" });
|
|
@@ -299,8 +402,12 @@
|
|
|
299
402
|
chTag ? el("span", { class: "tag" + (chTag.includes("persona") ? " persona" : chTag.includes("telegram") ? " tg" : "") }, chTag) : null,
|
|
300
403
|
el("span", { class: "spacer" }),
|
|
301
404
|
el("button", { class: "sm ghost", onclick: () => { editing = editing === name ? null : name; renderAgents(); } }, editing === name ? t("close") : t("edit")),
|
|
302
|
-
el("button", { class: "sm", onclick: () =>
|
|
303
|
-
el("button", { class: "sm
|
|
405
|
+
running ? el("button", { class: "sm", onclick: () => pauseWakeAgent(name, "pause") }, t("pause")) : null,
|
|
406
|
+
paused ? el("button", { class: "sm", onclick: () => pauseWakeAgent(name, "wake") }, t("wake")) : null,
|
|
407
|
+
!paused ? el("button", { class: "sm", onclick: () => toggleAgent(name, running) }, running ? t("stop") : t("start")) : null,
|
|
408
|
+
el("details", { class: "more-menu" },
|
|
409
|
+
el("summary", { title: "More actions" }, "⋯"),
|
|
410
|
+
el("div", { class: "menu" }, el("button", { class: "sm danger", onclick: () => delAgent(name) }, "Delete Agent"))),
|
|
304
411
|
));
|
|
305
412
|
if (editing === name) item.append(agentEditForm(name, inst));
|
|
306
413
|
return item;
|
|
@@ -333,6 +440,7 @@
|
|
|
333
440
|
}
|
|
334
441
|
function agentEditForm(name, inst) {
|
|
335
442
|
const box = el("div", { class: "edit" });
|
|
443
|
+
const defaults = state.fleet.defaults || {};
|
|
336
444
|
const fBackend = select(inst.backend || state.fleet.defaults?.backend || "claude-code", BACKENDS);
|
|
337
445
|
const fModel = el("input", { type: "text", value: inst.model || "", placeholder: "(inherit default)" });
|
|
338
446
|
const fWd = el("input", { type: "text", value: inst.working_directory || "" });
|
|
@@ -341,29 +449,92 @@
|
|
|
341
449
|
const fSys = el("input", { type: "text", value: inst.systemPrompt || "", placeholder: "e.g. file:prompts/dev.md" });
|
|
342
450
|
const tags = [...(inst.tags || [])];
|
|
343
451
|
const fGeneral = el("input", { type: "checkbox" }); fGeneral.checked = !!inst.general_topic;
|
|
452
|
+
const auto = overrideControl(inst, "auto_pause_after", el("input", { type: "number", min: "0", step: "1", value: inst.auto_pause_after ?? defaults.auto_pause_after ?? 0, placeholder: "0 = disabled" }), defaults.auto_pause_after ?? 0);
|
|
453
|
+
const hangRaw = inst.hang_detector || {};
|
|
454
|
+
const hangHolder = hasOwn(hangRaw, "timeout_minutes") ? { value: hangRaw.timeout_minutes } : {};
|
|
455
|
+
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
|
+
const agentMode = overrideControl(inst, "agent_mode", select(inst.agent_mode || defaults.agent_mode || "mcp", ["mcp", "cli"]), defaults.agent_mode || "mcp");
|
|
457
|
+
const toolSet = overrideControl(inst, "tool_set", select(inst.tool_set || defaults.tool_set || "full", ["full", "standard", "minimal"]), defaults.tool_set || "full");
|
|
458
|
+
const logLevel = overrideControl(inst, "log_level", select(inst.log_level || defaults.log_level || "info", ["trace", "debug", "info", "warn", "error"]), defaults.log_level || "info");
|
|
459
|
+
const fLightweight = el("input", { type: "checkbox" }); fLightweight.checked = inst.lightweight ?? defaults.lightweight ?? false;
|
|
460
|
+
const lightweight = overrideControl(inst, "lightweight", fLightweight, defaults.lightweight ?? false);
|
|
461
|
+
const fallbackValue = Array.isArray(inst.model_failover) ? (inst.model_failover[0] || "") : (defaults.model_failover?.[0] || "");
|
|
462
|
+
const failover = overrideControl(inst, "model_failover", el("input", { type: "text", value: fallbackValue, placeholder: "e.g. sonnet" }), defaults.model_failover?.[0] || "");
|
|
463
|
+
const displayName = overrideControl(inst, "display_name", el("input", { type: "text", value: inst.display_name ?? defaults.display_name ?? "", placeholder: name }), defaults.display_name || "");
|
|
464
|
+
const autoFeedback = el("div", { class: "feedback" });
|
|
465
|
+
const hangFeedback = el("div", { class: "feedback" });
|
|
466
|
+
const failoverFeedback = el("div", { class: "feedback" });
|
|
344
467
|
const msg = el("span", { class: "msg" });
|
|
468
|
+
const saveBtn = el("button", { class: "primary sm" }, t("save"));
|
|
469
|
+
const validate = () => {
|
|
470
|
+
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) ? "Must be 0 or greater" : "");
|
|
472
|
+
const validHang = setValidation(hang.input, hangFeedback, !hang.toggle.checked && (!Number.isFinite(hangValue) || hangValue <= 0) ? "Must be greater than 0" : "");
|
|
473
|
+
setValidation(failover.input, failoverFeedback, "", !failover.toggle.checked && !failover.input.value.trim() ? "Empty list disables failover" : "");
|
|
474
|
+
saveBtn.disabled = !(validAuto && validHang);
|
|
475
|
+
return !saveBtn.disabled;
|
|
476
|
+
};
|
|
477
|
+
for (const ctl of [auto, hang, agentMode, toolSet, logLevel, lightweight, failover, displayName]) {
|
|
478
|
+
ctl.input.addEventListener("input", validate); ctl.input.addEventListener("change", validate); ctl.toggle.addEventListener("change", validate);
|
|
479
|
+
}
|
|
480
|
+
saveBtn.onclick = async () => {
|
|
481
|
+
if (!validate()) return;
|
|
482
|
+
const patch = {
|
|
483
|
+
backend: fBackend.value, model: fModel.value || undefined,
|
|
484
|
+
working_directory: fWd.value, channel_id: fChan.value || undefined,
|
|
485
|
+
description: fDesc.value || undefined, systemPrompt: fSys.value || undefined,
|
|
486
|
+
tags: tags.length ? tags : undefined, general_topic: fGeneral.checked,
|
|
487
|
+
auto_pause_after: auto.toggle.checked ? null : Number(auto.input.value),
|
|
488
|
+
hang_detector: { timeout_minutes: hang.toggle.checked ? null : Number(hang.input.value) },
|
|
489
|
+
agent_mode: agentMode.toggle.checked ? null : agentMode.input.value,
|
|
490
|
+
tool_set: toolSet.toggle.checked ? null : toolSet.input.value,
|
|
491
|
+
log_level: logLevel.toggle.checked ? null : logLevel.input.value,
|
|
492
|
+
lightweight: lightweight.toggle.checked ? null : lightweight.input.checked,
|
|
493
|
+
model_failover: failover.toggle.checked ? null : (failover.input.value.trim() ? [failover.input.value.trim()] : []),
|
|
494
|
+
display_name: displayName.toggle.checked ? null : displayName.input.value.trim(),
|
|
495
|
+
};
|
|
496
|
+
state.fleet.instances[name] = { ...inst, ...patch };
|
|
497
|
+
for (const [key, value] of Object.entries(patch)) if (value === null) delete state.fleet.instances[name][key];
|
|
498
|
+
if (patch.hang_detector?.timeout_minutes === null) {
|
|
499
|
+
state.fleet.instances[name].hang_detector = { ...(inst.hang_detector || {}) };
|
|
500
|
+
delete state.fleet.instances[name].hang_detector.timeout_minutes;
|
|
501
|
+
}
|
|
502
|
+
stageChange(`agent:${name}`, {
|
|
503
|
+
label: `Update ${shortName(name, state.fleet.instances[name])}`,
|
|
504
|
+
impact: "instance",
|
|
505
|
+
apply: () => api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
|
506
|
+
});
|
|
507
|
+
editing = null; renderAgents();
|
|
508
|
+
};
|
|
345
509
|
box.append(
|
|
346
510
|
el("div", { class: "sub", style: "margin:0 0 8px" }, "ID: " + name),
|
|
347
|
-
el("div", { class: "grid2" },
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
el("label", {
|
|
511
|
+
el("div", { class: "grid2" },
|
|
512
|
+
el("div", {}, el("label", {}, t("displayName"), impact("now")), displayName.row),
|
|
513
|
+
el("div", {}, el("label", {}, t("description"), impact("now")), fDesc),
|
|
514
|
+
el("div", {}, el("label", {}, t("backend"), impact("instance")), fBackend),
|
|
515
|
+
el("div", {}, el("label", {}, t("model"), impact("instance")), fModel)),
|
|
516
|
+
el("label", {}, t("workingDir"), impact("instance")), fWd,
|
|
517
|
+
el("div", { class: "grid2" },
|
|
518
|
+
el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), auto.row, autoFeedback)),
|
|
519
|
+
el("div", { class: "advanced-only" },
|
|
520
|
+
el("div", { class: "sub", style: "margin:14px 0 6px;font-weight:600" }, "Advanced Agent settings"),
|
|
521
|
+
el("div", { class: "grid2" },
|
|
522
|
+
el("div", {}, el("label", {}, t("channelBinding"), impact("fleet")), fChan),
|
|
523
|
+
el("div", {}, el("label", {}, `${t("hangTimeout")} (${t("minutes")})`), hang.row, hangFeedback),
|
|
524
|
+
el("div", {}, el("label", {}, t("agentMode")), agentMode.row),
|
|
525
|
+
el("div", {}, el("label", {}, t("toolSet")), toolSet.row),
|
|
526
|
+
el("div", {}, el("label", {}, t("logLevel")), logLevel.row),
|
|
527
|
+
el("div", {}, el("label", {}, t("lightweight")), lightweight.row),
|
|
528
|
+
el("div", {}, el("label", {}, t("modelFailover")), failover.row, failoverFeedback)),
|
|
529
|
+
el("label", {}, t("systemPrompt"), impact("instance")), fSys,
|
|
530
|
+
el("label", {}, t("tags"), impact("now")), chipList(tags, null, "tag"),
|
|
531
|
+
el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer" }, fGeneral, el("span", {}, t("generalTopicField")), impact("fleet"))),
|
|
353
532
|
el("div", { class: "actions" },
|
|
354
|
-
|
|
355
|
-
const patch = {
|
|
356
|
-
backend: fBackend.value, model: fModel.value || undefined,
|
|
357
|
-
working_directory: fWd.value, channel_id: fChan.value || undefined,
|
|
358
|
-
description: fDesc.value || undefined, systemPrompt: fSys.value || undefined,
|
|
359
|
-
tags: tags.length ? tags : undefined, general_topic: fGeneral.checked,
|
|
360
|
-
};
|
|
361
|
-
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
362
|
-
resultMsg(msg, res); if (res.ok) await reload();
|
|
363
|
-
} }, t("save")),
|
|
533
|
+
saveBtn,
|
|
364
534
|
el("button", { class: "sm ghost", onclick: () => { editing = null; renderAgents(); } }, t("cancel")),
|
|
365
535
|
msg),
|
|
366
536
|
);
|
|
537
|
+
validate();
|
|
367
538
|
return box;
|
|
368
539
|
}
|
|
369
540
|
async function toggleAgent(name, running) {
|
|
@@ -371,10 +542,19 @@
|
|
|
371
542
|
else await api(`/api/instance/${encodeURIComponent(name)}/start`, { method: "POST" });
|
|
372
543
|
setTimeout(reloadLive, 800);
|
|
373
544
|
}
|
|
545
|
+
async function pauseWakeAgent(name, action) {
|
|
546
|
+
const res = await api(`/api/settings/instances/${encodeURIComponent(name)}/${action}`, { method: "POST" });
|
|
547
|
+
if (!res.ok) alert(res.body?.error || `${action} failed`);
|
|
548
|
+
await reloadLive();
|
|
549
|
+
}
|
|
374
550
|
async function delAgent(name) {
|
|
375
|
-
if (!confirm(`Delete agent "${name}"?
|
|
376
|
-
|
|
377
|
-
|
|
551
|
+
if (!confirm(`Delete agent "${name}"? This will be applied only after you confirm the pending changes.`)) return;
|
|
552
|
+
delete state.fleet.instances[name];
|
|
553
|
+
stageChange(`agent:${name}`, {
|
|
554
|
+
label: `Delete ${name}`, impact: "fleet",
|
|
555
|
+
apply: () => api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "DELETE" }),
|
|
556
|
+
});
|
|
557
|
+
renderAgents();
|
|
378
558
|
}
|
|
379
559
|
function renderNewAgent(open) {
|
|
380
560
|
const host = $("newAgent"); host.innerHTML = "";
|
|
@@ -399,6 +579,7 @@
|
|
|
399
579
|
}
|
|
400
580
|
|
|
401
581
|
// ── Bots ──
|
|
582
|
+
let editingBot = null;
|
|
402
583
|
function renderBots() {
|
|
403
584
|
const host = $("bots"); host.innerHTML = ""; const chs = channels();
|
|
404
585
|
if (!chs.length) { host.className = "card empty"; host.textContent = t("noBots"); return; }
|
|
@@ -408,20 +589,121 @@
|
|
|
408
589
|
const type = ch.type || "?";
|
|
409
590
|
item.append(el("div", { class: "item-row" },
|
|
410
591
|
el("span", { class: "name" }, type === "telegram" ? "Telegram" : "Discord"),
|
|
411
|
-
el("span", { class: "tag" + (i === 0 ? "" : " persona") + (type === "telegram" ? " tg" : "") }, chLabel(i)),
|
|
412
|
-
el("span", { class: "sub", style: "margin:0" }, ch.bot_token_env || "(no token env)"),
|
|
592
|
+
el("span", { class: "tag" + (i === 0 ? "" : " persona") + (type === "telegram" ? " tg" : "") }, ch.id || chLabel(i)),
|
|
593
|
+
el("span", { class: "sub advanced-only", style: "margin:0" }, ch.bot_token_env || "(no token env)"),
|
|
413
594
|
el("span", { class: "spacer" }),
|
|
414
|
-
el("span", { style: "
|
|
415
|
-
el("button", { class: "sm
|
|
595
|
+
el("span", { style: `color:${state.fleetUp ? "var(--green)" : "var(--red)"};font-size:13px` }, state.fleetUp ? "Connected" : "Problem"),
|
|
596
|
+
el("button", { class: "sm ghost advanced-only", onclick: () => { editingBot = editingBot === i ? null : i; renderBots(); } }, editingBot === i ? t("close") : t("edit")),
|
|
597
|
+
el("button", { class: "sm danger advanced-only", onclick: () => delBot(i) }, "Delete"),
|
|
416
598
|
));
|
|
599
|
+
if (editingBot === i) item.append(botEditForm(ch, i));
|
|
417
600
|
host.append(item);
|
|
418
601
|
});
|
|
419
602
|
}
|
|
420
603
|
|
|
421
|
-
|
|
604
|
+
function botEditForm(ch, i) {
|
|
605
|
+
ch.access = ch.access || { mode: "locked", allowed_users: [] };
|
|
606
|
+
ch.access.allowed_users = ch.access.allowed_users || [];
|
|
607
|
+
const box = el("div", { class: "edit" });
|
|
608
|
+
const fMode = select(ch.access.mode || "locked", ["open", "locked", "pairing"]);
|
|
609
|
+
const users = [...ch.access.allowed_users];
|
|
610
|
+
const msg = el("span", { class: "msg" });
|
|
611
|
+
box.append(
|
|
612
|
+
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, null, "user id"))),
|
|
615
|
+
el("div", { class: "actions" },
|
|
616
|
+
el("button", { class: "primary sm", onclick: async () => {
|
|
617
|
+
const chs = channels();
|
|
618
|
+
chs[i] = { ...chs[i], access: { ...chs[i].access, mode: fMode.value, allowed_users: users } };
|
|
619
|
+
const res = await saveChannels(msg);
|
|
620
|
+
if (res.ok) {
|
|
621
|
+
const affected = Object.keys(state.fleet.instances || {});
|
|
622
|
+
editingBot = null; await reload();
|
|
623
|
+
showBanner(`✓ ${t("affected")}: ${affected.length ? affected.join(", ") : chLabel(i) + " bot"} — 🔄🔄 restart fleet`);
|
|
624
|
+
}
|
|
625
|
+
} }, t("save")),
|
|
626
|
+
el("button", { class: "sm ghost", onclick: () => { editingBot = null; renderBots(); } }, t("cancel")), msg),
|
|
627
|
+
);
|
|
628
|
+
return box;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// ── ClassicBot Channels ──
|
|
632
|
+
let editingClassic = null;
|
|
633
|
+
function classicRuntime(instanceName) {
|
|
634
|
+
const live = state.classicLive[instanceName] || {};
|
|
635
|
+
const status = live.status || state.classicStatus[instanceName] || "stopped";
|
|
636
|
+
const execution = status === "paused" ? "paused" : (live.state || status);
|
|
637
|
+
return { status, execution };
|
|
638
|
+
}
|
|
639
|
+
function classicEditForm(c) {
|
|
640
|
+
const box = el("div", { class: "edit" });
|
|
641
|
+
const classicDefaults = state.classic?.defaults || {};
|
|
642
|
+
const fleetDefaults = state.fleet?.defaults || {};
|
|
643
|
+
const inheritedBackend = classicDefaults.backend || fleetDefaults.backend || "claude-code";
|
|
644
|
+
const inheritedModel = classicDefaults.model || fleetDefaults.model || "";
|
|
645
|
+
const inheritedAutoPause = classicDefaults.auto_pause_after ?? fleetDefaults.auto_pause_after ?? 0;
|
|
646
|
+
const runtime = classicRuntime(c.instanceName);
|
|
647
|
+
const fBackend = select(c.backend || inheritedBackend, BACKENDS.filter(value => value !== "gemini-cli"));
|
|
648
|
+
const fModel = el("input", { type: "text", value: c.model || "", placeholder: inheritedModel || "(inherit default)" });
|
|
649
|
+
const auto = overrideControl(c, "auto_pause_after", el("input", { type: "number", min: "0", step: "1", value: c.auto_pause_after ?? inheritedAutoPause, placeholder: "0 = disabled" }), inheritedAutoPause);
|
|
650
|
+
const fCollab = el("input", { type: "checkbox" }); fCollab.checked = !!c.collab;
|
|
651
|
+
const fContext = el("input", { type: "number", min: "0", step: "1", value: c.context_lines ?? classicDefaults.context_lines ?? 5 });
|
|
652
|
+
const autoFeedback = el("div", { class: "feedback" });
|
|
653
|
+
const contextFeedback = el("div", { class: "feedback" });
|
|
654
|
+
const saveBtn = el("button", { class: "primary sm" }, t("save"));
|
|
655
|
+
const validate = () => {
|
|
656
|
+
const autoValue = Number(auto.input.value);
|
|
657
|
+
const contextValue = Number(fContext.value);
|
|
658
|
+
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? "Must be 0 or greater" : "");
|
|
659
|
+
const validContext = setValidation(fContext, contextFeedback, !Number.isInteger(contextValue) || contextValue < 0 ? "Must be a non-negative integer" : "");
|
|
660
|
+
saveBtn.disabled = !(validAuto && validContext);
|
|
661
|
+
return !saveBtn.disabled;
|
|
662
|
+
};
|
|
663
|
+
for (const input of [fBackend, fModel, auto.input, auto.toggle, fCollab, fContext]) {
|
|
664
|
+
input.addEventListener("input", validate); input.addEventListener("change", validate);
|
|
665
|
+
}
|
|
666
|
+
saveBtn.onclick = () => {
|
|
667
|
+
if (!validate()) return;
|
|
668
|
+
const patch = {
|
|
669
|
+
backend: fBackend.value,
|
|
670
|
+
model: fModel.value.trim() || null,
|
|
671
|
+
auto_pause_after: auto.toggle.checked ? null : Number(auto.input.value),
|
|
672
|
+
collab: fCollab.checked,
|
|
673
|
+
context_lines: Number(fContext.value),
|
|
674
|
+
};
|
|
675
|
+
state.classic.channels[c.key] = { ...state.classic.channels[c.key], ...patch };
|
|
676
|
+
if (patch.model === null) delete state.classic.channels[c.key].model;
|
|
677
|
+
if (patch.auto_pause_after === null) delete state.classic.channels[c.key].auto_pause_after;
|
|
678
|
+
stageChange(`classic:${c.key}`, {
|
|
679
|
+
label: `Update ${c.name || c.instanceName}`,
|
|
680
|
+
impact: "instance",
|
|
681
|
+
apply: () => api(`/api/settings/classic/channels/${encodeURIComponent(c.key)}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
|
682
|
+
});
|
|
683
|
+
editingClassic = null;
|
|
684
|
+
renderClassic();
|
|
685
|
+
};
|
|
686
|
+
box.append(
|
|
687
|
+
el("div", { class: "sub", style: "margin:0 0 8px" }, `ID: ${c.instanceName} · ${EXECUTION_TEXT[runtime.execution] || runtime.execution}`),
|
|
688
|
+
el("div", { class: "grid2" },
|
|
689
|
+
el("div", {}, el("label", {}, t("backend"), impact("instance")), fBackend),
|
|
690
|
+
el("div", {}, el("label", {}, t("model"), impact("instance")), fModel),
|
|
691
|
+
el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), auto.row, autoFeedback),
|
|
692
|
+
el("div", {}, el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer;margin-top:28px" }, fCollab, el("span", {}, "Collaboration mode"), impact("instance")))),
|
|
693
|
+
el("div", { class: "advanced-only" },
|
|
694
|
+
el("label", {}, "Context lines", impact("instance")), fContext, contextFeedback),
|
|
695
|
+
el("div", { class: "actions" },
|
|
696
|
+
saveBtn,
|
|
697
|
+
runtime.status === "running" ? el("button", { class: "sm", onclick: () => pauseWakeAgent(c.instanceName, "pause") }, t("pause")) : null,
|
|
698
|
+
runtime.status === "paused" ? el("button", { class: "sm", onclick: () => pauseWakeAgent(c.instanceName, "wake") }, t("wake")) : null,
|
|
699
|
+
el("button", { class: "sm ghost", onclick: () => { editingClassic = null; renderClassic(); } }, t("cancel"))),
|
|
700
|
+
);
|
|
701
|
+
validate();
|
|
702
|
+
return box;
|
|
703
|
+
}
|
|
422
704
|
function renderClassic() {
|
|
423
705
|
const host = $("classicList"); if (!host) return; host.innerHTML = "";
|
|
424
|
-
const chans = Object.
|
|
706
|
+
const chans = Object.entries(state.classic?.channels || {}).map(([key, channel]) => ({ key, ...channel }));
|
|
425
707
|
if (!chans.length) { host.className = "card empty"; host.textContent = t("noClassic"); return; }
|
|
426
708
|
const filter = ($("agentSearch")?.value || "").trim().toLowerCase();
|
|
427
709
|
let list = chans;
|
|
@@ -429,19 +711,29 @@
|
|
|
429
711
|
if (!list.length) { host.className = "card empty"; host.textContent = "—"; return; }
|
|
430
712
|
host.className = "card";
|
|
431
713
|
list.sort((a, b) => {
|
|
432
|
-
const sa = STATUS_ORDER[
|
|
714
|
+
const sa = STATUS_ORDER[classicRuntime(a.instanceName).status] ?? 9;
|
|
715
|
+
const sb = STATUS_ORDER[classicRuntime(b.instanceName).status] ?? 9;
|
|
433
716
|
return sa !== sb ? sa - sb : String(a.name || "").localeCompare(String(b.name || ""));
|
|
434
717
|
});
|
|
435
718
|
for (const c of list) {
|
|
436
|
-
const
|
|
719
|
+
const runtime = classicRuntime(c.instanceName);
|
|
720
|
+
const st = runtime.status;
|
|
437
721
|
const adapter = c.adapterId || "discord";
|
|
438
|
-
|
|
439
|
-
|
|
722
|
+
const item = el("div", { class: "item" });
|
|
723
|
+
item.append(el("div", { class: "item-row" },
|
|
724
|
+
el("span", { class: "dot " + runtime.execution, title: EXECUTION_TEXT[runtime.execution] || runtime.execution }),
|
|
440
725
|
el("span", { class: "name", title: c.instanceName || "" }, c.name || c.instanceName || "?"),
|
|
441
|
-
el("span", { class: "sub", style: "margin:0" }, c.backend || state.classic?.defaults?.backend || "claude-code"),
|
|
726
|
+
el("span", { class: "sub", style: "margin:0" }, c.backend || state.classic?.defaults?.backend || state.fleet?.defaults?.backend || "claude-code"),
|
|
442
727
|
el("span", { class: "tag" + (adapter !== "discord" ? " persona" : "") }, adapter),
|
|
443
728
|
c.collab ? el("span", { class: "tag", style: "background:#ecfdf5;color:#065f46" }, "collab") : null,
|
|
444
|
-
|
|
729
|
+
el("span", { class: "spacer" }),
|
|
730
|
+
el("span", { class: "tag", title: STATUS_TEXT[st] || st }, EXECUTION_TEXT[runtime.execution] || runtime.execution),
|
|
731
|
+
el("button", { class: "sm ghost", onclick: () => { editingClassic = editingClassic === c.key ? null : c.key; renderClassic(); } }, editingClassic === c.key ? t("close") : t("edit")),
|
|
732
|
+
st === "running" ? el("button", { class: "sm", onclick: () => pauseWakeAgent(c.instanceName, "pause") }, t("pause")) : null,
|
|
733
|
+
st === "paused" ? el("button", { class: "sm", onclick: () => pauseWakeAgent(c.instanceName, "wake") }, t("wake")) : null,
|
|
734
|
+
));
|
|
735
|
+
if (editingClassic === c.key) item.append(classicEditForm(c));
|
|
736
|
+
host.append(item);
|
|
445
737
|
}
|
|
446
738
|
}
|
|
447
739
|
|
|
@@ -486,33 +778,106 @@
|
|
|
486
778
|
state.fleet.defaults = state.fleet.defaults || {};
|
|
487
779
|
state.classic.defaults = state.classic.defaults || {};
|
|
488
780
|
const d = state.fleet.defaults, c = state.classic.defaults;
|
|
781
|
+
const hang = d.hang_detector || {};
|
|
782
|
+
const startup = d.startup || {};
|
|
489
783
|
const primary = channels()[0];
|
|
490
784
|
if (primary) primary.access = primary.access || { mode: "locked", allowed_users: [] };
|
|
491
785
|
const allowedUsers = primary ? (primary.access.allowed_users = primary.access.allowed_users || []) : [];
|
|
492
786
|
c.admin_users = c.admin_users || []; c.allowed_guilds = c.allowed_guilds || [];
|
|
493
787
|
|
|
494
788
|
const fBackend = select(d.backend || "claude-code", BACKENDS);
|
|
789
|
+
const fClassicBackend = select(c.backend || d.backend || "claude-code", BACKENDS.filter(value => value !== "gemini-cli"));
|
|
495
790
|
const fModel = el("input", { type: "text", value: d.model || "", placeholder: "e.g. claude-opus-4-8" });
|
|
791
|
+
const fAgentMode = select(d.agent_mode || "mcp", ["mcp", "cli"]);
|
|
792
|
+
const fToolSet = select(d.tool_set || "full", ["full", "standard", "minimal"]);
|
|
793
|
+
const fLogLevel = select(d.log_level || "info", ["trace", "debug", "info", "warn", "error"]);
|
|
496
794
|
const fLocale = el("select");
|
|
795
|
+
const fAutoPause = el("input", { type: "number", min: "0", step: "1", value: d.auto_pause_after ?? 0, placeholder: "0 = disabled" });
|
|
796
|
+
const fHangEnabled = el("input", { type: "checkbox" }); fHangEnabled.checked = hang.enabled !== false;
|
|
797
|
+
const fHangTimeout = el("input", { type: "number", min: "0.1", step: "0.5", value: hang.timeout_minutes ?? 15 });
|
|
798
|
+
const fConcurrency = el("input", { type: "number", min: "1", max: "20", step: "1", value: startup.concurrency ?? 10 });
|
|
799
|
+
const fStagger = el("input", { type: "number", min: "0", max: "30000", step: "100", value: startup.stagger_delay_ms ?? 500 });
|
|
497
800
|
[["en", "English"], ["zh-TW", "繁體中文 (zh-TW)"]].forEach(([v, lbl]) => { const o = el("option", {}, lbl); o.value = v; if ((d.locale || "") === v) o.selected = true; fLocale.append(o); });
|
|
498
801
|
const msg = el("span", { class: "msg" });
|
|
802
|
+
const autoFeedback = el("div", { class: "feedback" });
|
|
803
|
+
const hangFeedback = el("div", { class: "feedback" });
|
|
804
|
+
const concurrencyFeedback = el("div", { class: "feedback" });
|
|
805
|
+
const staggerFeedback = el("div", { class: "feedback" });
|
|
806
|
+
const saveBtn = el("button", { class: "primary" }, t("save"));
|
|
807
|
+
const validate = () => {
|
|
808
|
+
const auto = Number(fAutoPause.value), timeout = Number(fHangTimeout.value);
|
|
809
|
+
const concurrency = Number(fConcurrency.value), stagger = Number(fStagger.value);
|
|
810
|
+
const validAuto = setValidation(fAutoPause, autoFeedback, !Number.isFinite(auto) || auto < 0 ? "Must be 0 or greater" : "");
|
|
811
|
+
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? "Must be greater than 0" : "");
|
|
812
|
+
const validConcurrency = setValidation(fConcurrency, concurrencyFeedback, !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 20 ? "Must be an integer from 1 to 20" : "", concurrency > 4 ? "May exceed CPU capacity on smaller hosts" : "");
|
|
813
|
+
const validStagger = setValidation(fStagger, staggerFeedback, !Number.isFinite(stagger) || stagger < 0 || stagger > 30000 ? "Must be between 0 and 30000" : "", stagger < 100 ? "Very low values can cause startup spikes" : "");
|
|
814
|
+
saveBtn.disabled = !(validAuto && validHang && validConcurrency && validStagger);
|
|
815
|
+
return !saveBtn.disabled;
|
|
816
|
+
};
|
|
817
|
+
for (const input of [fAutoPause, fHangTimeout, fConcurrency, fStagger]) {
|
|
818
|
+
input.addEventListener("input", validate); input.addEventListener("change", validate);
|
|
819
|
+
}
|
|
820
|
+
saveBtn.textContent = "Review changes";
|
|
821
|
+
saveBtn.onclick = () => {
|
|
822
|
+
if (!validate()) return;
|
|
823
|
+
const defaultsPatch = {
|
|
824
|
+
backend: fBackend.value,
|
|
825
|
+
model: fModel.value || undefined,
|
|
826
|
+
locale: fLocale.value,
|
|
827
|
+
auto_pause_after: Number(fAutoPause.value),
|
|
828
|
+
agent_mode: fAgentMode.value,
|
|
829
|
+
tool_set: fToolSet.value,
|
|
830
|
+
log_level: fLogLevel.value,
|
|
831
|
+
hang_detector: { enabled: fHangEnabled.checked, timeout_minutes: Number(fHangTimeout.value) },
|
|
832
|
+
startup: { concurrency: Number(fConcurrency.value), stagger_delay_ms: Number(fStagger.value) },
|
|
833
|
+
};
|
|
834
|
+
const classicPatch = { backend: fClassicBackend.value, admin_users: c.admin_users, allowed_guilds: c.allowed_guilds };
|
|
835
|
+
state.fleet.defaults = { ...d, ...defaultsPatch };
|
|
836
|
+
state.classic.defaults = { ...c, ...classicPatch };
|
|
837
|
+
stageChange("defaults:fleet", { label: "Update Agent defaults", impact: "instance", apply: () => api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) }) });
|
|
838
|
+
if (primary) stageChange("connections:primary", { label: "Update primary connection access", impact: "fleet", apply: () => api("/api/settings/fleet/channels", { method: "PUT", body: JSON.stringify(channels()) }) });
|
|
839
|
+
stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
|
|
840
|
+
msg.innerHTML = `<span class="s">Ready to apply</span>`;
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const section = (title, open, ...children) => {
|
|
844
|
+
const details = el("details", { class: "settings-section" }); details.open = open;
|
|
845
|
+
details.append(el("summary", {}, title), el("div", { class: "section-body" }, ...children));
|
|
846
|
+
return details;
|
|
847
|
+
};
|
|
499
848
|
host.append(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
849
|
+
section("Defaults", true,
|
|
850
|
+
el("div", { class: "grid2" },
|
|
851
|
+
el("div", { class: "field" }, el("label", {}, t("language"), impact("now")), fLocale),
|
|
852
|
+
el("div", { class: "field" }, el("label", {}, t("defaultBackend"), impact("instance")), fBackend),
|
|
853
|
+
el("div", { class: "field" }, el("label", {}, t("defaultModel"), impact("instance")), fModel),
|
|
854
|
+
el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), fAutoPause, autoFeedback))),
|
|
855
|
+
section("ClassicBot", true,
|
|
856
|
+
el("div", { class: "grid2" },
|
|
857
|
+
el("div", { class: "field" }, el("label", {}, "Classic default backend", impact("instance")), fClassicBackend)),
|
|
858
|
+
el("div", { class: "field" }, el("strong", {}, "Who can use ClassicBot"),
|
|
859
|
+
el("div", { class: "grid2" },
|
|
860
|
+
el("div", {}, el("label", {}, t("classicAdmins"), impact("fleet")), chipList(c.admin_users, null, "user id")),
|
|
861
|
+
el("div", {}, el("label", {}, t("allowedGuilds"), impact("fleet")), chipList(c.allowed_guilds, null, "guild id"))))),
|
|
862
|
+
el("div", { class: "advanced-only" },
|
|
863
|
+
section(t("runtimeResources"), false,
|
|
864
|
+
el("div", { class: "grid2" },
|
|
865
|
+
el("div", { class: "field" }, el("label", {}, t("toolSet"), impact("instance")), fToolSet),
|
|
866
|
+
el("div", { class: "field" }, el("label", {}, t("logLevel"), impact("instance")), fLogLevel),
|
|
867
|
+
el("div", { class: "field" }, el("label", {}, t("agentMode"), impact("instance")), fAgentMode))),
|
|
868
|
+
section(t("detection"), false,
|
|
869
|
+
el("div", { class: "grid2" },
|
|
870
|
+
el("div", { class: "field" }, el("label", { style: "display:flex;align-items:center;gap:8px" }, fHangEnabled, el("span", {}, t("hangEnabled")), impact("instance"))),
|
|
871
|
+
el("div", { class: "field" }, el("label", {}, `${t("hangTimeout")} (${t("minutes")})`, impact("instance")), fHangTimeout, hangFeedback))),
|
|
872
|
+
section(t("startup"), false,
|
|
873
|
+
el("div", { class: "grid2" },
|
|
874
|
+
el("div", { class: "field" }, el("label", {}, t("concurrency"), impact("fleet")), fConcurrency, concurrencyFeedback),
|
|
875
|
+
el("div", { class: "field" }, el("label", {}, `${t("staggerDelay")} (ms)`, impact("fleet")), fStagger, staggerFeedback))),
|
|
876
|
+
section("Advanced & Access", false,
|
|
877
|
+
el("div", { class: "field" }, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(allowedUsers, null, "user id")))),
|
|
878
|
+
el("div", { class: "field" }, el("div", { class: "actions" }, saveBtn, msg)),
|
|
515
879
|
);
|
|
880
|
+
validate();
|
|
516
881
|
}
|
|
517
882
|
|
|
518
883
|
// ── What's New ──
|
|
@@ -576,20 +941,25 @@
|
|
|
576
941
|
}
|
|
577
942
|
|
|
578
943
|
// ── Load / render ──
|
|
579
|
-
function renderAll() { renderAgents(); renderClassic(); renderBots(); renderGeneral();
|
|
944
|
+
function renderAll() { renderAgents(); renderClassic(); renderBots(); renderGeneral(); renderAdvanced(); applyStaticI18n(); }
|
|
580
945
|
async function reloadLive() {
|
|
581
946
|
const f = await api("/api/fleet");
|
|
582
947
|
state.fleetUp = f.ok;
|
|
583
|
-
state.live = {};
|
|
584
|
-
|
|
585
|
-
|
|
948
|
+
state.live = {}; state.classicLive = {};
|
|
949
|
+
if (f.ok && f.body?.instances) for (const i of f.body.instances) {
|
|
950
|
+
state.live[i.name] = { status: i.status, state: i.state };
|
|
951
|
+
if (i.classic) state.classicLive[i.name] = { status: i.status, state: i.state };
|
|
952
|
+
}
|
|
953
|
+
// /api/fleet carries live tri-state for Classic instances. Profiles remain
|
|
954
|
+
// a fallback roster for a daemon that has not yet appeared in live status.
|
|
586
955
|
const pr = await api("/api/profiles");
|
|
587
956
|
state.classicStatus = {}; if (pr.ok && Array.isArray(pr.body)) for (const r of pr.body) state.classicStatus[r.instance_name] = r.status;
|
|
588
957
|
if (f.ok && f.body?.version) { state.version = f.body.version; $("verBadge").textContent = "v" + state.version; }
|
|
589
958
|
renderAgents(); renderBots(); renderClassic();
|
|
590
959
|
}
|
|
591
960
|
async function reload() {
|
|
592
|
-
|
|
961
|
+
// Forms edit the user-authored config, not defaults-expanded runtime values.
|
|
962
|
+
const f = await api("/api/settings/fleet/raw");
|
|
593
963
|
if (!f.ok) { $("banner").style.display = "block"; $("banner").style.background = "#fef2f2"; $("banner").style.color = "var(--red)"; $("banner").textContent = "Failed to load config — check the token in the URL."; return; }
|
|
594
964
|
state.fleet = f.body || {};
|
|
595
965
|
const c = await api("/api/settings/classic"); state.classic = c.body || {};
|
|
@@ -602,13 +972,22 @@
|
|
|
602
972
|
$("helpClose").onclick = () => setHelp(false);
|
|
603
973
|
$("helpBackdrop").onclick = () => setHelp(false);
|
|
604
974
|
$("langBtn").onclick = () => { lang = lang === "en" ? "zh-TW" : "en"; localStorage.setItem("agend_lang", lang); renderAll(); };
|
|
605
|
-
$("verBadge").onclick = () =>
|
|
975
|
+
$("verBadge").onclick = () => window.open("https://github.com/songsid/AgEnD/releases/latest", "_blank", "noopener");
|
|
606
976
|
$("agentSearch").oninput = () => { renderAgents(); renderClassic(); };
|
|
607
977
|
$("newAgentBtn").onclick = () => { state.newAgentOpen = !state.newAgentOpen; $("newAgentBtn").textContent = state.newAgentOpen ? t("cancel") : t("new"); renderNewAgent(state.newAgentOpen); };
|
|
608
978
|
$("newBotBtn").onclick = () => { state.newBotOpen = !state.newBotOpen; $("newBotBtn").textContent = state.newBotOpen ? t("cancel") : t("add"); renderNewBot(state.newBotOpen); };
|
|
609
979
|
$("advToggle").onclick = () => { const b = $("advBody"); const open = !b.classList.contains("open"); b.classList.toggle("open", open); $("advToggle").textContent = open ? t("collapse") : t("expand"); };
|
|
610
|
-
|
|
611
|
-
|
|
980
|
+
const advancedEnabled = localStorage.getItem("agend_settings_advanced") === "true";
|
|
981
|
+
document.body.classList.toggle("show-advanced", advancedEnabled);
|
|
982
|
+
$("advancedToggle").textContent = advancedEnabled ? "Hide advanced" : "Show advanced";
|
|
983
|
+
$("advancedToggle").onclick = () => {
|
|
984
|
+
const enabled = !document.body.classList.contains("show-advanced");
|
|
985
|
+
document.body.classList.toggle("show-advanced", enabled);
|
|
986
|
+
localStorage.setItem("agend_settings_advanced", String(enabled));
|
|
987
|
+
$("advancedToggle").textContent = enabled ? "Hide advanced" : "Show advanced";
|
|
988
|
+
};
|
|
989
|
+
$("applyChanges").onclick = applyPendingChanges;
|
|
990
|
+
$("discardChanges").onclick = async () => { state.pending.clear(); updatePendingBar(); await reload(); };
|
|
612
991
|
|
|
613
992
|
reload();
|
|
614
993
|
})();
|