bosun 0.35.0 β 0.35.2
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/agent-pool.mjs +59 -0
- package/claude-shell.mjs +44 -6
- package/codex-shell.mjs +68 -21
- package/copilot-shell.mjs +53 -14
- package/maintenance.mjs +49 -0
- package/monitor.mjs +51 -66
- package/package.json +2 -1
- package/stream-resilience.mjs +79 -0
- package/telegram-bot.mjs +267 -102
- package/ui/index.html +5 -5
- package/ui-server.mjs +178 -6
package/telegram-bot.mjs
CHANGED
|
@@ -935,6 +935,80 @@ function getBrowserUiUrl() {
|
|
|
935
935
|
return appendTokenToUrl(base, token) || base;
|
|
936
936
|
}
|
|
937
937
|
|
|
938
|
+
function isTelegramInlineButtonUrlAllowed(inputUrl) {
|
|
939
|
+
try {
|
|
940
|
+
const parsed = new URL(String(inputUrl || "").trim());
|
|
941
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
const host = String(parsed.hostname || "").toLowerCase();
|
|
945
|
+
if (!host) return false;
|
|
946
|
+
// Telegram rejects localhost/loopback URLs for inline keyboard URL buttons.
|
|
947
|
+
if (
|
|
948
|
+
host === "localhost" ||
|
|
949
|
+
host === "127.0.0.1" ||
|
|
950
|
+
host === "::1" ||
|
|
951
|
+
host === "[::1]"
|
|
952
|
+
) {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
return true;
|
|
956
|
+
} catch {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function getBrowserUiUrlOptions({ forTelegramButtons = true } = {}) {
|
|
962
|
+
const base = String(telegramUiUrl || "").trim();
|
|
963
|
+
if (!base) return [];
|
|
964
|
+
|
|
965
|
+
const token = getSessionToken();
|
|
966
|
+
const options = [];
|
|
967
|
+
const seen = new Set();
|
|
968
|
+
const add = (label, inputUrl) => {
|
|
969
|
+
const raw = String(inputUrl || "").trim();
|
|
970
|
+
if (!raw) return;
|
|
971
|
+
const url = appendTokenToUrl(raw, token) || raw;
|
|
972
|
+
if (!url) return;
|
|
973
|
+
if (seen.has(url)) return;
|
|
974
|
+
seen.add(url);
|
|
975
|
+
if (forTelegramButtons && !isTelegramInlineButtonUrlAllowed(url)) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
options.push({ label, url });
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
let parsed = null;
|
|
982
|
+
try {
|
|
983
|
+
parsed = new URL(base);
|
|
984
|
+
} catch {
|
|
985
|
+
parsed = null;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (parsed) {
|
|
989
|
+
const localhostUrl = `${parsed.protocol}//localhost${parsed.port ? `:${parsed.port}` : ""}`;
|
|
990
|
+
add("π₯οΈ Localhost", localhostUrl);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (parsed) {
|
|
994
|
+
const lanIp = getLocalLanIp?.();
|
|
995
|
+
if (lanIp && parsed.port) {
|
|
996
|
+
const lanUrl = `${parsed.protocol}//${lanIp}:${parsed.port}`;
|
|
997
|
+
add("πΆ LAN", lanUrl);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const tunnelUrl = getTunnelUrl();
|
|
1002
|
+
if (tunnelUrl) {
|
|
1003
|
+
add("βοΈ Cloudflare", tunnelUrl);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (options.length === 0) {
|
|
1007
|
+
add("π Browser URL", base);
|
|
1008
|
+
}
|
|
1009
|
+
return options;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
938
1012
|
function syncUiUrlsFromServer() {
|
|
939
1013
|
const currentUiUrl = getTelegramUiUrl?.() || null;
|
|
940
1014
|
telegramUiUrl = currentUiUrl;
|
|
@@ -947,9 +1021,10 @@ function syncUiUrlsFromServer() {
|
|
|
947
1021
|
|
|
948
1022
|
// ββ Agent session state (for follow-up steering & bottom-pinning) ββββββββββββ
|
|
949
1023
|
|
|
950
|
-
|
|
951
|
-
let
|
|
952
|
-
let
|
|
1024
|
+
const activeAgentSessions = new Map(); // chatId -> session
|
|
1025
|
+
let activeAgentSession = null; // legacy pointer to the latest active session
|
|
1026
|
+
let agentMessageId = null; // latest agent streaming message ID
|
|
1027
|
+
let agentChatId = null; // latest chat where an agent is running
|
|
953
1028
|
|
|
954
1029
|
// ββ Sticky UI menu state (keep /menu accessible at bottom) βββββββββββββββββ
|
|
955
1030
|
const stickyMenuState = new Map();
|
|
@@ -960,7 +1035,7 @@ const STICKY_MENU_BUMP_MS = 600;
|
|
|
960
1035
|
|
|
961
1036
|
let fastCommandQueue = Promise.resolve();
|
|
962
1037
|
let commandQueue = Promise.resolve();
|
|
963
|
-
|
|
1038
|
+
const agentQueues = new Map();
|
|
964
1039
|
|
|
965
1040
|
function enqueueFastCommand(task) {
|
|
966
1041
|
fastCommandQueue = fastCommandQueue.then(task).catch((err) => {
|
|
@@ -974,10 +1049,69 @@ function enqueueCommand(task) {
|
|
|
974
1049
|
});
|
|
975
1050
|
}
|
|
976
1051
|
|
|
977
|
-
function enqueueAgentTask(task) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1052
|
+
function enqueueAgentTask(task, key = "global") {
|
|
1053
|
+
const queueKey = String(key || "global");
|
|
1054
|
+
const prev = agentQueues.get(queueKey) || Promise.resolve();
|
|
1055
|
+
const next = prev
|
|
1056
|
+
.then(task)
|
|
1057
|
+
.catch((err) => {
|
|
1058
|
+
console.error(
|
|
1059
|
+
`[telegram-bot] agent error (${queueKey}): ${err.message || err}`,
|
|
1060
|
+
);
|
|
1061
|
+
})
|
|
1062
|
+
.finally(() => {
|
|
1063
|
+
if (agentQueues.get(queueKey) === next) {
|
|
1064
|
+
agentQueues.delete(queueKey);
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
agentQueues.set(queueKey, next);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function getActiveAgentSession(chatId = null) {
|
|
1071
|
+
if (chatId != null) {
|
|
1072
|
+
return activeAgentSessions.get(String(chatId)) || null;
|
|
1073
|
+
}
|
|
1074
|
+
if (
|
|
1075
|
+
activeAgentSession &&
|
|
1076
|
+
activeAgentSessions.has(String(activeAgentSession.chatId || ""))
|
|
1077
|
+
) {
|
|
1078
|
+
return activeAgentSession;
|
|
1079
|
+
}
|
|
1080
|
+
const first = activeAgentSessions.values().next().value || null;
|
|
1081
|
+
if (first) activeAgentSession = first;
|
|
1082
|
+
return first;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function setActiveAgentSession(chatId, session) {
|
|
1086
|
+
const key = String(chatId || "");
|
|
1087
|
+
if (!key || !session) return;
|
|
1088
|
+
activeAgentSessions.set(key, session);
|
|
1089
|
+
activeAgentSession = session;
|
|
1090
|
+
if (session.messageId) {
|
|
1091
|
+
agentMessageId = session.messageId;
|
|
1092
|
+
agentChatId = key;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function clearActiveAgentSession(chatId, expectedSession = null) {
|
|
1097
|
+
const key = String(chatId || "");
|
|
1098
|
+
if (!key) return;
|
|
1099
|
+
const current = activeAgentSessions.get(key);
|
|
1100
|
+
if (expectedSession && current && current !== expectedSession) return;
|
|
1101
|
+
activeAgentSessions.delete(key);
|
|
1102
|
+
if (agentChatId === key) {
|
|
1103
|
+
agentChatId = null;
|
|
1104
|
+
agentMessageId = null;
|
|
1105
|
+
}
|
|
1106
|
+
if (
|
|
1107
|
+
activeAgentSession &&
|
|
1108
|
+
String(activeAgentSession.chatId || "") === key
|
|
1109
|
+
) {
|
|
1110
|
+
activeAgentSession = null;
|
|
1111
|
+
}
|
|
1112
|
+
if (!activeAgentSession && activeAgentSessions.size > 0) {
|
|
1113
|
+
activeAgentSession = Array.from(activeAgentSessions.values()).pop() || null;
|
|
1114
|
+
}
|
|
981
1115
|
}
|
|
982
1116
|
|
|
983
1117
|
async function getWorkspaceRegistryCached() {
|
|
@@ -1102,28 +1236,38 @@ export function injectMonitorFunctions({
|
|
|
1102
1236
|
* Re-sends the agent message so it stays at the bottom of the chat.
|
|
1103
1237
|
*/
|
|
1104
1238
|
export async function bumpAgentMessage() {
|
|
1105
|
-
|
|
1106
|
-
if (
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
await deleteDirect(agentChatId, agentMessageId);
|
|
1110
|
-
} catch {
|
|
1111
|
-
/* best effort */
|
|
1239
|
+
const candidates = [];
|
|
1240
|
+
if (agentChatId) {
|
|
1241
|
+
const pinned = getActiveAgentSession(agentChatId);
|
|
1242
|
+
if (pinned) candidates.push(pinned);
|
|
1112
1243
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1244
|
+
if (candidates.length === 0) {
|
|
1245
|
+
for (const session of activeAgentSessions.values()) {
|
|
1246
|
+
if (!session?.background) candidates.push(session);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
for (const session of candidates) {
|
|
1250
|
+
if (!session || session.background) continue;
|
|
1251
|
+
if (!session.messageId || !session.chatId) continue;
|
|
1252
|
+
try {
|
|
1253
|
+
await deleteDirect(session.chatId, session.messageId);
|
|
1254
|
+
} catch {
|
|
1255
|
+
/* best effort */
|
|
1256
|
+
}
|
|
1257
|
+
const msg = buildStreamMessage({
|
|
1258
|
+
taskPreview: session.taskPreview,
|
|
1259
|
+
actionLog: session.actionLog,
|
|
1260
|
+
currentThought: session.currentThought,
|
|
1261
|
+
totalActions: session.totalActions,
|
|
1262
|
+
phase: session.phase,
|
|
1263
|
+
finalResponse: null,
|
|
1264
|
+
});
|
|
1265
|
+
const newId = await sendDirect(session.chatId, msg);
|
|
1266
|
+
if (newId) {
|
|
1267
|
+
session.messageId = newId;
|
|
1268
|
+
agentMessageId = newId;
|
|
1269
|
+
agentChatId = String(session.chatId);
|
|
1270
|
+
}
|
|
1127
1271
|
}
|
|
1128
1272
|
}
|
|
1129
1273
|
|
|
@@ -1131,7 +1275,7 @@ export async function bumpAgentMessage() {
|
|
|
1131
1275
|
* Check if agent is active (for external callers like monitor.mjs).
|
|
1132
1276
|
*/
|
|
1133
1277
|
export function isAgentActive() {
|
|
1134
|
-
return
|
|
1278
|
+
return activeAgentSessions.size > 0;
|
|
1135
1279
|
}
|
|
1136
1280
|
|
|
1137
1281
|
function setStickyMenuState(chatId, patch) {
|
|
@@ -2381,13 +2525,14 @@ async function handleUpdate(update) {
|
|
|
2381
2525
|
return;
|
|
2382
2526
|
}
|
|
2383
2527
|
|
|
2384
|
-
// Free-text agent task runs in a separate queue so
|
|
2385
|
-
//
|
|
2386
|
-
|
|
2528
|
+
// Free-text agent task runs in a separate per-chat queue so one chat does not
|
|
2529
|
+
// block another. If this same chat already has an active run, handle
|
|
2530
|
+
// immediately so follow-ups can be queued into that run.
|
|
2531
|
+
if (isPrimaryBusy() && getActiveAgentSession(chatId)) {
|
|
2387
2532
|
safeDetach("free-text", () => handleFreeText(text, chatId));
|
|
2388
2533
|
return;
|
|
2389
2534
|
}
|
|
2390
|
-
enqueueAgentTask(() => handleFreeText(text, chatId));
|
|
2535
|
+
enqueueAgentTask(() => handleFreeText(text, chatId), chatId);
|
|
2391
2536
|
}
|
|
2392
2537
|
|
|
2393
2538
|
// ββ Command Router ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -4320,14 +4465,12 @@ Object.assign(UI_SCREENS, {
|
|
|
4320
4465
|
},
|
|
4321
4466
|
uiButton("β", "cb:close_menu"),
|
|
4322
4467
|
]);
|
|
4323
|
-
if (
|
|
4324
|
-
rows.unshift([
|
|
4325
|
-
{ text: "π Open in Browser", url: getBrowserUiUrl() || telegramUiUrl },
|
|
4326
|
-
]);
|
|
4468
|
+
if (getBrowserUiUrlOptions().length > 0) {
|
|
4469
|
+
rows.unshift([uiButton("π Open in Browser", uiGoAction("browser_urls"))]);
|
|
4327
4470
|
}
|
|
4328
4471
|
} else if (telegramUiUrl) {
|
|
4329
4472
|
rows.unshift([
|
|
4330
|
-
|
|
4473
|
+
uiButton("π Open in Browser", uiGoAction("browser_urls")),
|
|
4331
4474
|
uiButton("β", "cb:close_menu"),
|
|
4332
4475
|
]);
|
|
4333
4476
|
} else {
|
|
@@ -4336,6 +4479,27 @@ Object.assign(UI_SCREENS, {
|
|
|
4336
4479
|
return buildKeyboard(rows);
|
|
4337
4480
|
},
|
|
4338
4481
|
},
|
|
4482
|
+
browser_urls: {
|
|
4483
|
+
title: "Browser URLs",
|
|
4484
|
+
parent: "home",
|
|
4485
|
+
body: () => {
|
|
4486
|
+
const options = getBrowserUiUrlOptions();
|
|
4487
|
+
if (options.length === 0) {
|
|
4488
|
+
return "Mini App URL is not available yet.";
|
|
4489
|
+
}
|
|
4490
|
+
const lines = ["Choose a browser URL. Session token is pre-attached."];
|
|
4491
|
+
for (const option of options) {
|
|
4492
|
+
lines.push(`β’ ${option.label}`);
|
|
4493
|
+
}
|
|
4494
|
+
return lines.join("\n");
|
|
4495
|
+
},
|
|
4496
|
+
keyboard: () => {
|
|
4497
|
+
const options = getBrowserUiUrlOptions();
|
|
4498
|
+
const rows = options.map((option) => [{ text: option.label, url: option.url }]);
|
|
4499
|
+
rows.push(uiNavRow("home"));
|
|
4500
|
+
return buildKeyboard(rows);
|
|
4501
|
+
},
|
|
4502
|
+
},
|
|
4339
4503
|
overview: {
|
|
4340
4504
|
title: "Dashboard",
|
|
4341
4505
|
parent: "home",
|
|
@@ -6168,10 +6332,16 @@ async function cmdApp(chatId) {
|
|
|
6168
6332
|
);
|
|
6169
6333
|
return;
|
|
6170
6334
|
}
|
|
6171
|
-
const
|
|
6335
|
+
const browserOptions = getBrowserUiUrlOptions();
|
|
6336
|
+
const rows = [];
|
|
6172
6337
|
if (webAppUrl) {
|
|
6173
6338
|
rows.unshift([{ text: "π± Open Control Center", web_app: { url: webAppUrl } }]);
|
|
6174
6339
|
}
|
|
6340
|
+
if (browserOptions.length > 0) {
|
|
6341
|
+
rows.push(...browserOptions.map((option) => [{ text: option.label, url: option.url }]));
|
|
6342
|
+
} else {
|
|
6343
|
+
rows.push([{ text: "π Open in Browser", url: getBrowserUiUrl() || uiUrl }]);
|
|
6344
|
+
}
|
|
6175
6345
|
const keyboard = { inline_keyboard: rows };
|
|
6176
6346
|
|
|
6177
6347
|
await sendDirect(
|
|
@@ -6399,7 +6569,7 @@ async function cmdAsk(chatId, args) {
|
|
|
6399
6569
|
await sendReply(chatId, "Usage: /ask <prompt>");
|
|
6400
6570
|
return;
|
|
6401
6571
|
}
|
|
6402
|
-
enqueueAgentTask(() => handleFreeText(prompt, chatId));
|
|
6572
|
+
enqueueAgentTask(() => handleFreeText(prompt, chatId), chatId);
|
|
6403
6573
|
}
|
|
6404
6574
|
|
|
6405
6575
|
async function cmdStatus(chatId) {
|
|
@@ -9220,7 +9390,8 @@ async function cmdBackground(chatId, args) {
|
|
|
9220
9390
|
return;
|
|
9221
9391
|
}
|
|
9222
9392
|
|
|
9223
|
-
|
|
9393
|
+
const session = getActiveAgentSession(chatId);
|
|
9394
|
+
if (!session) {
|
|
9224
9395
|
await sendReply(
|
|
9225
9396
|
chatId,
|
|
9226
9397
|
"No active agent. Usage:\n/background <task>\n(background current agent with /background)",
|
|
@@ -9228,19 +9399,20 @@ async function cmdBackground(chatId, args) {
|
|
|
9228
9399
|
return;
|
|
9229
9400
|
}
|
|
9230
9401
|
|
|
9231
|
-
|
|
9232
|
-
|
|
9402
|
+
session.background = true;
|
|
9403
|
+
session.suppressEdits = true;
|
|
9233
9404
|
|
|
9234
|
-
if (
|
|
9405
|
+
if (session.messageId && session.chatId) {
|
|
9235
9406
|
try {
|
|
9236
|
-
await deleteDirect(
|
|
9407
|
+
await deleteDirect(session.chatId, session.messageId);
|
|
9237
9408
|
} catch {
|
|
9238
9409
|
/* best effort */
|
|
9239
9410
|
}
|
|
9240
9411
|
}
|
|
9241
|
-
|
|
9242
|
-
if (
|
|
9243
|
-
|
|
9412
|
+
session.messageId = null;
|
|
9413
|
+
if (agentChatId === String(chatId)) {
|
|
9414
|
+
agentMessageId = null;
|
|
9415
|
+
agentChatId = null;
|
|
9244
9416
|
}
|
|
9245
9417
|
|
|
9246
9418
|
await sendReply(
|
|
@@ -9261,25 +9433,26 @@ async function cmdStop(chatId, args) {
|
|
|
9261
9433
|
);
|
|
9262
9434
|
return;
|
|
9263
9435
|
}
|
|
9264
|
-
|
|
9436
|
+
const session = getActiveAgentSession(chatId);
|
|
9437
|
+
if (!session) {
|
|
9265
9438
|
await sendReply(chatId, "No agent is currently running.");
|
|
9266
9439
|
return;
|
|
9267
9440
|
}
|
|
9268
|
-
|
|
9269
|
-
if (
|
|
9441
|
+
session.aborted = true;
|
|
9442
|
+
if (session.abortController) {
|
|
9270
9443
|
try {
|
|
9271
|
-
|
|
9444
|
+
session.abortController.abort("user_stop");
|
|
9272
9445
|
} catch {
|
|
9273
9446
|
/* best effort */
|
|
9274
9447
|
}
|
|
9275
9448
|
}
|
|
9276
|
-
if (
|
|
9277
|
-
|
|
9449
|
+
if (session.actionLog) {
|
|
9450
|
+
session.actionLog.push({
|
|
9278
9451
|
icon: "π",
|
|
9279
9452
|
text: "Stop requested by user (will halt after current step)",
|
|
9280
9453
|
});
|
|
9281
|
-
if (
|
|
9282
|
-
|
|
9454
|
+
if (session.scheduleEdit) {
|
|
9455
|
+
session.scheduleEdit();
|
|
9283
9456
|
}
|
|
9284
9457
|
}
|
|
9285
9458
|
await sendReply(chatId, "π Stop signal sent. Agent will halt and wait.");
|
|
@@ -9294,7 +9467,8 @@ async function cmdSteer(chatId, steerArgs) {
|
|
|
9294
9467
|
}
|
|
9295
9468
|
const message = steerArgs.trim();
|
|
9296
9469
|
|
|
9297
|
-
|
|
9470
|
+
const session = getActiveAgentSession(chatId);
|
|
9471
|
+
if (!session) {
|
|
9298
9472
|
await sendReply(chatId, "No active agent. Sending as a new task.");
|
|
9299
9473
|
await handleFreeText(message, chatId);
|
|
9300
9474
|
return;
|
|
@@ -9302,34 +9476,34 @@ async function cmdSteer(chatId, steerArgs) {
|
|
|
9302
9476
|
|
|
9303
9477
|
const result = await steerPrimaryPrompt(message);
|
|
9304
9478
|
if (result.ok) {
|
|
9305
|
-
if (
|
|
9306
|
-
|
|
9479
|
+
if (session.actionLog) {
|
|
9480
|
+
session.actionLog.push({
|
|
9307
9481
|
icon: "π§",
|
|
9308
9482
|
text: `Steering update delivered (${result.mode})`,
|
|
9309
9483
|
});
|
|
9310
|
-
if (
|
|
9311
|
-
|
|
9484
|
+
if (session.scheduleEdit) {
|
|
9485
|
+
session.scheduleEdit();
|
|
9312
9486
|
}
|
|
9313
9487
|
}
|
|
9314
9488
|
await sendReply(chatId, `π§ Steering sent (${result.mode}).`);
|
|
9315
9489
|
return;
|
|
9316
9490
|
}
|
|
9317
9491
|
|
|
9318
|
-
if (!
|
|
9319
|
-
|
|
9492
|
+
if (!session.followUpQueue) {
|
|
9493
|
+
session.followUpQueue = [];
|
|
9320
9494
|
}
|
|
9321
|
-
|
|
9322
|
-
const qLen =
|
|
9323
|
-
if (
|
|
9495
|
+
session.followUpQueue.push(message);
|
|
9496
|
+
const qLen = session.followUpQueue.length;
|
|
9497
|
+
if (session.actionLog) {
|
|
9324
9498
|
const steerStatus = result.reason || "failed";
|
|
9325
|
-
|
|
9499
|
+
session.actionLog.push({
|
|
9326
9500
|
icon: "π§",
|
|
9327
9501
|
text: `Steering queued (#${qLen}; steer failed: ${steerStatus})`,
|
|
9328
9502
|
kind: "followup_queued",
|
|
9329
9503
|
steerStatus,
|
|
9330
9504
|
});
|
|
9331
|
-
if (
|
|
9332
|
-
|
|
9505
|
+
if (session.scheduleEdit) {
|
|
9506
|
+
session.scheduleEdit();
|
|
9333
9507
|
}
|
|
9334
9508
|
}
|
|
9335
9509
|
await sendReply(chatId, `π§ Steering queued (#${qLen}).`);
|
|
@@ -9454,13 +9628,14 @@ function buildStreamMessage({
|
|
|
9454
9628
|
async function handleFreeText(text, chatId, options = {}) {
|
|
9455
9629
|
const backgroundMode = !!options.background;
|
|
9456
9630
|
const isolatedMode = !!options.isolated;
|
|
9631
|
+
const chatSession = getActiveAgentSession(chatId);
|
|
9457
9632
|
// ββ Follow-up steering: if agent is busy, queue message as follow-up ββ
|
|
9458
|
-
if (!isolatedMode &&
|
|
9459
|
-
if (!
|
|
9460
|
-
|
|
9633
|
+
if (!isolatedMode && chatSession) {
|
|
9634
|
+
if (!chatSession.followUpQueue) {
|
|
9635
|
+
chatSession.followUpQueue = [];
|
|
9461
9636
|
}
|
|
9462
|
-
|
|
9463
|
-
const qLen =
|
|
9637
|
+
chatSession.followUpQueue.push(text);
|
|
9638
|
+
const qLen = chatSession.followUpQueue.length;
|
|
9464
9639
|
|
|
9465
9640
|
// Try immediate steering so the in-flight run can adapt ASAP.
|
|
9466
9641
|
const steerResult = await steerPrimaryPrompt(text);
|
|
@@ -9476,30 +9651,21 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9476
9651
|
);
|
|
9477
9652
|
|
|
9478
9653
|
// Add follow-up indicator to the streaming message
|
|
9479
|
-
if (
|
|
9480
|
-
|
|
9654
|
+
if (chatSession.actionLog) {
|
|
9655
|
+
chatSession.actionLog.push({
|
|
9481
9656
|
icon: "π",
|
|
9482
9657
|
text: `Follow-up: "${text.length > 60 ? text.slice(0, 60) + "β¦" : text}" (${steerNote})`,
|
|
9483
9658
|
kind: "followup_queued",
|
|
9484
9659
|
steerStatus,
|
|
9485
9660
|
});
|
|
9486
9661
|
// Trigger an edit to show the follow-up in the streaming message
|
|
9487
|
-
if (
|
|
9488
|
-
|
|
9662
|
+
if (chatSession.scheduleEdit) {
|
|
9663
|
+
chatSession.scheduleEdit();
|
|
9489
9664
|
}
|
|
9490
9665
|
}
|
|
9491
9666
|
return;
|
|
9492
9667
|
}
|
|
9493
9668
|
|
|
9494
|
-
// ββ Block if agent is busy but no session (shouldn't happen normally) ββ
|
|
9495
|
-
if (!isolatedMode && isPrimaryBusy()) {
|
|
9496
|
-
await sendReply(
|
|
9497
|
-
chatId,
|
|
9498
|
-
"β³ Agent is executing a task. Please wait for it to finish...",
|
|
9499
|
-
);
|
|
9500
|
-
return;
|
|
9501
|
-
}
|
|
9502
|
-
|
|
9503
9669
|
const taskPreview = text.length > 60 ? text.slice(0, 60) + "β¦" : text;
|
|
9504
9670
|
|
|
9505
9671
|
// Send the initial message and capture its ID for editing (unless background)
|
|
@@ -9548,7 +9714,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9548
9714
|
let hadError = false;
|
|
9549
9715
|
|
|
9550
9716
|
const doEdit = async () => {
|
|
9551
|
-
if (backgroundMode ||
|
|
9717
|
+
if (backgroundMode || sessionState.background) return;
|
|
9552
9718
|
editPending = false;
|
|
9553
9719
|
const msg = buildStreamMessage({
|
|
9554
9720
|
taskPreview,
|
|
@@ -9569,7 +9735,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9569
9735
|
};
|
|
9570
9736
|
|
|
9571
9737
|
const scheduleEdit = () => {
|
|
9572
|
-
if (backgroundMode ||
|
|
9738
|
+
if (backgroundMode || sessionState.background) return;
|
|
9573
9739
|
if (editPending) return;
|
|
9574
9740
|
const now = Date.now();
|
|
9575
9741
|
const elapsed = now - lastEditAt;
|
|
@@ -9585,7 +9751,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9585
9751
|
|
|
9586
9752
|
// ββ Set up agent session (enables follow-up steering & bottom-pinning) ββ
|
|
9587
9753
|
const abortController = new AbortController();
|
|
9588
|
-
|
|
9754
|
+
const sessionState = {
|
|
9589
9755
|
chatId,
|
|
9590
9756
|
messageId,
|
|
9591
9757
|
taskPreview,
|
|
@@ -9600,6 +9766,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9600
9766
|
background: backgroundMode,
|
|
9601
9767
|
suppressEdits: backgroundMode,
|
|
9602
9768
|
};
|
|
9769
|
+
setActiveAgentSession(chatId, sessionState);
|
|
9603
9770
|
agentMessageId = messageId;
|
|
9604
9771
|
agentChatId = chatId;
|
|
9605
9772
|
|
|
@@ -9692,17 +9859,17 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9692
9859
|
|
|
9693
9860
|
if (action.phase === "thinking") {
|
|
9694
9861
|
currentThought = action.text;
|
|
9695
|
-
|
|
9862
|
+
sessionState.currentThought = action.text;
|
|
9696
9863
|
} else {
|
|
9697
9864
|
if (action.phase === "done" || action.phase === "running") {
|
|
9698
9865
|
totalActions++;
|
|
9699
|
-
|
|
9866
|
+
sessionState.totalActions = totalActions;
|
|
9700
9867
|
}
|
|
9701
9868
|
actionLog.push(action);
|
|
9702
9869
|
// Keep thought visible while actions proceed (only clear on new non-thinking action)
|
|
9703
9870
|
if (action.phase !== "thinking") {
|
|
9704
9871
|
currentThought = null;
|
|
9705
|
-
|
|
9872
|
+
sessionState.currentThought = null;
|
|
9706
9873
|
}
|
|
9707
9874
|
}
|
|
9708
9875
|
|
|
@@ -9714,7 +9881,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9714
9881
|
} else {
|
|
9715
9882
|
phase = "workingβ¦";
|
|
9716
9883
|
}
|
|
9717
|
-
|
|
9884
|
+
sessionState.phase = phase;
|
|
9718
9885
|
|
|
9719
9886
|
scheduleEdit();
|
|
9720
9887
|
};
|
|
@@ -9736,8 +9903,8 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9736
9903
|
|
|
9737
9904
|
// ββ Process follow-up queue βββββββββββββββββββββββββββββββββββ
|
|
9738
9905
|
// If user sent follow-up messages while agent was working, process them now
|
|
9739
|
-
const followUps =
|
|
9740
|
-
if (followUps.length > 0 && !
|
|
9906
|
+
const followUps = sessionState.followUpQueue || [];
|
|
9907
|
+
if (followUps.length > 0 && !sessionState.aborted) {
|
|
9741
9908
|
for (const followUp of followUps) {
|
|
9742
9909
|
actionLog.push({
|
|
9743
9910
|
icon: "π",
|
|
@@ -9807,7 +9974,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9807
9974
|
searchesDone: searchCount,
|
|
9808
9975
|
statusIcon,
|
|
9809
9976
|
});
|
|
9810
|
-
if (backgroundMode ||
|
|
9977
|
+
if (backgroundMode || sessionState.background) {
|
|
9811
9978
|
await sendReply(chatId, finalMsg);
|
|
9812
9979
|
} else {
|
|
9813
9980
|
const finalMessageId = await editDirect(chatId, messageId, finalMsg);
|
|
@@ -9829,7 +9996,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9829
9996
|
searchesDone: searchCount,
|
|
9830
9997
|
statusIcon: "β",
|
|
9831
9998
|
});
|
|
9832
|
-
if (backgroundMode ||
|
|
9999
|
+
if (backgroundMode || sessionState.background) {
|
|
9833
10000
|
await sendReply(chatId, finalMsg);
|
|
9834
10001
|
} else {
|
|
9835
10002
|
const finalMessageId = await editDirect(chatId, messageId, finalMsg);
|
|
@@ -9839,9 +10006,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9839
10006
|
}
|
|
9840
10007
|
} finally {
|
|
9841
10008
|
// ββ Clean up agent session ββββββββββββββββββββββββββββββββββββ
|
|
9842
|
-
|
|
9843
|
-
agentMessageId = null;
|
|
9844
|
-
agentChatId = null;
|
|
10009
|
+
clearActiveAgentSession(chatId, sessionState);
|
|
9845
10010
|
}
|
|
9846
10011
|
}
|
|
9847
10012
|
|
package/ui/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
|
6
6
|
<meta name="color-scheme" content="dark light" />
|
|
7
7
|
<title>Bosun β Task Orchestrator</title>
|
|
8
|
-
<link rel="icon" href="favicon.png" type="image/png" />
|
|
8
|
+
<link rel="icon" href="/favicon.png" type="image/png" />
|
|
9
9
|
<script>
|
|
10
10
|
(function() {
|
|
11
11
|
var shim = document.createElement("script");
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
32
32
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
33
33
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Sora:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
|
34
|
-
<link rel="stylesheet" href="styles.css" />
|
|
35
|
-
<link rel="stylesheet" href="styles/kanban.css" />
|
|
36
|
-
<link rel="stylesheet" href="styles/sessions.css" />
|
|
34
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
35
|
+
<link rel="stylesheet" href="/styles/kanban.css" />
|
|
36
|
+
<link rel="stylesheet" href="/styles/sessions.css" />
|
|
37
37
|
<!-- Apply stored colour theme before paint to prevent flash -->
|
|
38
38
|
<script>
|
|
39
39
|
try {
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
tick();
|
|
136
136
|
});
|
|
137
137
|
const loadApp = async (bust = false) => {
|
|
138
|
-
const base = new URL("app.js",
|
|
138
|
+
const base = new URL("/app.js", window.location.origin).toString();
|
|
139
139
|
const url = bust ? `${base}?v=${Date.now()}` : base;
|
|
140
140
|
if (window.importShim) {
|
|
141
141
|
return window.importShim(url);
|