@jait/gateway 0.1.518 → 0.1.520
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.d.ts.map +1 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/remote-cli-provider.d.ts.map +1 -1
- package/dist/providers/remote-cli-provider.js +44 -5
- package/dist/providers/remote-cli-provider.js.map +1 -1
- package/dist/routes/chat.d.ts.map +1 -1
- package/dist/routes/chat.js +157 -60
- package/dist/routes/chat.js.map +1 -1
- package/dist/routes/terminals.d.ts.map +1 -1
- package/dist/routes/terminals.js +23 -2
- package/dist/routes/terminals.js.map +1 -1
- package/dist/services/primary-link.d.ts +2 -0
- package/dist/services/primary-link.d.ts.map +1 -1
- package/dist/services/primary-link.js +81 -0
- package/dist/services/primary-link.js.map +1 -1
- package/dist/surfaces/index.d.ts +1 -0
- package/dist/surfaces/index.d.ts.map +1 -1
- package/dist/surfaces/index.js +1 -0
- package/dist/surfaces/index.js.map +1 -1
- package/dist/surfaces/remote-terminal.d.ts +44 -0
- package/dist/surfaces/remote-terminal.d.ts.map +1 -0
- package/dist/surfaces/remote-terminal.js +157 -0
- package/dist/surfaces/remote-terminal.js.map +1 -0
- package/dist/tools/agent-loop.d.ts +8 -2
- package/dist/tools/agent-loop.d.ts.map +1 -1
- package/dist/tools/agent-loop.js +19 -12
- package/dist/tools/agent-loop.js.map +1 -1
- package/dist/ws.d.ts +16 -0
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +89 -0
- package/dist/ws.js.map +1 -1
- package/package.json +1 -1
- package/web-dist/assets/{_basePickBy-4GXZ6Ixc.js → _basePickBy-D97xdOAU.js} +1 -1
- package/web-dist/assets/{_baseUniq-C8rcHDOr.js → _baseUniq-WW7JAj1l.js} +1 -1
- package/web-dist/assets/{arc-Ighm_O0d.js → arc-3mhc1c6Z.js} +1 -1
- package/web-dist/assets/{architectureDiagram-2XIMDMQ5-BoQ0ku_A.js → architectureDiagram-2XIMDMQ5-BNi--qrb.js} +1 -1
- package/web-dist/assets/{blockDiagram-WCTKOSBZ-D6l3Qis1.js → blockDiagram-WCTKOSBZ-GRQWMiTX.js} +1 -1
- package/web-dist/assets/{c4Diagram-IC4MRINW-Cnjd1IUe.js → c4Diagram-IC4MRINW-C42ZAGzO.js} +1 -1
- package/web-dist/assets/channel-C8_OwGkq.js +1 -0
- package/web-dist/assets/{chunk-4BX2VUAB-PS_u7IOQ.js → chunk-4BX2VUAB-DBagJPoH.js} +1 -1
- package/web-dist/assets/{chunk-55IACEB6-C6pB5keO.js → chunk-55IACEB6-Bb2d5l7K.js} +1 -1
- package/web-dist/assets/{chunk-FMBD7UC4-CN3x1VQ0.js → chunk-FMBD7UC4-CS4028UO.js} +1 -1
- package/web-dist/assets/{chunk-JSJVCQXG-KHgB7WcM.js → chunk-JSJVCQXG-pEPkRBug.js} +1 -1
- package/web-dist/assets/{chunk-KX2RTZJC-hrFhSS46.js → chunk-KX2RTZJC-DPZPReEB.js} +1 -1
- package/web-dist/assets/{chunk-NQ4KR5QH-DxnqGGgs.js → chunk-NQ4KR5QH-CxkV1_9p.js} +1 -1
- package/web-dist/assets/{chunk-QZHKN3VN-G6IBm1-B.js → chunk-QZHKN3VN-CwRRfkgR.js} +1 -1
- package/web-dist/assets/{chunk-WL4C6EOR-CCbc-Xur.js → chunk-WL4C6EOR-fFqRBlWe.js} +1 -1
- package/web-dist/assets/classDiagram-VBA2DB6C-C78vWo8s.js +1 -0
- package/web-dist/assets/classDiagram-v2-RAHNMMFH-C78vWo8s.js +1 -0
- package/web-dist/assets/clone-Cyv8PlV4.js +1 -0
- package/web-dist/assets/{cose-bilkent-S5V4N54A-CQB1kRU6.js → cose-bilkent-S5V4N54A-DN3v9QU8.js} +1 -1
- package/web-dist/assets/{dagre-KLK3FWXG-DrmxWwMD.js → dagre-KLK3FWXG-MbLMo82j.js} +1 -1
- package/web-dist/assets/{diagram-E7M64L7V-2voGx62g.js → diagram-E7M64L7V--9bssKST.js} +1 -1
- package/web-dist/assets/{diagram-IFDJBPK2-Bva2BWRv.js → diagram-IFDJBPK2-6cDHg5fI.js} +1 -1
- package/web-dist/assets/{diagram-P4PSJMXO-Brc9UEH9.js → diagram-P4PSJMXO-CnppAHOs.js} +1 -1
- package/web-dist/assets/{erDiagram-INFDFZHY-BEWpAybG.js → erDiagram-INFDFZHY-TLp6z50h.js} +1 -1
- package/web-dist/assets/{flowDiagram-PKNHOUZH-Hz_dbESe.js → flowDiagram-PKNHOUZH-DWGC6P2V.js} +1 -1
- package/web-dist/assets/{ganttDiagram-A5KZAMGK-Cm9_LZlr.js → ganttDiagram-A5KZAMGK-xRTztCVN.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-K3NZZRJ6-BzAOIXCz.js → gitGraphDiagram-K3NZZRJ6-C71gFMh3.js} +1 -1
- package/web-dist/assets/{graph-CJOpfjgA.js → graph-llADkI2L.js} +1 -1
- package/web-dist/assets/{index-DRBaHTGk.css → index-BZrUCLxt.css} +1 -1
- package/web-dist/assets/{index-C61DE3ai.js → index-CdADtvED.js} +1 -1
- package/web-dist/assets/{index-Zx1B8JUl.js → index-DF1xQK9v.js} +374 -374
- package/web-dist/assets/{index-Ct5S701c.js → index-qskLMmip.js} +1 -1
- package/web-dist/assets/{infoDiagram-LFFYTUFH-Bt7Hd4ca.js → infoDiagram-LFFYTUFH-4auIkcGV.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-PHBUUO56-kRYwr5j6.js → ishikawaDiagram-PHBUUO56-QNl96mQj.js} +1 -1
- package/web-dist/assets/{journeyDiagram-4ABVD52K-D62phOj4.js → journeyDiagram-4ABVD52K-CRkJoa9h.js} +1 -1
- package/web-dist/assets/{kanban-definition-K7BYSVSG-CDphYUb1.js → kanban-definition-K7BYSVSG-Bfoj75nt.js} +1 -1
- package/web-dist/assets/{layout-BVxY5QRl.js → layout-ZxnBXXOL.js} +1 -1
- package/web-dist/assets/{linear-CVsEUrat.js → linear-BkTIMqg7.js} +1 -1
- package/web-dist/assets/{mindmap-definition-YRQLILUH-BMKbEMM2.js → mindmap-definition-YRQLILUH-2y6NaKAS.js} +1 -1
- package/web-dist/assets/{pieDiagram-SKSYHLDU-CeASSviv.js → pieDiagram-SKSYHLDU-C7mUVJbX.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-337W2JSQ-DyyBTffn.js → quadrantDiagram-337W2JSQ-CdZLSE27.js} +1 -1
- package/web-dist/assets/{requirementDiagram-Z7DCOOCP-CtNWH2d-.js → requirementDiagram-Z7DCOOCP-D1ICfZ5y.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-WA2Y5GQK-BKwJNuto.js → sankeyDiagram-WA2Y5GQK-B5xjy5zy.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-2WXFIKYE-CKaypO_N.js → sequenceDiagram-2WXFIKYE-rUHHa6Ef.js} +1 -1
- package/web-dist/assets/{stateDiagram-RAJIS63D-DPg4aXWw.js → stateDiagram-RAJIS63D-B0snaZqf.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BYlXc09O.js +1 -0
- package/web-dist/assets/{timeline-definition-YZTLITO2-C-Uv37-w.js → timeline-definition-YZTLITO2-YTe8muSH.js} +1 -1
- package/web-dist/assets/{treemap-KZPCXAKY-CTpBRroI.js → treemap-KZPCXAKY-BaJ0_K6k.js} +1 -1
- package/web-dist/assets/{vennDiagram-LZ73GAT5-BpbnxQAX.js → vennDiagram-LZ73GAT5-Clg7FJSd.js} +1 -1
- package/web-dist/assets/{xychartDiagram-JWTSCODW-B4A0fzY8.js → xychartDiagram-JWTSCODW-CN5F0XWs.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/channel-Bb2O48C6.js +0 -1
- package/web-dist/assets/classDiagram-VBA2DB6C-ByArUuoT.js +0 -1
- package/web-dist/assets/classDiagram-v2-RAHNMMFH-ByArUuoT.js +0 -1
- package/web-dist/assets/clone-R6yqfY1r.js +0 -1
- package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BJoc4OMi.js +0 -1
package/dist/routes/chat.js
CHANGED
|
@@ -6,7 +6,7 @@ import { RemoteCliProvider } from "../providers/remote-cli-provider.js";
|
|
|
6
6
|
import { resolveProjectRoot } from "../tools/core/get-fs.js";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { messages as messagesTable } from "../db/schema.js";
|
|
9
|
-
import { eq } from "drizzle-orm";
|
|
9
|
+
import { eq, sql } from "drizzle-orm";
|
|
10
10
|
import { uuidv7 } from "../db/uuidv7.js";
|
|
11
11
|
import { requireAuth } from "../security/http-auth.js";
|
|
12
12
|
import { signAuthToken } from "../security/http-auth.js";
|
|
@@ -541,6 +541,34 @@ function accumulateToolStart(sessionId, callId, tool, args, parentCallId) {
|
|
|
541
541
|
acc.segments.push({ type: "toolGroup", callIds: [callId] });
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
|
+
function accumulateToolApproval(sessionId, requestId, tool, args) {
|
|
545
|
+
const callId = `approval-${requestId}`;
|
|
546
|
+
const acc = getOrCreateAccumulator(sessionId);
|
|
547
|
+
const existing = acc.toolCalls.find(t => t.callId === callId);
|
|
548
|
+
if (!existing) {
|
|
549
|
+
acc.toolCalls.push({
|
|
550
|
+
callId,
|
|
551
|
+
tool,
|
|
552
|
+
args,
|
|
553
|
+
ok: true,
|
|
554
|
+
message: "Awaiting approval",
|
|
555
|
+
status: "pending",
|
|
556
|
+
approvalRequestId: requestId,
|
|
557
|
+
approvalState: "pending",
|
|
558
|
+
startedAt: Date.now(),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
const last = acc.segments[acc.segments.length - 1];
|
|
562
|
+
if (last?.type === "toolGroup") {
|
|
563
|
+
if (!last.callIds.includes(callId)) {
|
|
564
|
+
acc.segments[acc.segments.length - 1] = { type: "toolGroup", callIds: [...last.callIds, callId] };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
acc.segments.push({ type: "toolGroup", callIds: [callId] });
|
|
569
|
+
}
|
|
570
|
+
return callId;
|
|
571
|
+
}
|
|
544
572
|
/** Record streaming output for a tool call */
|
|
545
573
|
function accumulateToolOutput(sessionId, callId, content) {
|
|
546
574
|
const acc = sessionStreamingState.get(sessionId);
|
|
@@ -623,9 +651,11 @@ function mapPersistedToolCallsForUI(toolCalls) {
|
|
|
623
651
|
parentCallId: tc.parentCallId,
|
|
624
652
|
tool: tc.tool,
|
|
625
653
|
args: (typeof tc.args === "object" && tc.args !== null ? tc.args : {}),
|
|
626
|
-
status: tc.ok ? "success" : "error",
|
|
654
|
+
status: tc.status ?? (tc.ok ? "success" : "error"),
|
|
627
655
|
ok: tc.ok,
|
|
628
656
|
message: tc.message,
|
|
657
|
+
approvalRequestId: tc.approvalRequestId,
|
|
658
|
+
approvalState: tc.approvalState,
|
|
629
659
|
output: tc.output,
|
|
630
660
|
data: tc.data,
|
|
631
661
|
startedAt: tc.startedAt,
|
|
@@ -716,6 +746,65 @@ function windowMessages(messages, limit, before) {
|
|
|
716
746
|
hasMore: start > 0,
|
|
717
747
|
};
|
|
718
748
|
}
|
|
749
|
+
function rowToUIMsg(sessionId, row, visibleIndex) {
|
|
750
|
+
const msg = {
|
|
751
|
+
id: `${sessionId}-${visibleIndex}`,
|
|
752
|
+
role: row.role,
|
|
753
|
+
content: typeof row.content === "string" ? row.content : String(row.content ?? ""),
|
|
754
|
+
};
|
|
755
|
+
if (row.toolCalls) {
|
|
756
|
+
try {
|
|
757
|
+
msg.toolCalls = JSON.parse(row.toolCalls);
|
|
758
|
+
}
|
|
759
|
+
catch { /* ignore */ }
|
|
760
|
+
}
|
|
761
|
+
if (row.segments) {
|
|
762
|
+
try {
|
|
763
|
+
msg.segments = JSON.parse(row.segments);
|
|
764
|
+
}
|
|
765
|
+
catch { /* ignore */ }
|
|
766
|
+
}
|
|
767
|
+
if (row.thinking) {
|
|
768
|
+
msg.thinking = row.thinking;
|
|
769
|
+
}
|
|
770
|
+
if (row.contextFlow) {
|
|
771
|
+
try {
|
|
772
|
+
const parsed = JSON.parse(row.contextFlow);
|
|
773
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.rounds)) {
|
|
774
|
+
msg.contextFlow = parsed;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
catch { /* ignore */ }
|
|
778
|
+
}
|
|
779
|
+
return msg;
|
|
780
|
+
}
|
|
781
|
+
function persistedMessageWindow(db, sessionId, limit, before) {
|
|
782
|
+
const totalRow = db
|
|
783
|
+
.select({ count: sql `count(*)` })
|
|
784
|
+
.from(messagesTable)
|
|
785
|
+
.where(eq(messagesTable.sessionId, sessionId))
|
|
786
|
+
.get();
|
|
787
|
+
const total = Number(totalRow?.count ?? 0);
|
|
788
|
+
const end = typeof before === "number" && Number.isFinite(before) && before >= 0 && before < total
|
|
789
|
+
? Math.floor(before)
|
|
790
|
+
: total;
|
|
791
|
+
const start = Math.max(end - limit, 0);
|
|
792
|
+
const rows = start < end
|
|
793
|
+
? db
|
|
794
|
+
.select()
|
|
795
|
+
.from(messagesTable)
|
|
796
|
+
.where(eq(messagesTable.sessionId, sessionId))
|
|
797
|
+
.orderBy(messagesTable.createdAt, messagesTable.id)
|
|
798
|
+
.limit(end - start)
|
|
799
|
+
.offset(start)
|
|
800
|
+
.all()
|
|
801
|
+
: [];
|
|
802
|
+
return {
|
|
803
|
+
messages: rows.map((row, index) => rowToUIMsg(sessionId, row, start + index)),
|
|
804
|
+
total,
|
|
805
|
+
hasMore: start > 0,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
719
808
|
function sleep(ms) {
|
|
720
809
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
721
810
|
}
|
|
@@ -1015,7 +1104,7 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
1015
1104
|
.select()
|
|
1016
1105
|
.from(messagesTable)
|
|
1017
1106
|
.where(eq(messagesTable.sessionId, sessionId))
|
|
1018
|
-
.orderBy(messagesTable.createdAt)
|
|
1107
|
+
.orderBy(messagesTable.createdAt, messagesTable.id)
|
|
1019
1108
|
.all();
|
|
1020
1109
|
if (rows.length > 0) {
|
|
1021
1110
|
sessionHistory.set(sessionId, [
|
|
@@ -1095,7 +1184,7 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
1095
1184
|
.select()
|
|
1096
1185
|
.from(messagesTable)
|
|
1097
1186
|
.where(eq(messagesTable.sessionId, sessionId))
|
|
1098
|
-
.orderBy(messagesTable.createdAt)
|
|
1187
|
+
.orderBy(messagesTable.createdAt, messagesTable.id)
|
|
1099
1188
|
.all();
|
|
1100
1189
|
const lastAssistant = [...rows].reverse().find((row) => row.role === "assistant");
|
|
1101
1190
|
if (!lastAssistant)
|
|
@@ -1345,6 +1434,11 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
1345
1434
|
let resultSegmentsJson;
|
|
1346
1435
|
let contextFlowJson;
|
|
1347
1436
|
let hitMaxRounds = false;
|
|
1437
|
+
// Whether the agentic loop already persisted the assistant message via its
|
|
1438
|
+
// onPersist callback. The post-loop fallback persists must be skipped when
|
|
1439
|
+
// this is true to avoid writing a duplicate DB row (which scrambled message
|
|
1440
|
+
// order on reload because the rows share createdAt down to the millisecond).
|
|
1441
|
+
let loopPersisted = false;
|
|
1348
1442
|
activeStreams.add(sessionId);
|
|
1349
1443
|
// Reset streaming accumulator for this turn so reload snapshots start fresh
|
|
1350
1444
|
sessionStreamingState.delete(sessionId);
|
|
@@ -1690,9 +1784,13 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
1690
1784
|
}
|
|
1691
1785
|
break;
|
|
1692
1786
|
}
|
|
1693
|
-
case "tool.approval-required":
|
|
1694
|
-
|
|
1787
|
+
case "tool.approval-required": {
|
|
1788
|
+
const callId = accumulateToolApproval(sessionId, event.requestId, event.tool, event.args);
|
|
1789
|
+
const approvalEvent = { type: "approval_required", call_id: callId, request_id: event.requestId, tool: event.tool, args: event.args };
|
|
1790
|
+
safeWrite(`data: ${JSON.stringify(approvalEvent)}\n\n`);
|
|
1791
|
+
emitToSubscribers(sessionId, approvalEvent);
|
|
1695
1792
|
break;
|
|
1793
|
+
}
|
|
1696
1794
|
case "message":
|
|
1697
1795
|
if (event.role === "assistant" && event.content) {
|
|
1698
1796
|
flushToolGroup();
|
|
@@ -2026,6 +2124,7 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2026
2124
|
}
|
|
2027
2125
|
resultSegmentsJson = resultSegments.length > 0 ? JSON.stringify(resultSegments) : undefined;
|
|
2028
2126
|
hitMaxRounds = result.hitMaxRounds;
|
|
2127
|
+
loopPersisted = result.persisted === true;
|
|
2029
2128
|
// Persist fingerprints so a Continue turn reuses them
|
|
2030
2129
|
sessionFingerprints.set(sessionId, result.fingerprints);
|
|
2031
2130
|
// Re-serialize contextFlow now that round metrics have been attached
|
|
@@ -2068,12 +2167,13 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2068
2167
|
const wasCancelled = err instanceof Error && err.name === "AbortError";
|
|
2069
2168
|
if (!wasCancelled)
|
|
2070
2169
|
app.log.error(err, `${providerLabel} streaming error`);
|
|
2071
|
-
// Save partial content for real (non-cancel) errors
|
|
2072
|
-
|
|
2170
|
+
// Save partial content for real (non-cancel) errors — but only if the
|
|
2171
|
+
// agentic loop didn't already persist this turn's assistant message.
|
|
2172
|
+
if (!wasCancelled && !loopPersisted && (fullContent || partialToolCalls.length > 0)) {
|
|
2073
2173
|
const tcJson = partialToolCalls.length > 0 ? JSON.stringify(partialToolCalls) : undefined;
|
|
2074
2174
|
persistMessage(sessionId, "assistant", fullContent || "", tcJson, resultSegmentsJson, contextFlowJson);
|
|
2075
2175
|
}
|
|
2076
|
-
else if (!wasCancelled) {
|
|
2176
|
+
else if (!wasCancelled && !loopPersisted) {
|
|
2077
2177
|
// No partial content — persist the error message itself so it's visible on reload.
|
|
2078
2178
|
const errMsg2 = err instanceof Error ? err.message : `Failed to reach ${providerLabel}`;
|
|
2079
2179
|
persistMessage(sessionId, "assistant", errMsg2, undefined, JSON.stringify([{ type: "error", content: errMsg2 }]), contextFlowJson);
|
|
@@ -2091,7 +2191,9 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2091
2191
|
}
|
|
2092
2192
|
// Persist partial results BEFORE clearing stream state so that a reload
|
|
2093
2193
|
// between these two steps loads the cancelled tool calls from the DB.
|
|
2094
|
-
|
|
2194
|
+
// Skip when the agentic loop already persisted via onPersist (avoids a
|
|
2195
|
+
// duplicate row that scrambled message order on reload).
|
|
2196
|
+
if (streamAbort.signal.aborted && !loopPersisted && (fullContent || partialToolCalls.length > 0)) {
|
|
2095
2197
|
const tcJson = partialToolCalls.length > 0 ? JSON.stringify(partialToolCalls) : undefined;
|
|
2096
2198
|
persistMessage(sessionId, "assistant", fullContent || "", tcJson, resultSegmentsJson, contextFlowJson);
|
|
2097
2199
|
}
|
|
@@ -2152,6 +2254,30 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2152
2254
|
}
|
|
2153
2255
|
void drainQueuedChatMessages(sessionId);
|
|
2154
2256
|
});
|
|
2257
|
+
app.post("/api/sessions/:sessionId/approve", async (request, reply) => {
|
|
2258
|
+
const authUser = await requireAuth(request, reply, config.jwtSecret);
|
|
2259
|
+
if (!authUser)
|
|
2260
|
+
return;
|
|
2261
|
+
const { sessionId } = request.params;
|
|
2262
|
+
const body = request.body;
|
|
2263
|
+
const requestId = typeof body["requestId"] === "string" ? body["requestId"] : "";
|
|
2264
|
+
const approved = body["approved"] !== false;
|
|
2265
|
+
if (!requestId) {
|
|
2266
|
+
return reply.status(400).send({ error: "VALIDATION_ERROR", details: "requestId is required" });
|
|
2267
|
+
}
|
|
2268
|
+
if (sessionService) {
|
|
2269
|
+
const session = sessionService.getById(sessionId, authUser.id);
|
|
2270
|
+
if (!session) {
|
|
2271
|
+
return reply.status(404).send({ error: "NOT_FOUND", details: "Session not found" });
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
const activeCliSession = activeCliSessions.get(sessionId);
|
|
2275
|
+
if (!activeCliSession) {
|
|
2276
|
+
return reply.status(409).send({ error: "CONFLICT", details: "No active provider session for approval" });
|
|
2277
|
+
}
|
|
2278
|
+
await activeCliSession.provider.respondToApproval(activeCliSession.providerSessionId, requestId, approved);
|
|
2279
|
+
return reply.status(200).send({ ok: true });
|
|
2280
|
+
});
|
|
2155
2281
|
// Cancel an active stream for a session
|
|
2156
2282
|
app.post("/api/sessions/:sessionId/cancel", async (request, reply) => {
|
|
2157
2283
|
const authUser = await requireAuth(request, reply, config.jwtSecret);
|
|
@@ -2248,7 +2374,7 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2248
2374
|
.select()
|
|
2249
2375
|
.from(messagesTable)
|
|
2250
2376
|
.where(eq(messagesTable.sessionId, sessionId))
|
|
2251
|
-
.orderBy(messagesTable.createdAt)
|
|
2377
|
+
.orderBy(messagesTable.createdAt, messagesTable.id)
|
|
2252
2378
|
.all();
|
|
2253
2379
|
const rowsToDelete = rows.slice(Math.max(0, target.historyIndex - 1));
|
|
2254
2380
|
for (const row of rowsToDelete) {
|
|
@@ -2290,13 +2416,18 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2290
2416
|
: typeof query?.before === "string"
|
|
2291
2417
|
? Number.parseInt(query.before, 10)
|
|
2292
2418
|
: undefined;
|
|
2293
|
-
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2296
|
-
|
|
2419
|
+
const streaming = activeStreams.has(sessionId);
|
|
2420
|
+
const windowed = db && !streaming
|
|
2421
|
+
? persistedMessageWindow(db, sessionId, limit, Number.isFinite(before) ? before : undefined)
|
|
2422
|
+
: (() => {
|
|
2423
|
+
hydrateSession(sessionId);
|
|
2424
|
+
const history = sessionHistory.get(sessionId) ?? [];
|
|
2425
|
+
const visible = buildVisibleHistoryMessages(sessionId, history, { includePendingAssistantToolCalls: streaming });
|
|
2426
|
+
return windowMessages(visible, limit, Number.isFinite(before) ? before : undefined);
|
|
2427
|
+
})();
|
|
2297
2428
|
return {
|
|
2298
2429
|
sessionId,
|
|
2299
|
-
streaming
|
|
2430
|
+
streaming,
|
|
2300
2431
|
total: windowed.total,
|
|
2301
2432
|
hasMore: windowed.hasMore,
|
|
2302
2433
|
limit,
|
|
@@ -2326,56 +2457,22 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2326
2457
|
"Access-Control-Allow-Origin": reqOrigin,
|
|
2327
2458
|
"Access-Control-Allow-Credentials": "true",
|
|
2328
2459
|
});
|
|
2329
|
-
hydrateSession(sessionId);
|
|
2330
|
-
const history = sessionHistory.get(sessionId) ?? [];
|
|
2331
2460
|
const isStreaming = activeStreams.has(sessionId);
|
|
2461
|
+
// Only the DB-windowed idle path can skip hydrating. Sessions without a DB
|
|
2462
|
+
// (in-memory history) still need their completed messages loaded from the
|
|
2463
|
+
// sessionHistory map, and streaming sessions need partial content too.
|
|
2464
|
+
let history = [];
|
|
2465
|
+
if (!db || isStreaming) {
|
|
2466
|
+
hydrateSession(sessionId);
|
|
2467
|
+
history = sessionHistory.get(sessionId) ?? [];
|
|
2468
|
+
}
|
|
2332
2469
|
// Build snapshot. While streaming, prefer in-memory history so partial assistant
|
|
2333
2470
|
// content is visible immediately (DB persistence may lag until stream completion).
|
|
2334
2471
|
let snapshotMessages;
|
|
2335
2472
|
let total = 0;
|
|
2336
2473
|
let hasMore = false;
|
|
2337
2474
|
if (db && !isStreaming) {
|
|
2338
|
-
const
|
|
2339
|
-
.select()
|
|
2340
|
-
.from(messagesTable)
|
|
2341
|
-
.where(eq(messagesTable.sessionId, sessionId))
|
|
2342
|
-
.orderBy(messagesTable.createdAt)
|
|
2343
|
-
.all();
|
|
2344
|
-
const allMessages = rows
|
|
2345
|
-
.filter((r) => r.role === "user" || r.role === "assistant")
|
|
2346
|
-
.map((r, i) => {
|
|
2347
|
-
const msg = {
|
|
2348
|
-
id: `${sessionId}-${i}`,
|
|
2349
|
-
role: r.role,
|
|
2350
|
-
content: typeof r.content === "string" ? r.content : String(r.content ?? ""),
|
|
2351
|
-
};
|
|
2352
|
-
if (r.toolCalls) {
|
|
2353
|
-
try {
|
|
2354
|
-
msg.toolCalls = JSON.parse(r.toolCalls);
|
|
2355
|
-
}
|
|
2356
|
-
catch { /* ignore */ }
|
|
2357
|
-
}
|
|
2358
|
-
if (r.segments) {
|
|
2359
|
-
try {
|
|
2360
|
-
msg.segments = JSON.parse(r.segments);
|
|
2361
|
-
}
|
|
2362
|
-
catch { /* ignore */ }
|
|
2363
|
-
}
|
|
2364
|
-
if (r.thinking) {
|
|
2365
|
-
msg.thinking = r.thinking;
|
|
2366
|
-
}
|
|
2367
|
-
if (r.contextFlow) {
|
|
2368
|
-
try {
|
|
2369
|
-
const parsed = JSON.parse(r.contextFlow);
|
|
2370
|
-
if (parsed && typeof parsed === "object" && Array.isArray(parsed.rounds)) {
|
|
2371
|
-
msg.contextFlow = parsed;
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
catch { /* ignore */ }
|
|
2375
|
-
}
|
|
2376
|
-
return msg;
|
|
2377
|
-
});
|
|
2378
|
-
const windowed = windowMessages(allMessages, limit);
|
|
2475
|
+
const windowed = persistedMessageWindow(db, sessionId, limit);
|
|
2379
2476
|
snapshotMessages = windowed.messages;
|
|
2380
2477
|
total = windowed.total;
|
|
2381
2478
|
hasMore = windowed.hasMore;
|
|
@@ -2482,7 +2579,7 @@ export function registerChatRoutes(app, config, depsOrDb, sessionServiceArg) {
|
|
|
2482
2579
|
.select()
|
|
2483
2580
|
.from(messagesTable)
|
|
2484
2581
|
.where(eq(messagesTable.sessionId, sessionId))
|
|
2485
|
-
.orderBy(messagesTable.createdAt)
|
|
2582
|
+
.orderBy(messagesTable.createdAt, messagesTable.id)
|
|
2486
2583
|
.all();
|
|
2487
2584
|
const lastAssistant = [...rows].reverse().find((r) => r.role === "assistant");
|
|
2488
2585
|
if (lastAssistant) {
|