@songsid/agend 2.1.4-beta.32 → 2.1.4-beta.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel/adapters/discord.js +9 -7
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +2 -1
- package/dist/channel/ipc-bridge.js +33 -4
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/mcp-server.js +11 -2
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/config-validator.js +4 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +7 -0
- package/dist/daemon.js +62 -44
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.js +37 -9
- package/dist/fleet-manager.js.map +1 -1
- package/dist/reply-dedup.d.ts +7 -8
- package/dist/reply-dedup.js +0 -0
- package/dist/reply-dedup.js.map +1 -1
- package/dist/ui/settings.html +82 -11
- package/package.json +2 -2
package/dist/reply-dedup.d.ts
CHANGED
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
* this is the actual race (the retry arrives while the original is still
|
|
15
15
|
* waiting out a rate limit), and both callers report whatever the one real
|
|
16
16
|
* send did.
|
|
17
|
-
* -
|
|
18
|
-
* result
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* - Once a reply settles (success or failure), its entry is removed. Replaying
|
|
18
|
+
* a completed result is unsafe: two legitimate short replies such as "OK"
|
|
19
|
+
* would otherwise return the first platform message id without a second POST.
|
|
20
|
+
* - An in-flight entry has a hard lifetime. If an adapter promise never settles,
|
|
21
|
+
* later identical replies must not join that dead promise forever.
|
|
21
22
|
*/
|
|
22
23
|
export declare class ReplyDeduper {
|
|
23
|
-
private readonly
|
|
24
|
-
private readonly now;
|
|
24
|
+
private readonly inFlightTimeoutMs;
|
|
25
25
|
private entries;
|
|
26
|
-
constructor(
|
|
26
|
+
constructor(inFlightTimeoutMs?: number);
|
|
27
27
|
/**
|
|
28
28
|
* Register an outgoing reply. Returns `fresh` (send it, then call `complete`
|
|
29
29
|
* exactly once with the outcome) or `duplicate` (do NOT send; `subscribe` for
|
|
@@ -37,5 +37,4 @@ export declare class ReplyDeduper {
|
|
|
37
37
|
subscribe(cb: (result: unknown, error?: string) => void): void;
|
|
38
38
|
};
|
|
39
39
|
private key;
|
|
40
|
-
private prune;
|
|
41
40
|
}
|
package/dist/reply-dedup.js
CHANGED
|
Binary file
|
package/dist/reply-dedup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-dedup.js","sourceRoot":"","sources":["../src/reply-dedup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC
|
|
1
|
+
{"version":3,"file":"reply-dedup.js","sourceRoot":"","sources":["../src/reply-dedup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,YAAY;IAGM;IAFrB,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IAE3C,YAA6B,oBAAoB,CAAC,GAAG,MAAM;QAA9B,sBAAiB,GAAjB,iBAAiB,CAAa;IAAG,CAAC;IAE/D;;;;OAIG;IACH,KAAK,CAAC,QAAgB,EAAE,IAAY,EAAE,QAA2B,EAAE;QAGjE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEvC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;gBACL,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;aAC3C,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAU,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QACvE,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK;gBAAE,OAAO;YAC7D,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,MAAM,KAAK,GAAG,wCAAwC,IAAI,CAAC,iBAAiB,yBAAyB,CAAC;YACtG,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACpE,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;gBAC1B,IAAI,KAAK,CAAC,OAAO;oBAAE,OAAO,CAAC,8BAA8B;gBACzD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;gBACrB,IAAI,KAAK,CAAC,KAAK;oBAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC3C,sEAAsE;gBACtE,8BAA8B;gBAC9B,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK;oBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC9D,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;oBAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC5D,CAAC;SACF,CAAC;IACJ,CAAC;IAEO,GAAG,CAAC,QAAgB,EAAE,IAAY,EAAE,KAAwB;QAClE,OAAO,UAAU,CAAC,QAAQ,CAAC;aACxB,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;aAC7B,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;aACzB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACxB,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;CACF"}
|
package/dist/ui/settings.html
CHANGED
|
@@ -205,6 +205,7 @@
|
|
|
205
205
|
wnIntro: "Recent highlights:", releaseNotes: "📄 View full Release Notes →", languageButton: "Language", helpButton: "Help", showAdditional: "Show additional controls", hideAdvanced: "Hide advanced", showAdvanced: "Show advanced",
|
|
206
206
|
saved: "Saved", failed: "Failed", actionFailed: "{0} failed", noAgentMatch: "No agents match \"{0}\".", deleteAgent: "Delete agent \"{0}\"? This will be applied only after you confirm the pending changes.", nameRequired: "name required", tokenEnvRequired: "token env required", removeBot: "Remove bot \"{0}\"? (the token in .env is left untouched)",
|
|
207
207
|
changesPending: "{0} change(s) not applied", impactNow: "⚡ Immediately", impactAgent: "🔄 Restart this Agent", impactFleet: "🔄🔄 Restart AgEnD", queued: "Queued: {0}", applyFailed: "Apply failed", changesApplied: "✓ Changes applied. AgEnD is reconciling affected Agents.", reviewChanges: "Review changes", readyApply: "Ready to apply", copied: "Copied", expectedMapping: "expected a mapping", configLoadFailed: "Failed to load config — check the token in the URL.",
|
|
208
|
+
confirmOpenAccess: "Open access allows anyone in this channel to operate the bot. Continue?", confirmLockedEmpty: "Locked access has no allowed users. This may leave nobody able to administer the bot. Continue?", accessLockedEmpty: "Locked mode has no allowed users; add at least one administrator to avoid lockout.", accessChangeCancelled: "Access mode change cancelled",
|
|
208
209
|
mustNonNegative: "Must be 0 or greater", mustPositive: "Must be greater than 0", nonnegativeInteger: "Must be a non-negative integer", failoverEmpty: "Empty list disables failover", concurrencyRange: "Must be an integer from 1 to 20", concurrencyWarning: "May exceed CPU capacity on smaller hosts", staggerRange: "Must be between 0 and 30000", staggerWarning: "Very low values can cause startup spikes",
|
|
209
210
|
},
|
|
210
211
|
"zh-TW": {
|
|
@@ -226,6 +227,7 @@
|
|
|
226
227
|
wnIntro: "近期更新重點:", releaseNotes: "📄 查看完整 Release Notes →", languageButton: "語言", helpButton: "使用說明", showAdditional: "顯示更多控制項", hideAdvanced: "隱藏進階設定", showAdvanced: "顯示進階設定",
|
|
227
228
|
saved: "已儲存", failed: "失敗", actionFailed: "{0}失敗", noAgentMatch: "沒有符合「{0}」的 Agent。", deleteAgent: "確定刪除 Agent「{0}」嗎?只有在確認套用待處理變更後才會生效。", nameRequired: "必須填寫名稱", tokenEnvRequired: "必須填寫 token 環境變數", removeBot: "確定移除機器人「{0}」嗎?(.env 中的 token 會保留)",
|
|
228
229
|
changesPending: "有 {0} 項變更尚未套用", impactNow: "⚡ 立即生效", impactAgent: "🔄 重新啟動此 Agent", impactFleet: "🔄🔄 重新啟動 AgEnD", queued: "已加入待套用:{0}", applyFailed: "套用失敗", changesApplied: "✓ 變更已套用,AgEnD 正在協調受影響的 Agent。", reviewChanges: "檢查變更", readyApply: "可套用變更", copied: "已複製", expectedMapping: "預期為 mapping", configLoadFailed: "載入設定失敗 — 請檢查網址中的 token。",
|
|
230
|
+
confirmOpenAccess: "Open 模式會允許此頻道中的所有人操作 bot,確定要繼續嗎?", confirmLockedEmpty: "Locked 模式目前沒有允許的使用者,可能導致沒有人能管理 bot,確定要繼續嗎?", accessLockedEmpty: "Locked 模式沒有允許的使用者;建議至少加入一位管理員,避免無法操作。", accessChangeCancelled: "已取消 Access Mode 變更",
|
|
229
231
|
mustNonNegative: "必須為 0 或更大的數值", mustPositive: "必須大於 0", nonnegativeInteger: "必須是非負整數", failoverEmpty: "空白清單會停用模型備援", concurrencyRange: "必須是 1 到 20 的整數", concurrencyWarning: "在較小的主機上可能超出 CPU 負荷", staggerRange: "必須介於 0 到 30000", staggerWarning: "數值太低可能造成啟動尖峰",
|
|
230
232
|
},
|
|
231
233
|
};
|
|
@@ -346,6 +348,15 @@
|
|
|
346
348
|
}
|
|
347
349
|
async function applyPendingChanges() {
|
|
348
350
|
const button = $("applyChanges"); button.disabled = true;
|
|
351
|
+
// Complete every safety confirmation before the first write. Otherwise a
|
|
352
|
+
// cancelled access-mode change could leave earlier staged settings applied
|
|
353
|
+
// and later ones pending (a surprising partial apply).
|
|
354
|
+
for (const change of state.pending.values()) {
|
|
355
|
+
if (change.confirm && !change.confirm()) {
|
|
356
|
+
showBanner(change.cancelMessage || t("accessChangeCancelled"), true);
|
|
357
|
+
button.disabled = false; return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
349
360
|
for (const [key, change] of [...state.pending]) {
|
|
350
361
|
let result;
|
|
351
362
|
try { result = await change.apply(); }
|
|
@@ -384,6 +395,15 @@
|
|
|
384
395
|
feedback.textContent = error ? `✗ ${error}` : warning ? `⚠ ${warning}` : "";
|
|
385
396
|
return !error;
|
|
386
397
|
}
|
|
398
|
+
function confirmAccessChange(previousAccess, nextMode, nextUsers) {
|
|
399
|
+
const previousMode = previousAccess?.mode || "locked";
|
|
400
|
+
const previousUsers = previousAccess?.allowed_users || [];
|
|
401
|
+
if (previousMode !== "open" && nextMode === "open" && !confirm(t("confirmOpenAccess"))) return false;
|
|
402
|
+
const newlyEmptyLocked = nextMode === "locked" && nextUsers.length === 0
|
|
403
|
+
&& (previousMode !== "locked" || previousUsers.length > 0);
|
|
404
|
+
if (newlyEmptyLocked && !confirm(t("confirmLockedEmpty"))) return false;
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
387
407
|
|
|
388
408
|
// ── My Agents ──
|
|
389
409
|
let editing = null;
|
|
@@ -399,12 +419,23 @@
|
|
|
399
419
|
const STATUS_TEXT = { running: "🟢 Running", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped" };
|
|
400
420
|
const EXECUTION_TEXT = { idle: "🟢 Idle", working: "🔵 Working", stuck: "🔴 Stuck", paused: "⏸ Paused", crashed: "🔴 Crashed", stopped: "⚪ Stopped", running: "🟢 Running" };
|
|
401
421
|
const STATUS_ORDER = { running: 0, paused: 1, crashed: 2, stopped: 3 };
|
|
422
|
+
const EFFORT_BACKENDS = new Set(["claude-code", "codex", "kiro-cli", "grok", "antigravity"]);
|
|
402
423
|
const groupOf = (inst) => (inst.tags && inst.tags[0]) || "Other";
|
|
424
|
+
function effectiveSummary(name, config, classic = false) {
|
|
425
|
+
const live = state.live[name] || {};
|
|
426
|
+
const defaults = classic ? (state.classic?.defaults || {}) : (state.fleet?.defaults || {});
|
|
427
|
+
const backend = live.backend || config.backend || defaults.backend || state.fleet?.defaults?.backend || "claude-code";
|
|
428
|
+
const model = live.model_display || config.model || defaults.model || state.fleet?.defaults?.model || "default";
|
|
429
|
+
const effortSupported = live.effort_supported ?? EFFORT_BACKENDS.has(backend);
|
|
430
|
+
const effort = effortSupported ? (live.effort || config.effort || state.fleet?.defaults?.effort || "auto") : "—";
|
|
431
|
+
return { backend, model, effort };
|
|
432
|
+
}
|
|
403
433
|
function compactRow(name, inst) {
|
|
404
434
|
const st = state.live[name]?.status || "stopped";
|
|
405
435
|
const running = st === "running";
|
|
406
436
|
const paused = st === "paused";
|
|
407
437
|
const short = shortName(name, inst);
|
|
438
|
+
const summary = effectiveSummary(name, inst);
|
|
408
439
|
const chTag = agentChannelLabel(inst);
|
|
409
440
|
const item = el("div", { class: "item" });
|
|
410
441
|
// Single fixed-height line: dot + short name + backend + channel tag + actions.
|
|
@@ -412,7 +443,8 @@
|
|
|
412
443
|
item.append(el("div", { class: "item-row" },
|
|
413
444
|
el("span", { class: "dot " + st, title: STATUS_TEXT[st] || st }),
|
|
414
445
|
el("span", { class: "name", title: name }, short),
|
|
415
|
-
el("span", { class: "sub", style: "margin:0"
|
|
446
|
+
el("span", { class: "sub", style: "margin:0", title: summary.backend }, summary.model),
|
|
447
|
+
el("span", { class: "tag" }, `effort: ${summary.effort}`),
|
|
416
448
|
chTag ? el("span", { class: "tag" + (chTag.includes("persona") ? " persona" : chTag.includes("telegram") ? " tg" : "") }, chTag) : null,
|
|
417
449
|
el("span", { class: "spacer" }),
|
|
418
450
|
el("button", { class: "sm ghost", onclick: () => { editing = editing === name ? null : name; renderAgents(); } }, editing === name ? t("close") : t("edit")),
|
|
@@ -640,16 +672,21 @@
|
|
|
640
672
|
function botEditForm(ch, i) {
|
|
641
673
|
ch.access = ch.access || { mode: "locked", allowed_users: [] };
|
|
642
674
|
ch.access.allowed_users = ch.access.allowed_users || [];
|
|
675
|
+
const previousAccess = structuredClone(ch.access);
|
|
643
676
|
const box = el("div", { class: "edit" });
|
|
644
677
|
const fMode = select(ch.access.mode || "locked", ["open", "locked", "pairing"]);
|
|
645
678
|
const users = [...ch.access.allowed_users];
|
|
646
679
|
const msg = el("span", { class: "msg" });
|
|
680
|
+
const accessFeedback = el("div", { class: "feedback" });
|
|
681
|
+
const validateAccess = () => setValidation(fMode, accessFeedback, "", fMode.value === "locked" && users.length === 0 ? t("accessLockedEmpty") : "");
|
|
682
|
+
fMode.addEventListener("change", validateAccess);
|
|
647
683
|
box.append(
|
|
648
684
|
el("div", { class: "grid2" },
|
|
649
|
-
el("div", {}, el("label", {}, t("accessMode"), impact("fleet")), fMode),
|
|
650
|
-
el("div", {}, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(users,
|
|
685
|
+
el("div", {}, el("label", {}, t("accessMode"), impact("fleet")), fMode, accessFeedback),
|
|
686
|
+
el("div", {}, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(users, validateAccess, "user id"))),
|
|
651
687
|
el("div", { class: "actions" },
|
|
652
688
|
el("button", { class: "primary sm", onclick: async () => {
|
|
689
|
+
if (!confirmAccessChange(previousAccess, fMode.value, users)) return;
|
|
653
690
|
const chs = channels();
|
|
654
691
|
chs[i] = { ...chs[i], access: { ...chs[i].access, mode: fMode.value, allowed_users: users } };
|
|
655
692
|
const res = await saveChannels(msg);
|
|
@@ -661,6 +698,7 @@
|
|
|
661
698
|
} }, t("save")),
|
|
662
699
|
el("button", { class: "sm ghost", onclick: () => { editingBot = null; renderBots(); } }, t("cancel")), msg),
|
|
663
700
|
);
|
|
701
|
+
validateAccess();
|
|
664
702
|
return box;
|
|
665
703
|
}
|
|
666
704
|
|
|
@@ -755,11 +793,14 @@
|
|
|
755
793
|
const runtime = classicRuntime(c.instanceName);
|
|
756
794
|
const st = runtime.status;
|
|
757
795
|
const adapter = c.adapterId || "discord";
|
|
796
|
+
const summary = effectiveSummary(c.instanceName, c, true);
|
|
797
|
+
const displayName = shortName(c.name || c.instanceName || "?", { display_name: c.display_name });
|
|
758
798
|
const item = el("div", { class: "item" });
|
|
759
799
|
item.append(el("div", { class: "item-row" },
|
|
760
800
|
el("span", { class: "dot " + runtime.execution, title: EXECUTION_TEXT[runtime.execution] || runtime.execution }),
|
|
761
|
-
el("span", { class: "name", title: c.instanceName || "" },
|
|
762
|
-
el("span", { class: "sub", style: "margin:0"
|
|
801
|
+
el("span", { class: "name", title: c.instanceName || "" }, displayName),
|
|
802
|
+
el("span", { class: "sub", style: "margin:0", title: summary.backend }, summary.model),
|
|
803
|
+
el("span", { class: "tag" }, `effort: ${summary.effort}`),
|
|
763
804
|
el("span", { class: "tag" + (adapter !== "discord" ? " persona" : "") }, adapter),
|
|
764
805
|
c.collab ? el("span", { class: "tag", style: "background:#ecfdf5;color:#065f46" }, "collab") : null,
|
|
765
806
|
el("span", { class: "spacer" }),
|
|
@@ -822,8 +863,8 @@
|
|
|
822
863
|
const hang = d.hang_detector || {};
|
|
823
864
|
const startup = d.startup || {};
|
|
824
865
|
const primary = channels()[0];
|
|
825
|
-
|
|
826
|
-
const allowedUsers =
|
|
866
|
+
const originalPrimaryAccess = structuredClone(primary?.access || { mode: "locked", allowed_users: [] });
|
|
867
|
+
const allowedUsers = [...(originalPrimaryAccess.allowed_users || [])];
|
|
827
868
|
c.admin_users = c.admin_users || []; c.allowed_guilds = c.allowed_guilds || [];
|
|
828
869
|
|
|
829
870
|
const fBackend = select(d.backend || "claude-code", BACKENDS);
|
|
@@ -832,6 +873,7 @@
|
|
|
832
873
|
const fAgentMode = select(d.agent_mode || "mcp", ["mcp", "cli"]);
|
|
833
874
|
const fToolSet = select(d.tool_set || "full", ["full", "standard", "minimal"]);
|
|
834
875
|
const fToolProgress = select(d.tool_progress || "off", ["off", "standard", "verbose"]);
|
|
876
|
+
const fAccessMode = select(originalPrimaryAccess.mode || "locked", ["locked", "open", "pairing"]);
|
|
835
877
|
const fLogLevel = select(d.log_level || "info", ["trace", "debug", "info", "warn", "error"]);
|
|
836
878
|
const fLocale = el("select");
|
|
837
879
|
const fAutoPause = el("input", { type: "number", min: "0", step: "1", value: d.auto_pause_after ?? 0, placeholder: "0 = disabled" });
|
|
@@ -845,6 +887,7 @@
|
|
|
845
887
|
const hangFeedback = el("div", { class: "feedback" });
|
|
846
888
|
const concurrencyFeedback = el("div", { class: "feedback" });
|
|
847
889
|
const staggerFeedback = el("div", { class: "feedback" });
|
|
890
|
+
const accessFeedback = el("div", { class: "feedback" });
|
|
848
891
|
const saveBtn = el("button", { class: "primary" }, t("save"));
|
|
849
892
|
const validate = () => {
|
|
850
893
|
const auto = Number(fAutoPause.value), timeout = Number(fHangTimeout.value);
|
|
@@ -853,12 +896,14 @@
|
|
|
853
896
|
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? t("mustPositive") : "");
|
|
854
897
|
const validConcurrency = setValidation(fConcurrency, concurrencyFeedback, !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 20 ? t("concurrencyRange") : "", concurrency > 4 ? t("concurrencyWarning") : "");
|
|
855
898
|
const validStagger = setValidation(fStagger, staggerFeedback, !Number.isFinite(stagger) || stagger < 0 || stagger > 30000 ? t("staggerRange") : "", stagger < 100 ? t("staggerWarning") : "");
|
|
899
|
+
if (primary) setValidation(fAccessMode, accessFeedback, "", fAccessMode.value === "locked" && allowedUsers.length === 0 ? t("accessLockedEmpty") : "");
|
|
856
900
|
saveBtn.disabled = !(validAuto && validHang && validConcurrency && validStagger);
|
|
857
901
|
return !saveBtn.disabled;
|
|
858
902
|
};
|
|
859
903
|
for (const input of [fAutoPause, fHangTimeout, fConcurrency, fStagger]) {
|
|
860
904
|
input.addEventListener("input", validate); input.addEventListener("change", validate);
|
|
861
905
|
}
|
|
906
|
+
fAccessMode.addEventListener("change", validate);
|
|
862
907
|
saveBtn.textContent = t("reviewChanges");
|
|
863
908
|
saveBtn.onclick = () => {
|
|
864
909
|
if (!validate()) return;
|
|
@@ -899,8 +944,22 @@
|
|
|
899
944
|
if (Object.keys(defaultsPatch).length > 0) {
|
|
900
945
|
stageChange("defaults:fleet", { label: "Update Agent defaults", impact: Object.keys(defaultsPatch).every(key => HOT_FIELDS.has(key)) ? "now" : "instance", apply: () => api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) }) });
|
|
901
946
|
}
|
|
902
|
-
if (primary
|
|
903
|
-
|
|
947
|
+
if (primary) {
|
|
948
|
+
const stagedAccessMode = fAccessMode.value;
|
|
949
|
+
const stagedAllowedUsers = [...allowedUsers];
|
|
950
|
+
const nextChannels = structuredClone(originalChannels);
|
|
951
|
+
nextChannels[0] = { ...nextChannels[0], access: { ...nextChannels[0].access, mode: stagedAccessMode, allowed_users: stagedAllowedUsers } };
|
|
952
|
+
if (!sameValue(nextChannels, originalChannels)) {
|
|
953
|
+
stageChange("connections:primary", {
|
|
954
|
+
label: "Update primary connection access",
|
|
955
|
+
impact: "fleet",
|
|
956
|
+
// Confirm the immutable staged snapshot, not live form controls
|
|
957
|
+
// that the user may edit again before pressing global Apply.
|
|
958
|
+
confirm: () => confirmAccessChange(originalPrimaryAccess, stagedAccessMode, stagedAllowedUsers),
|
|
959
|
+
cancelMessage: t("accessChangeCancelled"),
|
|
960
|
+
apply: () => api("/api/settings/fleet/channels", { method: "PUT", body: JSON.stringify(nextChannels) }),
|
|
961
|
+
});
|
|
962
|
+
}
|
|
904
963
|
}
|
|
905
964
|
if (Object.keys(classicPatch).length > 0) {
|
|
906
965
|
stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
|
|
@@ -943,7 +1002,11 @@
|
|
|
943
1002
|
el("div", { class: "field" }, el("label", {}, t("concurrency"), impact("fleet")), fConcurrency, concurrencyFeedback),
|
|
944
1003
|
el("div", { class: "field" }, el("label", {}, `${t("staggerDelay")} (ms)`, impact("fleet")), fStagger, staggerFeedback))),
|
|
945
1004
|
section("Advanced & Access", false,
|
|
946
|
-
|
|
1005
|
+
primary
|
|
1006
|
+
? el("div", { class: "grid2" },
|
|
1007
|
+
el("div", { class: "field" }, el("label", {}, t("accessMode"), impact("fleet")), fAccessMode, accessFeedback),
|
|
1008
|
+
el("div", { class: "field" }, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(allowedUsers, validate, "user id")))
|
|
1009
|
+
: el("div", { class: "field hint" }, t("noBots")))),
|
|
947
1010
|
el("div", { class: "field" }, el("div", { class: "actions" }, saveBtn, msg)),
|
|
948
1011
|
);
|
|
949
1012
|
validate();
|
|
@@ -1016,7 +1079,15 @@
|
|
|
1016
1079
|
state.fleetUp = f.ok;
|
|
1017
1080
|
state.live = {}; state.classicLive = {};
|
|
1018
1081
|
if (f.ok && f.body?.instances) for (const i of f.body.instances) {
|
|
1019
|
-
state.live[i.name] = {
|
|
1082
|
+
state.live[i.name] = {
|
|
1083
|
+
status: i.status,
|
|
1084
|
+
state: i.state,
|
|
1085
|
+
backend: i.backend,
|
|
1086
|
+
model: i.model,
|
|
1087
|
+
model_display: i.model_display,
|
|
1088
|
+
effort: i.effort,
|
|
1089
|
+
effort_supported: i.effort_supported,
|
|
1090
|
+
};
|
|
1020
1091
|
if (i.classic) state.classicLive[i.name] = { status: i.status, state: i.state };
|
|
1021
1092
|
}
|
|
1022
1093
|
// /api/fleet carries live tri-state for Classic instances. Profiles remain
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@songsid/agend",
|
|
3
|
-
"version": "2.1.4-beta.
|
|
3
|
+
"version": "2.1.4-beta.34",
|
|
4
4
|
"description": "Multi-agent fleet daemon — run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
|
-
"postbuild": "cp -r src/plugin dist/ && cp -r src/workflow-templates dist/ && cp src/agent-cli-instructions.md dist/ && cp -r src/ui dist/ && cp -r src/general-knowledge dist/ && chmod +x dist/cli.js && cd dist/plugin/agend && ln -sf ../../channel/mcp-server.js server.js",
|
|
19
|
+
"postbuild": "node scripts/generate-tips-html.mjs && cp -r src/plugin dist/ && cp -r src/workflow-templates dist/ && cp src/agent-cli-instructions.md dist/ && cp -r src/ui dist/ && cp -r src/general-knowledge dist/ && chmod +x dist/cli.js && cd dist/plugin/agend && ln -sf ../../channel/mcp-server.js server.js",
|
|
20
20
|
"start": "tsx src/cli.ts start",
|
|
21
21
|
"dev": "tsx src/cli.ts",
|
|
22
22
|
"test": "vitest run",
|