@townco/ui 0.1.109 → 0.1.111
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/core/hooks/use-chat-messages.js +5 -5
- package/dist/core/hooks/use-subagent-stream.d.ts +28 -0
- package/dist/core/hooks/use-subagent-stream.js +256 -0
- package/dist/core/hooks/use-tool-calls.js +25 -1
- package/dist/core/store/chat-store.d.ts +3 -0
- package/dist/core/store/chat-store.js +134 -3
- package/dist/gui/components/ChatPanelTabContent.js +23 -1
- package/dist/gui/components/ChatView.js +47 -4
- package/dist/gui/components/InvokingGroup.d.ts +9 -0
- package/dist/gui/components/InvokingGroup.js +16 -0
- package/dist/gui/components/Message.js +1 -1
- package/dist/gui/components/MessageContent.js +32 -5
- package/dist/gui/components/Response.d.ts +11 -0
- package/dist/gui/components/Response.js +60 -19
- package/dist/gui/components/SourceListItem.d.ts +10 -0
- package/dist/gui/components/SourceListItem.js +52 -4
- package/dist/gui/components/ToolCall.d.ts +8 -0
- package/dist/gui/components/ToolCall.js +226 -0
- package/dist/gui/components/ToolCallGroup.d.ts +8 -0
- package/dist/gui/components/ToolCallGroup.js +29 -0
- package/dist/sdk/schemas/session.d.ts +68 -0
- package/dist/sdk/schemas/session.js +10 -1
- package/dist/sdk/transports/http.d.ts +1 -0
- package/dist/sdk/transports/http.js +199 -19
- package/package.json +3 -3
|
@@ -25,6 +25,7 @@ export class HttpTransport {
|
|
|
25
25
|
options;
|
|
26
26
|
isReceivingMessages = false;
|
|
27
27
|
isInReplayMode = false; // True during session replay, ignores non-replay streaming
|
|
28
|
+
pendingReplayUpdates = []; // Queue session updates during replay for late-subscribing callbacks
|
|
28
29
|
agentInfo;
|
|
29
30
|
constructor(options) {
|
|
30
31
|
// Validate options at the boundary using Zod
|
|
@@ -565,6 +566,61 @@ export class HttpTransport {
|
|
|
565
566
|
}
|
|
566
567
|
onSessionUpdate(callback) {
|
|
567
568
|
this.sessionUpdateCallbacks.add(callback);
|
|
569
|
+
// Replay any queued session updates from replay that arrived before callbacks were registered
|
|
570
|
+
// IMPORTANT: Use notifySessionUpdate to send to ALL registered callbacks, not just this one.
|
|
571
|
+
// This ensures that when multiple hooks register (e.g., message handler, useToolCalls),
|
|
572
|
+
// they ALL receive the queued updates, not just the first one to register.
|
|
573
|
+
if (this.pendingReplayUpdates.length > 0) {
|
|
574
|
+
logger.info("Replaying queued session updates to ALL callbacks", {
|
|
575
|
+
count: this.pendingReplayUpdates.length,
|
|
576
|
+
callbackCount: this.sessionUpdateCallbacks.size,
|
|
577
|
+
updateTypes: this.pendingReplayUpdates.map((u) => u.type),
|
|
578
|
+
updateDetails: this.pendingReplayUpdates.map((u) => {
|
|
579
|
+
if (u.type === "tool_call") {
|
|
580
|
+
return { type: "tool_call", toolCallId: u.toolCall?.id };
|
|
581
|
+
}
|
|
582
|
+
if (u.type === "sources") {
|
|
583
|
+
return {
|
|
584
|
+
type: "sources",
|
|
585
|
+
count: u.sources?.length,
|
|
586
|
+
ids: u.sources?.map((s) => s.id),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
if (u.type === "generic" && u.message) {
|
|
590
|
+
const firstContent = u.message.content?.[0];
|
|
591
|
+
return {
|
|
592
|
+
type: "message",
|
|
593
|
+
role: u.message.role,
|
|
594
|
+
contentLength: firstContent?.type === "text" ? firstContent.text?.length : 0,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return { type: u.type };
|
|
598
|
+
}),
|
|
599
|
+
});
|
|
600
|
+
// Copy and clear the queue immediately to prevent re-triggering when other callbacks register
|
|
601
|
+
const updatesToReplay = [...this.pendingReplayUpdates];
|
|
602
|
+
this.pendingReplayUpdates = [];
|
|
603
|
+
// Use setTimeout to ensure all callbacks have a chance to register before replaying
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
logger.info("Starting replay of queued updates to all callbacks", {
|
|
606
|
+
updateCount: updatesToReplay.length,
|
|
607
|
+
callbackCount: this.sessionUpdateCallbacks.size,
|
|
608
|
+
});
|
|
609
|
+
for (const update of updatesToReplay) {
|
|
610
|
+
try {
|
|
611
|
+
logger.info("Replaying update to all callbacks", {
|
|
612
|
+
type: update.type,
|
|
613
|
+
});
|
|
614
|
+
// Notify ALL registered callbacks, not just the one that triggered this replay
|
|
615
|
+
this.notifySessionUpdate(update);
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
logger.error("Error replaying session update", { error });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
logger.info("Finished replay of queued updates");
|
|
622
|
+
}, 0);
|
|
623
|
+
}
|
|
568
624
|
return () => {
|
|
569
625
|
this.sessionUpdateCallbacks.delete(callback);
|
|
570
626
|
};
|
|
@@ -810,6 +866,28 @@ export class HttpTransport {
|
|
|
810
866
|
try {
|
|
811
867
|
const message = JSON.parse(data);
|
|
812
868
|
logger.debug("Received SSE message", { message });
|
|
869
|
+
// Check if this is a sources message (custom extension to ACP)
|
|
870
|
+
const isSourcesMessage = message &&
|
|
871
|
+
typeof message === "object" &&
|
|
872
|
+
message.method === "session/update" &&
|
|
873
|
+
message.params?.update?.sessionUpdate === "sources";
|
|
874
|
+
if (isSourcesMessage) {
|
|
875
|
+
// Use console.warn directly for gui-console capture
|
|
876
|
+
console.warn("🟢 RECEIVED SOURCES SSE MESSAGE", {
|
|
877
|
+
sourcesCount: message.params?.update?.sources?.length,
|
|
878
|
+
isInReplayMode: this.isInReplayMode,
|
|
879
|
+
callbackCount: this.sessionUpdateCallbacks.size,
|
|
880
|
+
});
|
|
881
|
+
// Handle sources directly without ACP schema validation
|
|
882
|
+
try {
|
|
883
|
+
this.handleSessionNotification(message.params);
|
|
884
|
+
console.warn("🟢 AFTER handleSessionNotification for sources");
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
console.error("🔴 ERROR in handleSessionNotification for sources", error);
|
|
888
|
+
}
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
813
891
|
// Validate the message is an ACP agent outgoing message
|
|
814
892
|
const parseResult = acp.agentOutgoingMessageSchema.safeParse(message);
|
|
815
893
|
if (!parseResult.success) {
|
|
@@ -843,18 +921,29 @@ export class HttpTransport {
|
|
|
843
921
|
* Handle a session notification from the agent
|
|
844
922
|
*/
|
|
845
923
|
handleSessionNotification(params) {
|
|
924
|
+
// Extract update type early to check if this is a sources notification
|
|
925
|
+
const paramsAny = params;
|
|
926
|
+
const updateAny = paramsAny.update;
|
|
927
|
+
const isSourcesNotification = updateAny?.sessionUpdate === "sources";
|
|
846
928
|
// Skip processing if stream has been cancelled/completed
|
|
847
|
-
|
|
929
|
+
// BUT always allow sources through - they arrive after stream completion during replay
|
|
930
|
+
if (this.streamComplete && !isSourcesNotification) {
|
|
848
931
|
logger.debug("Skipping session notification - stream complete/cancelled");
|
|
849
932
|
return;
|
|
850
933
|
}
|
|
934
|
+
if (this.streamComplete && isSourcesNotification) {
|
|
935
|
+
console.warn("🟢 Processing sources notification after stream complete");
|
|
936
|
+
}
|
|
851
937
|
logger.debug("handleSessionNotification called", { params });
|
|
852
938
|
// Extract content from the update
|
|
853
939
|
const paramsExtended = params;
|
|
854
940
|
const update = paramsExtended.update;
|
|
855
941
|
const sessionId = this.currentSessionId || params.sessionId;
|
|
856
|
-
logger.
|
|
942
|
+
logger.warn("📥 SSE UPDATE RECEIVED", {
|
|
857
943
|
sessionUpdate: update?.sessionUpdate,
|
|
944
|
+
hasUpdate: !!update,
|
|
945
|
+
isInReplayMode: this.isInReplayMode,
|
|
946
|
+
callbackCount: this.sessionUpdateCallbacks.size,
|
|
858
947
|
});
|
|
859
948
|
// Handle sandbox file changes
|
|
860
949
|
// Type assertion needed because TypeScript doesn't recognize this as a valid session update type
|
|
@@ -1064,9 +1153,18 @@ export class HttpTransport {
|
|
|
1064
1153
|
isReplay,
|
|
1065
1154
|
isInReplayMode: this.isInReplayMode,
|
|
1066
1155
|
});
|
|
1067
|
-
// During replay,
|
|
1156
|
+
// During replay, handle specially
|
|
1068
1157
|
if (isReplay || this.isInReplayMode) {
|
|
1069
|
-
|
|
1158
|
+
// If no callbacks registered yet, queue for late-subscribing hooks
|
|
1159
|
+
if (this.sessionUpdateCallbacks.size === 0) {
|
|
1160
|
+
logger.debug("Queueing tool_call for late-subscribing callbacks", {
|
|
1161
|
+
toolCallId: toolCall.id,
|
|
1162
|
+
});
|
|
1163
|
+
this.pendingReplayUpdates.push(sessionUpdate);
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
this.notifySessionUpdate(sessionUpdate);
|
|
1167
|
+
}
|
|
1070
1168
|
}
|
|
1071
1169
|
else {
|
|
1072
1170
|
// Queue tool call as a chunk for ordered processing during live streaming
|
|
@@ -1273,8 +1371,16 @@ export class HttpTransport {
|
|
|
1273
1371
|
// Check if this is replay (tool_call_update doesn't have isReplay in _meta,
|
|
1274
1372
|
// but we can check if we're in replay mode)
|
|
1275
1373
|
if (this.isInReplayMode) {
|
|
1276
|
-
//
|
|
1277
|
-
this.
|
|
1374
|
+
// If no callbacks registered yet, queue for late-subscribing hooks
|
|
1375
|
+
if (this.sessionUpdateCallbacks.size === 0) {
|
|
1376
|
+
logger.debug("Queueing tool_call_update for late-subscribing callbacks", {
|
|
1377
|
+
toolCallId: toolCallUpdate.id,
|
|
1378
|
+
});
|
|
1379
|
+
this.pendingReplayUpdates.push(sessionUpdate);
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
this.notifySessionUpdate(sessionUpdate);
|
|
1383
|
+
}
|
|
1278
1384
|
}
|
|
1279
1385
|
else {
|
|
1280
1386
|
// Queue tool call update as a chunk for ordered processing
|
|
@@ -1406,7 +1512,16 @@ export class HttpTransport {
|
|
|
1406
1512
|
};
|
|
1407
1513
|
// During replay, notify directly; otherwise queue for ordered processing
|
|
1408
1514
|
if (this.isInReplayMode) {
|
|
1409
|
-
|
|
1515
|
+
// If no callbacks registered yet, queue for late-subscribing hooks
|
|
1516
|
+
if (this.sessionUpdateCallbacks.size === 0) {
|
|
1517
|
+
logger.debug("Queueing tool_output for late-subscribing callbacks", {
|
|
1518
|
+
toolCallId: toolOutput.id,
|
|
1519
|
+
});
|
|
1520
|
+
this.pendingReplayUpdates.push(sessionUpdate);
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
this.notifySessionUpdate(sessionUpdate);
|
|
1524
|
+
}
|
|
1410
1525
|
}
|
|
1411
1526
|
else {
|
|
1412
1527
|
// Queue tool output as a chunk for ordered processing
|
|
@@ -1433,21 +1548,56 @@ export class HttpTransport {
|
|
|
1433
1548
|
update.sessionUpdate === "sources") {
|
|
1434
1549
|
// Sources notification - citation sources from tool calls
|
|
1435
1550
|
const sourcesUpdate = update;
|
|
1436
|
-
|
|
1551
|
+
console.warn("🔵 SOURCES in handleSessionNotification", {
|
|
1437
1552
|
sourcesCount: sourcesUpdate.sources.length,
|
|
1553
|
+
isInReplayMode: this.isInReplayMode,
|
|
1554
|
+
callbackCount: this.sessionUpdateCallbacks.size,
|
|
1555
|
+
firstSourceId: sourcesUpdate.sources[0]?.id,
|
|
1556
|
+
firstSourceToolCallId: sourcesUpdate.sources[0]?.toolCallId,
|
|
1438
1557
|
});
|
|
1439
|
-
// Create a sources
|
|
1440
|
-
const
|
|
1558
|
+
// Create a sources session update
|
|
1559
|
+
const sessionUpdate = {
|
|
1441
1560
|
type: "sources",
|
|
1561
|
+
sessionId,
|
|
1562
|
+
status: "active",
|
|
1442
1563
|
sources: sourcesUpdate.sources,
|
|
1443
1564
|
};
|
|
1444
|
-
//
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1565
|
+
// During replay, handle sources specially
|
|
1566
|
+
if (this.isInReplayMode) {
|
|
1567
|
+
// If no callbacks are registered yet (React hooks haven't subscribed),
|
|
1568
|
+
// queue the sources to be replayed when they do subscribe
|
|
1569
|
+
if (this.sessionUpdateCallbacks.size === 0) {
|
|
1570
|
+
console.warn("🔵 QUEUEING sources for late-subscribing callbacks", {
|
|
1571
|
+
sourcesCount: sourcesUpdate.sources.length,
|
|
1572
|
+
queueLengthBefore: this.pendingReplayUpdates.length,
|
|
1573
|
+
});
|
|
1574
|
+
this.pendingReplayUpdates.push(sessionUpdate);
|
|
1575
|
+
console.warn("🔵 Queue length after:", {
|
|
1576
|
+
queueLengthAfter: this.pendingReplayUpdates.length,
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
console.warn("🔵 NOTIFYING sources immediately (callbacks registered)", {
|
|
1581
|
+
sourcesCount: sourcesUpdate.sources.length,
|
|
1582
|
+
callbackCount: this.sessionUpdateCallbacks.size,
|
|
1583
|
+
});
|
|
1584
|
+
this.notifySessionUpdate(sessionUpdate);
|
|
1585
|
+
}
|
|
1448
1586
|
}
|
|
1449
1587
|
else {
|
|
1450
|
-
|
|
1588
|
+
// Create a sources chunk for the message queue
|
|
1589
|
+
const sourcesChunk = {
|
|
1590
|
+
type: "sources",
|
|
1591
|
+
sources: sourcesUpdate.sources,
|
|
1592
|
+
};
|
|
1593
|
+
// Queue for ordered processing
|
|
1594
|
+
const resolver = this.chunkResolvers.shift();
|
|
1595
|
+
if (resolver) {
|
|
1596
|
+
resolver(sourcesChunk);
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
this.messageQueue.push(sourcesChunk);
|
|
1600
|
+
}
|
|
1451
1601
|
}
|
|
1452
1602
|
}
|
|
1453
1603
|
else if (update?.sessionUpdate === "agent_message_chunk") {
|
|
@@ -1539,8 +1689,18 @@ export class HttpTransport {
|
|
|
1539
1689
|
timestamp: new Date().toISOString(),
|
|
1540
1690
|
},
|
|
1541
1691
|
};
|
|
1542
|
-
//
|
|
1543
|
-
|
|
1692
|
+
// During replay, queue messages along with tool_calls and sources
|
|
1693
|
+
// so everything is processed in the correct order
|
|
1694
|
+
if (isReplay && this.sessionUpdateCallbacks.size === 0) {
|
|
1695
|
+
logger.debug("Queueing assistant message for late-subscribing callbacks", {
|
|
1696
|
+
textLength: contentObj.text.length,
|
|
1697
|
+
});
|
|
1698
|
+
this.pendingReplayUpdates.push(messageSessionUpdate);
|
|
1699
|
+
}
|
|
1700
|
+
else {
|
|
1701
|
+
// Notify as a complete message (for session replay or initial message)
|
|
1702
|
+
this.notifySessionUpdate(messageSessionUpdate);
|
|
1703
|
+
}
|
|
1544
1704
|
}
|
|
1545
1705
|
}
|
|
1546
1706
|
// Send session update for:
|
|
@@ -1554,6 +1714,11 @@ export class HttpTransport {
|
|
|
1554
1714
|
else if (update?.sessionUpdate === "user_message_chunk") {
|
|
1555
1715
|
// Handle user message chunks (could be from replay or new messages)
|
|
1556
1716
|
logger.debug("Received user_message_chunk", { update });
|
|
1717
|
+
// Check if this is a replay
|
|
1718
|
+
const isReplay = update._meta &&
|
|
1719
|
+
typeof update._meta === "object" &&
|
|
1720
|
+
"isReplay" in update._meta &&
|
|
1721
|
+
update._meta.isReplay === true;
|
|
1557
1722
|
const content = update.content;
|
|
1558
1723
|
if (content && typeof content === "object") {
|
|
1559
1724
|
const contentObj = content;
|
|
@@ -1575,13 +1740,28 @@ export class HttpTransport {
|
|
|
1575
1740
|
timestamp: new Date().toISOString(),
|
|
1576
1741
|
},
|
|
1577
1742
|
};
|
|
1578
|
-
|
|
1579
|
-
|
|
1743
|
+
// During replay, queue messages along with tool_calls and sources
|
|
1744
|
+
// so everything is processed in the correct order
|
|
1745
|
+
if (isReplay && this.sessionUpdateCallbacks.size === 0) {
|
|
1746
|
+
logger.debug("Queueing user message for late-subscribing callbacks", {
|
|
1747
|
+
textLength: contentObj.text.length,
|
|
1748
|
+
});
|
|
1749
|
+
this.pendingReplayUpdates.push(sessionUpdate);
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
logger.debug("Notifying session update for user message");
|
|
1753
|
+
this.notifySessionUpdate(sessionUpdate);
|
|
1754
|
+
}
|
|
1580
1755
|
}
|
|
1581
1756
|
}
|
|
1582
1757
|
}
|
|
1583
1758
|
else {
|
|
1584
1759
|
// Handle other session updates
|
|
1760
|
+
logger.warn("⚠️ UNHANDLED SESSION UPDATE - falling through to generic", {
|
|
1761
|
+
sessionUpdate: update?.sessionUpdate,
|
|
1762
|
+
updateKeys: update ? Object.keys(update) : [],
|
|
1763
|
+
hasSources: update && "sources" in update,
|
|
1764
|
+
});
|
|
1585
1765
|
const sessionUpdate = {
|
|
1586
1766
|
type: "generic",
|
|
1587
1767
|
sessionId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.111",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@radix-ui/react-slot": "^1.2.4",
|
|
50
50
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
51
51
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
52
|
-
"@townco/core": "0.0.
|
|
52
|
+
"@townco/core": "0.0.89",
|
|
53
53
|
"@types/mdast": "^4.0.4",
|
|
54
54
|
"@uiw/react-json-view": "^2.0.0-alpha.39",
|
|
55
55
|
"class-variance-authority": "^0.7.1",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"zustand": "^5.0.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@townco/tsconfig": "0.1.
|
|
70
|
+
"@townco/tsconfig": "0.1.108",
|
|
71
71
|
"@types/node": "^24.10.0",
|
|
72
72
|
"@types/react": "^19.2.2",
|
|
73
73
|
"@types/unist": "^3.0.3",
|