@lelouchhe/webagent 0.7.0 → 0.8.0
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/index.html +14 -5
- package/dist/js/app.XBFXH37R.js +2 -0
- package/dist/js/chunk.CT5WBNGZ.js +173 -0
- package/dist/js/viewer.CVWXSKJM.js +1 -0
- package/dist/login.html +1 -1
- package/dist/share-viewer.html +3 -3
- package/dist/{styles.01ky3cex.css → styles.00etlpgs.css} +94 -4
- package/dist/sw.js +19 -9
- package/lib/bridge.js +49 -11
- package/lib/event-handler.js +60 -15
- package/lib/message-cleanup.js +5 -4
- package/lib/routes.js +199 -44
- package/lib/server.js +4 -3
- package/lib/session-manager.js +170 -22
- package/lib/session-state.js +70 -5
- package/lib/sse-manager.js +31 -7
- package/lib/store.js +25 -23
- package/package.json +1 -1
- package/dist/js/app.KFQAFHA3.js +0 -4
- package/dist/js/chunk.3CCHZCG7.js +0 -173
- package/dist/js/viewer.T32M5AZZ.js +0 -1
package/lib/routes.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join, extname, basename } from "node:path";
|
|
|
4
4
|
import { gzipSync } from "node:zlib";
|
|
5
5
|
import busboy from "busboy";
|
|
6
6
|
import { errorMessage, MessageIngressSchema } from "./types.js";
|
|
7
|
-
import { interruptBashProc } from "./session-manager.js";
|
|
7
|
+
import { interruptBashProc, InvalidSessionDirectoryError, } from "./session-manager.js";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { createWriteStream } from "node:fs";
|
|
10
10
|
import { handleShareRoutes } from "./share/routes.js";
|
|
@@ -22,6 +22,12 @@ import { readImageDimensions } from "./image-dimensions.js";
|
|
|
22
22
|
import { HTTP_STATUS } from "./http-status.js";
|
|
23
23
|
const IS_WIN = process.platform === "win32";
|
|
24
24
|
const SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
25
|
+
function broadcastInboxCount(store, sseManager) {
|
|
26
|
+
sseManager.broadcastGlobal({
|
|
27
|
+
type: "inbox_count_changed",
|
|
28
|
+
pendingCount: store.countUnprocessed(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
25
31
|
const MIME = {
|
|
26
32
|
".html": "text/html; charset=utf-8",
|
|
27
33
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -736,37 +742,85 @@ export function createRequestHandler(deps) {
|
|
|
736
742
|
json(res, HTTP_STATUS.NOT_FOUND, { error: "Session not found" });
|
|
737
743
|
return;
|
|
738
744
|
}
|
|
739
|
-
const bridge = getBridge?.();
|
|
740
|
-
if (!bridge) {
|
|
741
|
-
json(res, HTTP_STATUS.SERVICE_UNAVAILABLE, {
|
|
742
|
-
error: "Agent not ready yet",
|
|
743
|
-
});
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
745
|
const { opId, replayed } = tryReplayClientOp(req, res, store, sessionId);
|
|
747
746
|
if (replayed)
|
|
748
747
|
return;
|
|
748
|
+
const hadAgentPrompt = sessions?.activePrompts.has(sessionId) ?? false;
|
|
749
|
+
const hadPendingPrompt = sessions?.cancelPendingPromptSubmission(sessionId) ?? false;
|
|
750
|
+
const hadBash = sessions?.runningBashProcs.has(sessionId) ?? false;
|
|
751
|
+
if (!hadAgentPrompt && !hadPendingPrompt && !hadBash) {
|
|
752
|
+
const idleBody = { ok: true, status: "idle" };
|
|
753
|
+
saveClientOpResult(store, opId, sessionId, HTTP_STATUS.OK, idleBody);
|
|
754
|
+
json(res, HTTP_STATUS.OK, idleBody);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const cancelledPromptId = hadAgentPrompt
|
|
758
|
+
? (sessions?.state.getState(sessionId).runtime.busy?.promptId ?? null)
|
|
759
|
+
: null;
|
|
749
760
|
// Kill running bash process if any
|
|
750
761
|
const proc = sessions?.runningBashProcs.get(sessionId);
|
|
751
762
|
if (proc) {
|
|
752
|
-
|
|
753
|
-
|
|
763
|
+
const force = sessions.interruptedBashProcs.has(proc);
|
|
764
|
+
interruptBashProc(proc, force);
|
|
765
|
+
sessions.interruptedBashProcs.add(proc);
|
|
754
766
|
}
|
|
755
|
-
|
|
756
|
-
if (
|
|
767
|
+
const bridge = hadAgentPrompt ? getBridge?.() : null;
|
|
768
|
+
if (hadAgentPrompt && !bridge) {
|
|
769
|
+
json(res, HTTP_STATUS.SERVICE_UNAVAILABLE, {
|
|
770
|
+
error: "Agent not ready yet",
|
|
771
|
+
});
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
// ACP cancel is a notification, not an acknowledgement. Keep the
|
|
775
|
+
// prompt active until its prompt response supplies the terminal stop
|
|
776
|
+
// reason, and allow repeated requests to resend the notification.
|
|
777
|
+
if (hadAgentPrompt && sessions && bridge) {
|
|
778
|
+
const previousCancelStatus = sessions.state.getState(sessionId).runtime.busy?.cancelStatus ??
|
|
779
|
+
null;
|
|
780
|
+
rlog.info("cancel requested", {
|
|
781
|
+
sessionId: sessionId.slice(0, 8),
|
|
782
|
+
retry: previousCancelStatus !== null,
|
|
783
|
+
previousStatus: previousCancelStatus,
|
|
784
|
+
});
|
|
757
785
|
await bridge.cancel(sessionId);
|
|
758
|
-
sessions.
|
|
786
|
+
const busy = sessions.state.getState(sessionId).runtime.busy;
|
|
787
|
+
const stillCancellingSamePrompt = sessions.activePrompts.has(sessionId) &&
|
|
788
|
+
busy?.kind === "agent" &&
|
|
789
|
+
busy.promptId === cancelledPromptId;
|
|
790
|
+
if (stillCancellingSamePrompt) {
|
|
791
|
+
sessions.state.markCancelRequested(sessionId);
|
|
792
|
+
}
|
|
759
793
|
}
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
// the old frontend-side cancel timer.
|
|
794
|
+
// If prompt_done does not arrive, expose the lack of acknowledgement
|
|
795
|
+
// instead of pretending the prompt stopped.
|
|
763
796
|
const cancelTimeout = deps.limits.cancel_timeout ?? 0;
|
|
764
|
-
|
|
797
|
+
const busyAfterCancel = sessions?.state.getState(sessionId).runtime.busy;
|
|
798
|
+
const cancelPending = hadAgentPrompt &&
|
|
799
|
+
sessions?.activePrompts.has(sessionId) === true &&
|
|
800
|
+
busyAfterCancel?.kind === "agent" &&
|
|
801
|
+
busyAfterCancel.promptId === cancelledPromptId;
|
|
802
|
+
if (cancelPending && cancelTimeout > 0)
|
|
765
803
|
sessions.state.armCancelSafety(sessionId, cancelTimeout);
|
|
766
804
|
sessions?.syncBusy(sessionId);
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
805
|
+
const workPending = cancelPending || hadBash;
|
|
806
|
+
const replacementPromptActive = hadAgentPrompt &&
|
|
807
|
+
((sessions?.activePrompts.has(sessionId) === true &&
|
|
808
|
+
busyAfterCancel?.kind === "agent" &&
|
|
809
|
+
busyAfterCancel.promptId !== cancelledPromptId) ||
|
|
810
|
+
sessions?.pendingPromptSubmissions.has(sessionId) === true);
|
|
811
|
+
const status = workPending || replacementPromptActive
|
|
812
|
+
? HTTP_STATUS.ACCEPTED
|
|
813
|
+
: HTTP_STATUS.OK;
|
|
814
|
+
const okBody = {
|
|
815
|
+
ok: true,
|
|
816
|
+
status: workPending
|
|
817
|
+
? "cancelling"
|
|
818
|
+
: replacementPromptActive
|
|
819
|
+
? "superseded"
|
|
820
|
+
: "cancelled",
|
|
821
|
+
};
|
|
822
|
+
saveClientOpResult(store, opId, sessionId, status, okBody);
|
|
823
|
+
json(res, status, okBody);
|
|
770
824
|
return;
|
|
771
825
|
}
|
|
772
826
|
// --- GET /api/v1/sessions/:id/status ---
|
|
@@ -888,11 +942,43 @@ export function createRequestHandler(deps) {
|
|
|
888
942
|
const { opId, replayed } = tryReplayClientOp(req, res, store, sessionId);
|
|
889
943
|
if (replayed)
|
|
890
944
|
return;
|
|
945
|
+
const promptSubmissionId = sessions.reservePromptSubmission(sessionId);
|
|
946
|
+
if (promptSubmissionId === null) {
|
|
947
|
+
const busyKind = sessions.getBusyKind(sessionId);
|
|
948
|
+
logPromptRejectBeforeSave({
|
|
949
|
+
sessionId,
|
|
950
|
+
status: HTTP_STATUS.CONFLICT,
|
|
951
|
+
reason: "session_busy",
|
|
952
|
+
opId,
|
|
953
|
+
busyKind: busyKind ?? undefined,
|
|
954
|
+
});
|
|
955
|
+
json(res, HTTP_STATUS.CONFLICT, {
|
|
956
|
+
error: "Session is busy",
|
|
957
|
+
busyKind,
|
|
958
|
+
});
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const requestState = { aborted: false };
|
|
962
|
+
const isRequestAborted = () => requestState.aborted;
|
|
963
|
+
const abortPromptSubmission = () => {
|
|
964
|
+
requestState.aborted = true;
|
|
965
|
+
sessions.releasePromptSubmission(sessionId, promptSubmissionId);
|
|
966
|
+
};
|
|
967
|
+
res.once("finish", () => {
|
|
968
|
+
sessions.releasePromptSubmission(sessionId, promptSubmissionId);
|
|
969
|
+
});
|
|
970
|
+
req.once("aborted", abortPromptSubmission);
|
|
971
|
+
res.once("close", () => {
|
|
972
|
+
if (!res.writableEnded)
|
|
973
|
+
abortPromptSubmission();
|
|
974
|
+
});
|
|
891
975
|
// Ensure session is live in ACP before prompting (awaits in-flight resume)
|
|
892
976
|
try {
|
|
893
977
|
await sessions.ensureResumed(bridge, sessionId);
|
|
894
978
|
}
|
|
895
979
|
catch (err) {
|
|
980
|
+
if (isRequestAborted())
|
|
981
|
+
return;
|
|
896
982
|
logPromptRejectBeforeSave({
|
|
897
983
|
sessionId,
|
|
898
984
|
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
@@ -905,19 +991,16 @@ export function createRequestHandler(deps) {
|
|
|
905
991
|
});
|
|
906
992
|
return;
|
|
907
993
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (busyKind) {
|
|
994
|
+
if (isRequestAborted() ||
|
|
995
|
+
sessions.isPromptSubmissionCancelled(promptSubmissionId)) {
|
|
911
996
|
logPromptRejectBeforeSave({
|
|
912
997
|
sessionId,
|
|
913
998
|
status: HTTP_STATUS.CONFLICT,
|
|
914
|
-
reason: "
|
|
999
|
+
reason: "prompt_cancelled_before_start",
|
|
915
1000
|
opId,
|
|
916
|
-
busyKind,
|
|
917
1001
|
});
|
|
918
1002
|
json(res, HTTP_STATUS.CONFLICT, {
|
|
919
|
-
error: "
|
|
920
|
-
busyKind,
|
|
1003
|
+
error: "Prompt was cancelled before start",
|
|
921
1004
|
});
|
|
922
1005
|
return;
|
|
923
1006
|
}
|
|
@@ -926,6 +1009,8 @@ export function createRequestHandler(deps) {
|
|
|
926
1009
|
body = JSON.parse(await readBody(req));
|
|
927
1010
|
}
|
|
928
1011
|
catch {
|
|
1012
|
+
if (isRequestAborted())
|
|
1013
|
+
return;
|
|
929
1014
|
logPromptRejectBeforeSave({
|
|
930
1015
|
sessionId,
|
|
931
1016
|
status: HTTP_STATUS.BAD_REQUEST,
|
|
@@ -935,6 +1020,13 @@ export function createRequestHandler(deps) {
|
|
|
935
1020
|
json(res, HTTP_STATUS.BAD_REQUEST, { error: "Invalid JSON" });
|
|
936
1021
|
return;
|
|
937
1022
|
}
|
|
1023
|
+
if (isRequestAborted() ||
|
|
1024
|
+
sessions.isPromptSubmissionCancelled(promptSubmissionId)) {
|
|
1025
|
+
json(res, HTTP_STATUS.CONFLICT, {
|
|
1026
|
+
error: "Prompt was cancelled before start",
|
|
1027
|
+
});
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
938
1030
|
if (!body.text) {
|
|
939
1031
|
logPromptRejectBeforeSave({
|
|
940
1032
|
sessionId,
|
|
@@ -1057,8 +1149,10 @@ export function createRequestHandler(deps) {
|
|
|
1057
1149
|
},
|
|
1058
1150
|
];
|
|
1059
1151
|
});
|
|
1152
|
+
const eventClientOpId = opId ?? randomUUID();
|
|
1060
1153
|
store.saveEvent(sessionId, "user_message", {
|
|
1061
1154
|
text: body.text,
|
|
1155
|
+
clientOpId: eventClientOpId,
|
|
1062
1156
|
...(storedAttachments?.length
|
|
1063
1157
|
? { attachments: storedAttachments }
|
|
1064
1158
|
: {}),
|
|
@@ -1069,6 +1163,7 @@ export function createRequestHandler(deps) {
|
|
|
1069
1163
|
type: "user_message",
|
|
1070
1164
|
sessionId,
|
|
1071
1165
|
text: body.text,
|
|
1166
|
+
clientOpId: eventClientOpId,
|
|
1072
1167
|
attachments: storedAttachments,
|
|
1073
1168
|
};
|
|
1074
1169
|
sseManager.broadcast(userMsgEvent);
|
|
@@ -1086,14 +1181,21 @@ export function createRequestHandler(deps) {
|
|
|
1086
1181
|
});
|
|
1087
1182
|
}
|
|
1088
1183
|
// Fire prompt asynchronously (don't await — response is 202)
|
|
1184
|
+
sessions.releasePromptSubmission(sessionId, promptSubmissionId, false);
|
|
1089
1185
|
sessions.activePrompts.add(sessionId);
|
|
1090
1186
|
sessions.syncBusy(sessionId);
|
|
1187
|
+
const promptId = sessions.state.getState(sessionId).runtime.busy?.promptId ??
|
|
1188
|
+
undefined;
|
|
1091
1189
|
bridge
|
|
1092
|
-
.prompt(sessionId, agentText, attachments)
|
|
1190
|
+
.prompt(sessionId, agentText, attachments, promptId)
|
|
1093
1191
|
.catch((err) => {
|
|
1094
1192
|
plog.error("error", { sessionId, error: err });
|
|
1095
1193
|
})
|
|
1096
1194
|
.finally(() => {
|
|
1195
|
+
// A turn that outlived its own supersession must not clear the
|
|
1196
|
+
// busy state of the turn that replaced it.
|
|
1197
|
+
if (!sessions.isCurrentPrompt(sessionId, promptId))
|
|
1198
|
+
return;
|
|
1097
1199
|
sessions.activePrompts.delete(sessionId);
|
|
1098
1200
|
sessions.syncBusy(sessionId);
|
|
1099
1201
|
});
|
|
@@ -1460,6 +1562,12 @@ export function createRequestHandler(deps) {
|
|
|
1460
1562
|
json(res, HTTP_STATUS.NOT_FOUND, { error: "Session not found" });
|
|
1461
1563
|
return;
|
|
1462
1564
|
}
|
|
1565
|
+
if (sessions && sessions.getBusyKind(sessionId) !== null) {
|
|
1566
|
+
json(res, HTTP_STATUS.CONFLICT, {
|
|
1567
|
+
error: "Cancel active work before deleting the session",
|
|
1568
|
+
});
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1463
1571
|
if (sessions) {
|
|
1464
1572
|
sessions.deleteSession(sessionId);
|
|
1465
1573
|
}
|
|
@@ -1474,6 +1582,7 @@ export function createRequestHandler(deps) {
|
|
|
1474
1582
|
}
|
|
1475
1583
|
// POST /api/v1/sessions (create new session)
|
|
1476
1584
|
if (url === "/api/v1/sessions" && req.method === "POST") {
|
|
1585
|
+
const clientOpId = getClientOpId(req);
|
|
1477
1586
|
const bridge = getBridge?.();
|
|
1478
1587
|
if (!bridge) {
|
|
1479
1588
|
json(res, HTTP_STATUS.SERVICE_UNAVAILABLE, {
|
|
@@ -1506,6 +1615,7 @@ export function createRequestHandler(deps) {
|
|
|
1506
1615
|
title: session?.title,
|
|
1507
1616
|
configOptions,
|
|
1508
1617
|
agentCommands: sessions.getAgentCommands(sessionId),
|
|
1618
|
+
clientOpId: clientOpId ?? undefined,
|
|
1509
1619
|
};
|
|
1510
1620
|
sseManager.broadcast(sessionCreatedEvent);
|
|
1511
1621
|
// ACP's session_created event fires before inheritance runs, so
|
|
@@ -1524,11 +1634,12 @@ export function createRequestHandler(deps) {
|
|
|
1524
1634
|
source: session?.source ?? source,
|
|
1525
1635
|
configOptions,
|
|
1526
1636
|
agentCommands: sessions.getAgentCommands(sessionId),
|
|
1637
|
+
clientOpId: clientOpId ?? undefined,
|
|
1527
1638
|
});
|
|
1528
1639
|
}
|
|
1529
1640
|
catch (err) {
|
|
1530
1641
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1531
|
-
if (
|
|
1642
|
+
if (err instanceof InvalidSessionDirectoryError) {
|
|
1532
1643
|
json(res, HTTP_STATUS.BAD_REQUEST, { error: msg });
|
|
1533
1644
|
}
|
|
1534
1645
|
else {
|
|
@@ -1563,14 +1674,15 @@ export function createRequestHandler(deps) {
|
|
|
1563
1674
|
let streamingThinking = false;
|
|
1564
1675
|
let streamingAssistant = false;
|
|
1565
1676
|
if (sessions) {
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1677
|
+
const runtimeStreaming = sessions.state.peekStreaming(sessionId);
|
|
1678
|
+
streamingThinking =
|
|
1679
|
+
runtimeStreaming.thinking ||
|
|
1680
|
+
Boolean(sessions.thinkingBuffers.get(sessionId));
|
|
1681
|
+
streamingAssistant =
|
|
1682
|
+
runtimeStreaming.assistant ||
|
|
1683
|
+
Boolean(sessions.assistantBuffers.get(sessionId));
|
|
1684
|
+
sessions.flushThinkingBuffer(sessionId);
|
|
1685
|
+
sessions.flushAssistantBuffer(sessionId);
|
|
1574
1686
|
}
|
|
1575
1687
|
const events = store.getEvents(sessionId, {
|
|
1576
1688
|
excludeThinking,
|
|
@@ -1653,6 +1765,7 @@ export function createRequestHandler(deps) {
|
|
|
1653
1765
|
type: "connected",
|
|
1654
1766
|
clientId,
|
|
1655
1767
|
debugLevel: deps.debugLevel ?? "off",
|
|
1768
|
+
pendingCount: store.countUnprocessed(),
|
|
1656
1769
|
});
|
|
1657
1770
|
sseManager.writeHeartbeat(client);
|
|
1658
1771
|
return;
|
|
@@ -1696,6 +1809,7 @@ export function createRequestHandler(deps) {
|
|
|
1696
1809
|
type: "connected",
|
|
1697
1810
|
clientId,
|
|
1698
1811
|
debugLevel: deps.debugLevel ?? "off",
|
|
1812
|
+
pendingCount: store.countUnprocessed(),
|
|
1699
1813
|
});
|
|
1700
1814
|
sseManager.writeHeartbeat(client);
|
|
1701
1815
|
// Replay events from Last-Event-ID if provided
|
|
@@ -1906,6 +2020,7 @@ export function createRequestHandler(deps) {
|
|
|
1906
2020
|
created_at: Date.now(),
|
|
1907
2021
|
});
|
|
1908
2022
|
sseManager.broadcast({ type: "message_created", messageId: id });
|
|
2023
|
+
broadcastInboxCount(store, sseManager);
|
|
1909
2024
|
if (deps.pushService) {
|
|
1910
2025
|
void deps.pushService.sendForMessage({
|
|
1911
2026
|
id,
|
|
@@ -1937,23 +2052,58 @@ export function createRequestHandler(deps) {
|
|
|
1937
2052
|
const consumeMatch = tail.match(/^([^/?]+)\/consume\/?$/);
|
|
1938
2053
|
if (consumeMatch && req.method === "POST") {
|
|
1939
2054
|
const id = decodeURIComponent(consumeMatch[1]);
|
|
1940
|
-
|
|
2055
|
+
let inheritFromSessionId;
|
|
2056
|
+
try {
|
|
2057
|
+
const rawBody = await readBody(req);
|
|
2058
|
+
if (rawBody) {
|
|
2059
|
+
const body = JSON.parse(rawBody);
|
|
2060
|
+
if (body.inheritFromSessionId !== undefined &&
|
|
2061
|
+
typeof body.inheritFromSessionId !== "string") {
|
|
2062
|
+
json(res, HTTP_STATUS.BAD_REQUEST, {
|
|
2063
|
+
error: "inheritFromSessionId must be a string",
|
|
2064
|
+
});
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
inheritFromSessionId = body.inheritFromSessionId;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
catch {
|
|
2071
|
+
json(res, HTTP_STATUS.BAD_REQUEST, { error: "Invalid JSON" });
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const bridge = getBridge?.();
|
|
2075
|
+
if (!sessions || !bridge) {
|
|
2076
|
+
json(res, HTTP_STATUS.SERVICE_UNAVAILABLE, {
|
|
2077
|
+
error: "Agent not available",
|
|
2078
|
+
});
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
1941
2081
|
let out;
|
|
1942
2082
|
try {
|
|
1943
|
-
out =
|
|
2083
|
+
out = await sessions.consumeMessage(bridge, id, inheritFromSessionId);
|
|
1944
2084
|
}
|
|
1945
2085
|
catch (err) {
|
|
1946
|
-
if (
|
|
1947
|
-
json(res, HTTP_STATUS.
|
|
2086
|
+
if (err instanceof InvalidSessionDirectoryError) {
|
|
2087
|
+
json(res, HTTP_STATUS.BAD_REQUEST, {
|
|
2088
|
+
error: err.message,
|
|
2089
|
+
});
|
|
1948
2090
|
return;
|
|
1949
2091
|
}
|
|
1950
|
-
|
|
2092
|
+
json(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, {
|
|
2093
|
+
error: errorMessage(err),
|
|
2094
|
+
});
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
if (!out) {
|
|
2098
|
+
json(res, HTTP_STATUS.NOT_FOUND, { error: "Message not found" });
|
|
2099
|
+
return;
|
|
1951
2100
|
}
|
|
1952
2101
|
sseManager.broadcast({
|
|
1953
2102
|
type: "message_consumed",
|
|
1954
2103
|
messageId: id,
|
|
1955
2104
|
sessionId: out.sessionId,
|
|
1956
2105
|
});
|
|
2106
|
+
broadcastInboxCount(store, sseManager);
|
|
1957
2107
|
if (!out.alreadyConsumed && deps.pushService) {
|
|
1958
2108
|
void deps.pushService.sendClose(id);
|
|
1959
2109
|
}
|
|
@@ -1980,6 +2130,7 @@ export function createRequestHandler(deps) {
|
|
|
1980
2130
|
return;
|
|
1981
2131
|
}
|
|
1982
2132
|
sseManager.broadcast({ type: "message_acked", messageId: id });
|
|
2133
|
+
broadcastInboxCount(store, sseManager);
|
|
1983
2134
|
if (deps.pushService)
|
|
1984
2135
|
void deps.pushService.sendClose(id);
|
|
1985
2136
|
mlog.info("ack", { msg_id: id });
|
|
@@ -2051,10 +2202,14 @@ export function createRequestHandler(deps) {
|
|
|
2051
2202
|
sseManager.broadcast(titleEvent);
|
|
2052
2203
|
});
|
|
2053
2204
|
}
|
|
2205
|
+
const betaPromptId = sessions.state.getState(sessionId).runtime.busy?.promptId ??
|
|
2206
|
+
undefined;
|
|
2054
2207
|
bridge
|
|
2055
|
-
.prompt(sessionId, text)
|
|
2208
|
+
.prompt(sessionId, text, undefined, betaPromptId)
|
|
2056
2209
|
.catch(() => { })
|
|
2057
2210
|
.finally(() => {
|
|
2211
|
+
if (!sessions.isCurrentPrompt(sessionId, betaPromptId))
|
|
2212
|
+
return;
|
|
2058
2213
|
sessions.activePrompts.delete(sessionId);
|
|
2059
2214
|
sessions.syncBusy(sessionId);
|
|
2060
2215
|
});
|
|
@@ -2233,7 +2388,7 @@ export function createRequestHandler(deps) {
|
|
|
2233
2388
|
return;
|
|
2234
2389
|
}
|
|
2235
2390
|
// --- Static files ---
|
|
2236
|
-
let staticPath = url;
|
|
2391
|
+
let staticPath = url.split("?")[0] ?? url;
|
|
2237
2392
|
const htmlEntry = HTML_ENTRYPOINTS.find((e) => e.urlPath === staticPath);
|
|
2238
2393
|
if (htmlEntry)
|
|
2239
2394
|
staticPath = "/" + htmlEntry.file;
|
package/lib/server.js
CHANGED
|
@@ -104,7 +104,9 @@ sessions.state.onPatch((event) => {
|
|
|
104
104
|
sseManager.broadcast(event);
|
|
105
105
|
});
|
|
106
106
|
let bridge = null;
|
|
107
|
-
|
|
107
|
+
const messageCleanup = startMessageCleanup(store, config.messages.unprocessed_ttl_days, (pendingCount) => {
|
|
108
|
+
sseManager.broadcastGlobal({ type: "inbox_count_changed", pendingCount });
|
|
109
|
+
});
|
|
108
110
|
let sharePreviewCleanup = null;
|
|
109
111
|
// --- HTTP server ---
|
|
110
112
|
const server = createServer((req, res) => {
|
|
@@ -153,7 +155,7 @@ async function initBridge(agentCmd) {
|
|
|
153
155
|
async function shutdown() {
|
|
154
156
|
console.log("\n[server] shutting down...");
|
|
155
157
|
sseManager.stopHeartbeat();
|
|
156
|
-
messageCleanup
|
|
158
|
+
messageCleanup.stop();
|
|
157
159
|
sharePreviewCleanup?.stop();
|
|
158
160
|
sessions.killAllBashProcs();
|
|
159
161
|
await bridge?.shutdown();
|
|
@@ -185,7 +187,6 @@ server.listen(config.port, config.host, () => {
|
|
|
185
187
|
// will use. If the gate ran, auth.json exists and has ≥ 1 token.
|
|
186
188
|
await authStore.load();
|
|
187
189
|
console.log(`[server] listening on http://localhost:${config.port}`);
|
|
188
|
-
messageCleanup = startMessageCleanup(store, config.messages.unprocessed_ttl_days);
|
|
189
190
|
if (config.share.enabled) {
|
|
190
191
|
sharePreviewCleanup = startSharePreviewCleanup(store);
|
|
191
192
|
console.log(`[share] preview gc armed (24h interval)`);
|