@songsid/agend 2.1.0-beta.2 → 2.1.0-beta.21
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-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/factory.js +5 -1
- package/dist/backend/factory.js.map +1 -1
- package/dist/backend/grok.d.ts +41 -0
- package/dist/backend/grok.js +256 -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/classic-channel-manager.js +1 -1
- package/dist/cli.js +56 -6
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +50 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +13 -0
- 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 +51 -3
- package/dist/daemon.js +380 -36
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -1
- package/dist/fleet-manager.d.ts +23 -5
- package/dist/fleet-manager.js +229 -123
- 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 +6 -1
- package/dist/instance-lifecycle.js +46 -3
- package/dist/instance-lifecycle.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 +5 -0
- package/dist/outbound-handlers.js +68 -16
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -0
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/settings-api.d.ts +17 -2
- package/dist/settings-api.js +105 -8
- 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-manager.d.ts +3 -1
- package/dist/tmux-manager.js +9 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-archiver.d.ts +1 -1
- package/dist/topic-commands.d.ts +14 -1
- package/dist/topic-commands.js +96 -25
- 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 +18 -1
- package/dist/ui/dashboard.html +2 -2
- package/dist/ui/settings.html +251 -40
- package/dist/view-api.d.ts +1 -1
- package/dist/web-api.d.ts +2 -1
- package/dist/web-api.js +24 -15
- package/dist/web-api.js.map +1 -1
- package/package.json +2 -1
package/dist/ui/settings.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
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
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: 60px; }
|
|
@@ -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 { background: var(--green); } .dot.paused { background:#f59e0b; } .dot.crashed { 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,6 +63,19 @@
|
|
|
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; }
|
|
68
81
|
.toolbar { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
|
@@ -84,6 +97,16 @@
|
|
|
84
97
|
.help-panel li { font-size: 13px; margin: 3px 0; }
|
|
85
98
|
.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
99
|
.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; }
|
|
100
|
+
@media (max-width: 640px) {
|
|
101
|
+
header { padding:10px 12px; flex-wrap:wrap; } header h1 { font-size:16px; }
|
|
102
|
+
main { padding:12px 10px; } section { margin-bottom:18px; }
|
|
103
|
+
.grid2 { grid-template-columns:1fr; gap:0; }
|
|
104
|
+
.item { padding:9px 10px; } .item-row { flex-wrap:wrap; }
|
|
105
|
+
.item-row .spacer { flex-basis:100%; height:0; }
|
|
106
|
+
.sub { margin-left:0; } .field { padding:10px 12px; }
|
|
107
|
+
#agentSearch { width:130px !important; margin-right:0 !important; }
|
|
108
|
+
button { min-height:36px; } button.sm { min-height:32px; }
|
|
109
|
+
}
|
|
87
110
|
</style>
|
|
88
111
|
</head>
|
|
89
112
|
<body>
|
|
@@ -141,7 +164,7 @@
|
|
|
141
164
|
const $ = (id) => document.getElementById(id);
|
|
142
165
|
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
166
|
const esc = (s) => String(s).replace(/[&<>]/g, c => ({ "&":"&","<":"<",">":">" }[c]));
|
|
144
|
-
const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "gemini-cli", "antigravity"];
|
|
167
|
+
const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "gemini-cli", "antigravity", { value: "grok", label: "grok (experimental)" }];
|
|
145
168
|
|
|
146
169
|
// ── i18n ──
|
|
147
170
|
const I18N = {
|
|
@@ -152,6 +175,8 @@
|
|
|
152
175
|
edit: "Edit", close: "Close", save: "Save", cancel: "Cancel", create: "Create", start: "Start", stop: "Stop",
|
|
153
176
|
backend: "Backend", model: "Model", workingDir: "Working directory", channelBinding: "Channel binding",
|
|
154
177
|
description: "Description", systemPrompt: "System prompt", tags: "Tags", generalTopicField: "General topic (fleet dispatcher)",
|
|
178
|
+
runtimeResources: "Runtime & Resources", autoPause: "Auto-pause after", minutes: "minutes", inheritDefault: "Inherit from defaults",
|
|
179
|
+
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
180
|
language: "Language (bot replies)", defaultBackend: "Default backend", defaultModel: "Default model", allowedUsers: "Allowed users (primary bot)",
|
|
156
181
|
classicAdmins: "ClassicBot admins", allowedGuilds: "Allowed guilds", type: "Type", adapterId: "Adapter id",
|
|
157
182
|
tokenEnv: "Bot token env var", groupId: "Group / guild id", nameField: "Name",
|
|
@@ -168,6 +193,8 @@
|
|
|
168
193
|
edit: "編輯", close: "關閉", save: "儲存", cancel: "取消", create: "建立", start: "啟動", stop: "停止",
|
|
169
194
|
backend: "後端", model: "模型", workingDir: "工作目錄", channelBinding: "頻道綁定",
|
|
170
195
|
description: "描述", systemPrompt: "System prompt", tags: "標籤", generalTopicField: "General topic(fleet 分派器)",
|
|
196
|
+
runtimeResources: "執行環境與資源", autoPause: "閒置後自動暫停", minutes: "分鐘", inheritDefault: "沿用全域預設值",
|
|
197
|
+
detection: "偵測", startup: "啟動", backendSection: "後端", advancedSection: "進階與存取", hangEnabled: "啟用卡住偵測", hangTimeout: "卡住逾時", agentMode: "Agent 模式", toolSet: "工具集", logLevel: "Log 等級", concurrency: "啟動併發數", staggerDelay: "錯開延遲", lightweight: "輕量模式", modelFailover: "備援模型", displayName: "顯示名稱", pause: "暫停", wake: "喚醒", accessMode: "存取模式", affected: "受影響 instances",
|
|
171
198
|
language: "語言(bot 回覆)", defaultBackend: "預設後端", defaultModel: "預設模型", allowedUsers: "允許使用者(主 bot)",
|
|
172
199
|
classicAdmins: "ClassicBot 管理員", allowedGuilds: "允許的伺服器", type: "類型", adapterId: "Adapter id",
|
|
173
200
|
tokenEnv: "Bot token 環境變數", groupId: "群組 / guild id", nameField: "名稱",
|
|
@@ -251,6 +278,14 @@
|
|
|
251
278
|
else if (!html) html = `<span class="e">✗ ${esc((b && b.error) || ("HTTP " + res.status))}</span>`;
|
|
252
279
|
elm.innerHTML = html;
|
|
253
280
|
}
|
|
281
|
+
function showBanner(text, error = false) {
|
|
282
|
+
const b = $("banner");
|
|
283
|
+
b.style.display = "block";
|
|
284
|
+
b.style.background = error ? "#fef2f2" : "#ecfdf5";
|
|
285
|
+
b.style.color = error ? "var(--red)" : "var(--green)";
|
|
286
|
+
b.textContent = text;
|
|
287
|
+
setTimeout(() => { b.style.display = "none"; }, 6000);
|
|
288
|
+
}
|
|
254
289
|
|
|
255
290
|
// ── State ──
|
|
256
291
|
const state = { fleet: {}, classic: {}, classicStatus: {}, live: {}, fleetUp: false, advFmt: "yaml", advEdit: false, collapsedGroups: new Set(), version: "", newAgentOpen: false, newBotOpen: false };
|
|
@@ -268,7 +303,26 @@
|
|
|
268
303
|
box.append(chips, el("div", { class: "chip-add" }, inp, el("button", { class: "sm", onclick: add }, "+ Add")));
|
|
269
304
|
redraw(); return box;
|
|
270
305
|
}
|
|
271
|
-
function select(value, options) { const s = el("select"); for (const o of options) { const opt = el("option", {},
|
|
306
|
+
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; }
|
|
307
|
+
const impact = (kind) => el("span", { class: "impact" }, kind === "now" ? "⚡ immediate" : kind === "fleet" ? "🔄🔄 restart fleet" : "🔄 restart instance");
|
|
308
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
309
|
+
function overrideControl(inst, key, input, inheritedValue, impactKind = "instance") {
|
|
310
|
+
const inherited = !hasOwn(inst, key);
|
|
311
|
+
const toggle = el("input", { type: "checkbox" }); toggle.checked = inherited;
|
|
312
|
+
input.disabled = inherited;
|
|
313
|
+
if (inherited && inheritedValue !== undefined) {
|
|
314
|
+
if (input.type === "checkbox") input.checked = !!inheritedValue;
|
|
315
|
+
else input.value = inheritedValue;
|
|
316
|
+
}
|
|
317
|
+
toggle.onchange = () => { input.disabled = toggle.checked; };
|
|
318
|
+
return { input, toggle, row: el("div", {}, input, el("label", { class: "inherit-row" }, toggle, el("span", {}, t("inheritDefault")), impact(impactKind))) };
|
|
319
|
+
}
|
|
320
|
+
function setValidation(input, feedback, error, warning) {
|
|
321
|
+
input.classList.toggle("invalid", !!error);
|
|
322
|
+
feedback.className = "feedback" + (error ? " error" : warning ? " warning" : "");
|
|
323
|
+
feedback.textContent = error ? `✗ ${error}` : warning ? `⚠ ${warning}` : "";
|
|
324
|
+
return !error;
|
|
325
|
+
}
|
|
272
326
|
|
|
273
327
|
// ── My Agents ──
|
|
274
328
|
let editing = null;
|
|
@@ -281,12 +335,13 @@
|
|
|
281
335
|
}
|
|
282
336
|
// Display name: profile display_name → else strip the -t<topicId> suffix.
|
|
283
337
|
const shortName = (name, inst) => (inst && inst.display_name) || name.replace(/-t\d+$/, "");
|
|
284
|
-
const STATUS_TEXT = { running: "🟢 Running", crashed: "🔴 Crashed", stopped: "⚪ Stopped" };
|
|
285
|
-
const STATUS_ORDER = { running: 0,
|
|
338
|
+
const STATUS_TEXT = { running: "🟢 Running", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped" };
|
|
339
|
+
const STATUS_ORDER = { running: 0, paused: 1, crashed: 2, stopped: 3 };
|
|
286
340
|
const groupOf = (inst) => (inst.tags && inst.tags[0]) || "Other";
|
|
287
341
|
function compactRow(name, inst) {
|
|
288
342
|
const st = state.live[name]?.status || "stopped";
|
|
289
343
|
const running = st === "running";
|
|
344
|
+
const paused = st === "paused";
|
|
290
345
|
const short = shortName(name, inst);
|
|
291
346
|
const chTag = agentChannelLabel(inst);
|
|
292
347
|
const item = el("div", { class: "item" });
|
|
@@ -299,7 +354,9 @@
|
|
|
299
354
|
chTag ? el("span", { class: "tag" + (chTag.includes("persona") ? " persona" : chTag.includes("telegram") ? " tg" : "") }, chTag) : null,
|
|
300
355
|
el("span", { class: "spacer" }),
|
|
301
356
|
el("button", { class: "sm ghost", onclick: () => { editing = editing === name ? null : name; renderAgents(); } }, editing === name ? t("close") : t("edit")),
|
|
302
|
-
el("button", { class: "sm", onclick: () =>
|
|
357
|
+
running ? el("button", { class: "sm", onclick: () => pauseWakeAgent(name, "pause") }, t("pause")) : null,
|
|
358
|
+
paused ? el("button", { class: "sm", onclick: () => pauseWakeAgent(name, "wake") }, t("wake")) : null,
|
|
359
|
+
!paused ? el("button", { class: "sm", onclick: () => toggleAgent(name, running) }, running ? t("stop") : t("start")) : null,
|
|
303
360
|
el("button", { class: "sm danger del-btn", title: t("close"), onclick: () => delAgent(name) }, "🗑️"),
|
|
304
361
|
));
|
|
305
362
|
if (editing === name) item.append(agentEditForm(name, inst));
|
|
@@ -333,6 +390,7 @@
|
|
|
333
390
|
}
|
|
334
391
|
function agentEditForm(name, inst) {
|
|
335
392
|
const box = el("div", { class: "edit" });
|
|
393
|
+
const defaults = state.fleet.defaults || {};
|
|
336
394
|
const fBackend = select(inst.backend || state.fleet.defaults?.backend || "claude-code", BACKENDS);
|
|
337
395
|
const fModel = el("input", { type: "text", value: inst.model || "", placeholder: "(inherit default)" });
|
|
338
396
|
const fWd = el("input", { type: "text", value: inst.working_directory || "" });
|
|
@@ -341,29 +399,77 @@
|
|
|
341
399
|
const fSys = el("input", { type: "text", value: inst.systemPrompt || "", placeholder: "e.g. file:prompts/dev.md" });
|
|
342
400
|
const tags = [...(inst.tags || [])];
|
|
343
401
|
const fGeneral = el("input", { type: "checkbox" }); fGeneral.checked = !!inst.general_topic;
|
|
402
|
+
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);
|
|
403
|
+
const hangRaw = inst.hang_detector || {};
|
|
404
|
+
const hangHolder = hasOwn(hangRaw, "timeout_minutes") ? { value: hangRaw.timeout_minutes } : {};
|
|
405
|
+
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);
|
|
406
|
+
const agentMode = overrideControl(inst, "agent_mode", select(inst.agent_mode || defaults.agent_mode || "mcp", ["mcp", "cli"]), defaults.agent_mode || "mcp");
|
|
407
|
+
const toolSet = overrideControl(inst, "tool_set", select(inst.tool_set || defaults.tool_set || "full", ["full", "standard", "minimal"]), defaults.tool_set || "full");
|
|
408
|
+
const logLevel = overrideControl(inst, "log_level", select(inst.log_level || defaults.log_level || "info", ["trace", "debug", "info", "warn", "error"]), defaults.log_level || "info");
|
|
409
|
+
const fLightweight = el("input", { type: "checkbox" }); fLightweight.checked = inst.lightweight ?? defaults.lightweight ?? false;
|
|
410
|
+
const lightweight = overrideControl(inst, "lightweight", fLightweight, defaults.lightweight ?? false);
|
|
411
|
+
const fallbackValue = Array.isArray(inst.model_failover) ? (inst.model_failover[0] || "") : (defaults.model_failover?.[0] || "");
|
|
412
|
+
const failover = overrideControl(inst, "model_failover", el("input", { type: "text", value: fallbackValue, placeholder: "e.g. sonnet" }), defaults.model_failover?.[0] || "");
|
|
413
|
+
const displayName = overrideControl(inst, "display_name", el("input", { type: "text", value: inst.display_name ?? defaults.display_name ?? "", placeholder: name }), defaults.display_name || "");
|
|
414
|
+
const autoFeedback = el("div", { class: "feedback" });
|
|
415
|
+
const hangFeedback = el("div", { class: "feedback" });
|
|
416
|
+
const failoverFeedback = el("div", { class: "feedback" });
|
|
344
417
|
const msg = el("span", { class: "msg" });
|
|
418
|
+
const saveBtn = el("button", { class: "primary sm" }, t("save"));
|
|
419
|
+
const validate = () => {
|
|
420
|
+
const autoValue = Number(auto.input.value), hangValue = Number(hang.input.value);
|
|
421
|
+
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? "Must be 0 or greater" : "");
|
|
422
|
+
const validHang = setValidation(hang.input, hangFeedback, !hang.toggle.checked && (!Number.isFinite(hangValue) || hangValue <= 0) ? "Must be greater than 0" : "");
|
|
423
|
+
setValidation(failover.input, failoverFeedback, "", !failover.toggle.checked && !failover.input.value.trim() ? "Empty list disables failover" : "");
|
|
424
|
+
saveBtn.disabled = !(validAuto && validHang);
|
|
425
|
+
return !saveBtn.disabled;
|
|
426
|
+
};
|
|
427
|
+
for (const ctl of [auto, hang, agentMode, toolSet, logLevel, lightweight, failover, displayName]) {
|
|
428
|
+
ctl.input.addEventListener("input", validate); ctl.input.addEventListener("change", validate); ctl.toggle.addEventListener("change", validate);
|
|
429
|
+
}
|
|
430
|
+
saveBtn.onclick = async () => {
|
|
431
|
+
if (!validate()) return;
|
|
432
|
+
const patch = {
|
|
433
|
+
backend: fBackend.value, model: fModel.value || undefined,
|
|
434
|
+
working_directory: fWd.value, channel_id: fChan.value || undefined,
|
|
435
|
+
description: fDesc.value || undefined, systemPrompt: fSys.value || undefined,
|
|
436
|
+
tags: tags.length ? tags : undefined, general_topic: fGeneral.checked,
|
|
437
|
+
auto_pause_after: auto.toggle.checked ? null : Number(auto.input.value),
|
|
438
|
+
hang_detector: { timeout_minutes: hang.toggle.checked ? null : Number(hang.input.value) },
|
|
439
|
+
agent_mode: agentMode.toggle.checked ? null : agentMode.input.value,
|
|
440
|
+
tool_set: toolSet.toggle.checked ? null : toolSet.input.value,
|
|
441
|
+
log_level: logLevel.toggle.checked ? null : logLevel.input.value,
|
|
442
|
+
lightweight: lightweight.toggle.checked ? null : lightweight.input.checked,
|
|
443
|
+
model_failover: failover.toggle.checked ? null : (failover.input.value.trim() ? [failover.input.value.trim()] : []),
|
|
444
|
+
display_name: displayName.toggle.checked ? null : displayName.input.value.trim(),
|
|
445
|
+
};
|
|
446
|
+
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
447
|
+
resultMsg(msg, res); if (res.ok) { await reload(); showBanner(`✓ ${t("affected")}: ${name} — 🔄 restart instance`); }
|
|
448
|
+
};
|
|
345
449
|
box.append(
|
|
346
450
|
el("div", { class: "sub", style: "margin:0 0 8px" }, "ID: " + name),
|
|
347
|
-
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("backend")), fBackend), el("div", {}, el("label", {}, t("model")), fModel)),
|
|
348
|
-
el("label", {}, t("workingDir")), fWd,
|
|
349
|
-
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("channelBinding")), fChan), el("div", {}, el("label", {}, t("description")), fDesc)),
|
|
350
|
-
el("label", {}, t("systemPrompt")), fSys,
|
|
351
|
-
el("label", {}, t("tags")), chipList(tags, null, "tag"),
|
|
352
|
-
el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer" }, fGeneral, el("span", {}, t("generalTopicField"))),
|
|
451
|
+
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("backend"), impact("instance")), fBackend), el("div", {}, el("label", {}, t("model"), impact("instance")), fModel)),
|
|
452
|
+
el("label", {}, t("workingDir"), impact("instance")), fWd,
|
|
453
|
+
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("channelBinding"), impact("fleet")), fChan), el("div", {}, el("label", {}, t("description"), impact("now")), fDesc)),
|
|
454
|
+
el("label", {}, t("systemPrompt"), impact("instance")), fSys,
|
|
455
|
+
el("label", {}, t("tags"), impact("now")), chipList(tags, null, "tag"),
|
|
456
|
+
el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer" }, fGeneral, el("span", {}, t("generalTopicField")), impact("fleet")),
|
|
457
|
+
el("div", { class: "sub", style: "margin:14px 0 6px;font-weight:600" }, t("runtimeResources")),
|
|
458
|
+
el("div", { class: "grid2" },
|
|
459
|
+
el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`), auto.row, autoFeedback),
|
|
460
|
+
el("div", {}, el("label", {}, `${t("hangTimeout")} (${t("minutes")})`), hang.row, hangFeedback),
|
|
461
|
+
el("div", {}, el("label", {}, t("agentMode")), agentMode.row),
|
|
462
|
+
el("div", {}, el("label", {}, t("toolSet")), toolSet.row),
|
|
463
|
+
el("div", {}, el("label", {}, t("logLevel")), logLevel.row),
|
|
464
|
+
el("div", {}, el("label", {}, t("lightweight")), lightweight.row),
|
|
465
|
+
el("div", {}, el("label", {}, t("modelFailover")), failover.row, failoverFeedback),
|
|
466
|
+
el("div", {}, el("label", {}, t("displayName")), displayName.row)),
|
|
353
467
|
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")),
|
|
468
|
+
saveBtn,
|
|
364
469
|
el("button", { class: "sm ghost", onclick: () => { editing = null; renderAgents(); } }, t("cancel")),
|
|
365
470
|
msg),
|
|
366
471
|
);
|
|
472
|
+
validate();
|
|
367
473
|
return box;
|
|
368
474
|
}
|
|
369
475
|
async function toggleAgent(name, running) {
|
|
@@ -371,6 +477,11 @@
|
|
|
371
477
|
else await api(`/api/instance/${encodeURIComponent(name)}/start`, { method: "POST" });
|
|
372
478
|
setTimeout(reloadLive, 800);
|
|
373
479
|
}
|
|
480
|
+
async function pauseWakeAgent(name, action) {
|
|
481
|
+
const res = await api(`/api/settings/instances/${encodeURIComponent(name)}/${action}`, { method: "POST" });
|
|
482
|
+
if (!res.ok) alert(res.body?.error || `${action} failed`);
|
|
483
|
+
await reloadLive();
|
|
484
|
+
}
|
|
374
485
|
async function delAgent(name) {
|
|
375
486
|
if (!confirm(`Delete agent "${name}"? (config removed; running daemon stops on next Reload)`)) return;
|
|
376
487
|
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
@@ -399,6 +510,7 @@
|
|
|
399
510
|
}
|
|
400
511
|
|
|
401
512
|
// ── Bots ──
|
|
513
|
+
let editingBot = null;
|
|
402
514
|
function renderBots() {
|
|
403
515
|
const host = $("bots"); host.innerHTML = ""; const chs = channels();
|
|
404
516
|
if (!chs.length) { host.className = "card empty"; host.textContent = t("noBots"); return; }
|
|
@@ -412,12 +524,41 @@
|
|
|
412
524
|
el("span", { class: "sub", style: "margin:0" }, ch.bot_token_env || "(no token env)"),
|
|
413
525
|
el("span", { class: "spacer" }),
|
|
414
526
|
el("span", { style: "color:var(--green);font-size:13px" }, state.fleetUp ? t("online") : "—"),
|
|
527
|
+
el("button", { class: "sm ghost", onclick: () => { editingBot = editingBot === i ? null : i; renderBots(); } }, editingBot === i ? t("close") : t("edit")),
|
|
415
528
|
el("button", { class: "sm danger", onclick: () => delBot(i) }, "🗑️"),
|
|
416
529
|
));
|
|
530
|
+
if (editingBot === i) item.append(botEditForm(ch, i));
|
|
417
531
|
host.append(item);
|
|
418
532
|
});
|
|
419
533
|
}
|
|
420
534
|
|
|
535
|
+
function botEditForm(ch, i) {
|
|
536
|
+
ch.access = ch.access || { mode: "locked", allowed_users: [] };
|
|
537
|
+
ch.access.allowed_users = ch.access.allowed_users || [];
|
|
538
|
+
const box = el("div", { class: "edit" });
|
|
539
|
+
const fMode = select(ch.access.mode || "locked", ["open", "locked", "pairing"]);
|
|
540
|
+
const users = [...ch.access.allowed_users];
|
|
541
|
+
const msg = el("span", { class: "msg" });
|
|
542
|
+
box.append(
|
|
543
|
+
el("div", { class: "grid2" },
|
|
544
|
+
el("div", {}, el("label", {}, t("accessMode"), impact("fleet")), fMode),
|
|
545
|
+
el("div", {}, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(users, null, "user id"))),
|
|
546
|
+
el("div", { class: "actions" },
|
|
547
|
+
el("button", { class: "primary sm", onclick: async () => {
|
|
548
|
+
const chs = channels();
|
|
549
|
+
chs[i] = { ...chs[i], access: { ...chs[i].access, mode: fMode.value, allowed_users: users } };
|
|
550
|
+
const res = await saveChannels(msg);
|
|
551
|
+
if (res.ok) {
|
|
552
|
+
const affected = Object.keys(state.fleet.instances || {});
|
|
553
|
+
editingBot = null; await reload();
|
|
554
|
+
showBanner(`✓ ${t("affected")}: ${affected.length ? affected.join(", ") : chLabel(i) + " bot"} — 🔄🔄 restart fleet`);
|
|
555
|
+
}
|
|
556
|
+
} }, t("save")),
|
|
557
|
+
el("button", { class: "sm ghost", onclick: () => { editingBot = null; renderBots(); } }, t("cancel")), msg),
|
|
558
|
+
);
|
|
559
|
+
return box;
|
|
560
|
+
}
|
|
561
|
+
|
|
421
562
|
// ── ClassicBot Channels (read-only; created dynamically via /start /stop) ──
|
|
422
563
|
function renderClassic() {
|
|
423
564
|
const host = $("classicList"); if (!host) return; host.innerHTML = "";
|
|
@@ -486,6 +627,8 @@
|
|
|
486
627
|
state.fleet.defaults = state.fleet.defaults || {};
|
|
487
628
|
state.classic.defaults = state.classic.defaults || {};
|
|
488
629
|
const d = state.fleet.defaults, c = state.classic.defaults;
|
|
630
|
+
const hang = d.hang_detector || {};
|
|
631
|
+
const startup = d.startup || {};
|
|
489
632
|
const primary = channels()[0];
|
|
490
633
|
if (primary) primary.access = primary.access || { mode: "locked", allowed_users: [] };
|
|
491
634
|
const allowedUsers = primary ? (primary.access.allowed_users = primary.access.allowed_users || []) : [];
|
|
@@ -493,26 +636,93 @@
|
|
|
493
636
|
|
|
494
637
|
const fBackend = select(d.backend || "claude-code", BACKENDS);
|
|
495
638
|
const fModel = el("input", { type: "text", value: d.model || "", placeholder: "e.g. claude-opus-4-8" });
|
|
639
|
+
const fAgentMode = select(d.agent_mode || "mcp", ["mcp", "cli"]);
|
|
640
|
+
const fToolSet = select(d.tool_set || "full", ["full", "standard", "minimal"]);
|
|
641
|
+
const fLogLevel = select(d.log_level || "info", ["trace", "debug", "info", "warn", "error"]);
|
|
496
642
|
const fLocale = el("select");
|
|
643
|
+
const fAutoPause = el("input", { type: "number", min: "0", step: "1", value: d.auto_pause_after ?? 0, placeholder: "0 = disabled" });
|
|
644
|
+
const fHangEnabled = el("input", { type: "checkbox" }); fHangEnabled.checked = hang.enabled !== false;
|
|
645
|
+
const fHangTimeout = el("input", { type: "number", min: "0.1", step: "0.5", value: hang.timeout_minutes ?? 15 });
|
|
646
|
+
const fConcurrency = el("input", { type: "number", min: "1", max: "20", step: "1", value: startup.concurrency ?? 10 });
|
|
647
|
+
const fStagger = el("input", { type: "number", min: "0", max: "30000", step: "100", value: startup.stagger_delay_ms ?? 500 });
|
|
497
648
|
[["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
649
|
const msg = el("span", { class: "msg" });
|
|
650
|
+
const autoFeedback = el("div", { class: "feedback" });
|
|
651
|
+
const hangFeedback = el("div", { class: "feedback" });
|
|
652
|
+
const concurrencyFeedback = el("div", { class: "feedback" });
|
|
653
|
+
const staggerFeedback = el("div", { class: "feedback" });
|
|
654
|
+
const saveBtn = el("button", { class: "primary" }, t("save"));
|
|
655
|
+
const validate = () => {
|
|
656
|
+
const auto = Number(fAutoPause.value), timeout = Number(fHangTimeout.value);
|
|
657
|
+
const concurrency = Number(fConcurrency.value), stagger = Number(fStagger.value);
|
|
658
|
+
const validAuto = setValidation(fAutoPause, autoFeedback, !Number.isFinite(auto) || auto < 0 ? "Must be 0 or greater" : "");
|
|
659
|
+
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? "Must be greater than 0" : "");
|
|
660
|
+
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" : "");
|
|
661
|
+
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" : "");
|
|
662
|
+
saveBtn.disabled = !(validAuto && validHang && validConcurrency && validStagger);
|
|
663
|
+
return !saveBtn.disabled;
|
|
664
|
+
};
|
|
665
|
+
for (const input of [fAutoPause, fHangTimeout, fConcurrency, fStagger]) {
|
|
666
|
+
input.addEventListener("input", validate); input.addEventListener("change", validate);
|
|
667
|
+
}
|
|
668
|
+
saveBtn.onclick = async () => {
|
|
669
|
+
if (!validate()) return;
|
|
670
|
+
const defaultsPatch = {
|
|
671
|
+
backend: fBackend.value,
|
|
672
|
+
model: fModel.value || undefined,
|
|
673
|
+
locale: fLocale.value,
|
|
674
|
+
auto_pause_after: Number(fAutoPause.value),
|
|
675
|
+
agent_mode: fAgentMode.value,
|
|
676
|
+
tool_set: fToolSet.value,
|
|
677
|
+
log_level: fLogLevel.value,
|
|
678
|
+
hang_detector: { enabled: fHangEnabled.checked, timeout_minutes: Number(fHangTimeout.value) },
|
|
679
|
+
startup: { concurrency: Number(fConcurrency.value), stagger_delay_ms: Number(fStagger.value) },
|
|
680
|
+
};
|
|
681
|
+
const r1 = await api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) });
|
|
682
|
+
let r2 = { ok: true, body: {} };
|
|
683
|
+
if (primary) r2 = await saveChannels();
|
|
684
|
+
const r3 = await api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify({ backend: c.backend, admin_users: c.admin_users, allowed_guilds: c.allowed_guilds }) });
|
|
685
|
+
const bad = [r1, r2, r3].find(r => !r.ok);
|
|
686
|
+
resultMsg(msg, bad || { ok: true, body: { warnings: [...(r1.body?.warnings || []), ...(r3.body?.warnings || [])] } });
|
|
687
|
+
if (!bad) {
|
|
688
|
+
const affected = Object.keys(state.fleet.instances || {});
|
|
689
|
+
await reload();
|
|
690
|
+
showBanner(`✓ ${t("affected")}: ${affected.length ? affected.join(", ") : "none"} — 🔄 restart instances; startup changes 🔄🔄 restart fleet`);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const section = (title, open, ...children) => {
|
|
695
|
+
const details = el("details", { class: "settings-section" }); details.open = open;
|
|
696
|
+
details.append(el("summary", {}, title), el("div", { class: "section-body" }, ...children));
|
|
697
|
+
return details;
|
|
698
|
+
};
|
|
499
699
|
host.append(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
700
|
+
section(t("runtimeResources"), true,
|
|
701
|
+
el("div", { class: "grid2" },
|
|
702
|
+
el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), fAutoPause, autoFeedback),
|
|
703
|
+
el("div", { class: "field" }, el("label", {}, t("toolSet"), impact("instance")), fToolSet),
|
|
704
|
+
el("div", { class: "field" }, el("label", {}, t("logLevel"), impact("instance")), fLogLevel))),
|
|
705
|
+
section(t("backendSection"), true,
|
|
706
|
+
el("div", { class: "grid2" },
|
|
707
|
+
el("div", { class: "field" }, el("label", {}, t("defaultBackend"), impact("instance")), fBackend),
|
|
708
|
+
el("div", { class: "field" }, el("label", {}, t("defaultModel"), impact("instance")), fModel),
|
|
709
|
+
el("div", { class: "field" }, el("label", {}, t("agentMode"), impact("instance")), fAgentMode))),
|
|
710
|
+
section(t("detection"), false,
|
|
711
|
+
el("div", { class: "grid2" },
|
|
712
|
+
el("div", { class: "field" }, el("label", { style: "display:flex;align-items:center;gap:8px" }, fHangEnabled, el("span", {}, t("hangEnabled")), impact("instance"))),
|
|
713
|
+
el("div", { class: "field" }, el("label", {}, `${t("hangTimeout")} (${t("minutes")})`, impact("instance")), fHangTimeout, hangFeedback))),
|
|
714
|
+
section(t("startup"), false,
|
|
715
|
+
el("div", { class: "grid2" },
|
|
716
|
+
el("div", { class: "field" }, el("label", {}, t("concurrency"), impact("fleet")), fConcurrency, concurrencyFeedback),
|
|
717
|
+
el("div", { class: "field" }, el("label", {}, `${t("staggerDelay")} (ms)`, impact("fleet")), fStagger, staggerFeedback))),
|
|
718
|
+
section(t("advancedSection"), false,
|
|
719
|
+
el("div", { class: "field" }, el("label", {}, t("language"), impact("now")), fLocale),
|
|
720
|
+
el("div", { class: "field" }, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(allowedUsers, null, "user id")),
|
|
721
|
+
el("div", { class: "field" }, el("label", {}, t("classicAdmins"), impact("fleet")), chipList(c.admin_users, null, "user id")),
|
|
722
|
+
el("div", { class: "field" }, el("label", {}, t("allowedGuilds"), impact("fleet")), chipList(c.allowed_guilds, null, "guild id"))),
|
|
723
|
+
el("div", { class: "field" }, el("div", { class: "actions" }, saveBtn, msg)),
|
|
515
724
|
);
|
|
725
|
+
validate();
|
|
516
726
|
}
|
|
517
727
|
|
|
518
728
|
// ── What's New ──
|
|
@@ -589,7 +799,8 @@
|
|
|
589
799
|
renderAgents(); renderBots(); renderClassic();
|
|
590
800
|
}
|
|
591
801
|
async function reload() {
|
|
592
|
-
|
|
802
|
+
// Forms edit the user-authored config, not defaults-expanded runtime values.
|
|
803
|
+
const f = await api("/api/settings/fleet/raw");
|
|
593
804
|
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
805
|
state.fleet = f.body || {};
|
|
595
806
|
const c = await api("/api/settings/classic"); state.classic = c.body || {};
|
package/dist/view-api.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ViewApiContext {
|
|
|
16
16
|
}[];
|
|
17
17
|
getBackendByInstance(name: string, fleetDefault?: string): string;
|
|
18
18
|
} | null;
|
|
19
|
-
getInstanceStatus(name: string): "running" | "stopped" | "crashed";
|
|
19
|
+
getInstanceStatus(name: string): "running" | "paused" | "stopped" | "crashed";
|
|
20
20
|
getUiStatus(): unknown;
|
|
21
21
|
}
|
|
22
22
|
/** True if the path belongs to the view feature (so the caller can skip the
|
package/dist/web-api.d.ts
CHANGED
|
@@ -60,7 +60,8 @@ export interface WebApiContext {
|
|
|
60
60
|
error(obj: unknown, msg?: string): void;
|
|
61
61
|
};
|
|
62
62
|
getInstanceDir(name: string): string;
|
|
63
|
-
getInstanceStatus(name: string): "running" | "stopped" | "crashed";
|
|
63
|
+
getInstanceStatus(name: string): "running" | "paused" | "stopped" | "crashed";
|
|
64
|
+
deliverToInstance(instanceName: string, payload: Record<string, unknown>): Promise<void>;
|
|
64
65
|
getUiStatus(): unknown;
|
|
65
66
|
emitSseEvent(event: string, data: unknown): void;
|
|
66
67
|
startInstance(name: string, config: unknown, topicMode: boolean): Promise<void>;
|
package/dist/web-api.js
CHANGED
|
@@ -41,7 +41,7 @@ const ConfigUpdateSchema = z.object({
|
|
|
41
41
|
access: z.record(z.string(), z.unknown()).optional(),
|
|
42
42
|
}).strict().optional(),
|
|
43
43
|
defaults: z.object({
|
|
44
|
-
backend: z.enum(["claude-code", "gemini-cli", "codex", "opencode", "kiro-cli", "antigravity"]).optional(),
|
|
44
|
+
backend: z.enum(["claude-code", "gemini-cli", "codex", "opencode", "kiro-cli", "antigravity", "grok"]).optional(),
|
|
45
45
|
model: z.string().max(128).optional(),
|
|
46
46
|
}).strict().optional(),
|
|
47
47
|
project_roots: z.array(z.string().min(1).max(1024)).max(64).optional(),
|
|
@@ -165,6 +165,8 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
165
165
|
{ name: "gemini-cli", binary: "gemini" },
|
|
166
166
|
{ name: "opencode", binary: "opencode" },
|
|
167
167
|
{ name: "kiro-cli", binary: "kiro-cli" },
|
|
168
|
+
{ name: "antigravity", binary: "agy" },
|
|
169
|
+
{ name: "grok", binary: "grok" },
|
|
168
170
|
];
|
|
169
171
|
const backends = BACKENDS.map(b => {
|
|
170
172
|
let installed = false;
|
|
@@ -592,7 +594,7 @@ function handleSendMessage(req, res, ctx) {
|
|
|
592
594
|
}
|
|
593
595
|
body += chunk.toString();
|
|
594
596
|
});
|
|
595
|
-
req.on("end", () => {
|
|
597
|
+
req.on("end", async () => {
|
|
596
598
|
try {
|
|
597
599
|
const raw = JSON.parse(body);
|
|
598
600
|
const parsed = SendMessageSchema.safeParse(raw);
|
|
@@ -614,19 +616,26 @@ function handleSendMessage(req, res, ctx) {
|
|
|
614
616
|
// a separate reply path for that case.
|
|
615
617
|
const groupId = ctx.fleetConfig?.channel?.group_id;
|
|
616
618
|
const topicId = ctx.fleetConfig?.instances[instance]?.topic_id;
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
619
|
+
try {
|
|
620
|
+
await ctx.deliverToInstance(instance, {
|
|
621
|
+
type: "fleet_inbound",
|
|
622
|
+
content: message,
|
|
623
|
+
targetSession: instance,
|
|
624
|
+
meta: {
|
|
625
|
+
chat_id: groupId ? String(groupId) : "",
|
|
626
|
+
message_id: `web-${Date.now()}`,
|
|
627
|
+
user: "web-user", user_id: "web-user",
|
|
628
|
+
ts,
|
|
629
|
+
thread_id: topicId != null ? String(topicId) : "",
|
|
630
|
+
source: "web",
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
ctx.logger.error({ err, instance }, "Web message delivery failed");
|
|
636
|
+
json(res, 503, { error: "Instance delivery failed" });
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
630
639
|
ctx.lastInboundUser.set(instance, "web-user");
|
|
631
640
|
ctx.eventLog?.logActivity("message", "web-user", message.slice(0, 200), instance);
|
|
632
641
|
ctx.emitSseEvent("message", { instance, sender: "web-user", text: message, ts });
|