@adecore/agents-react 0.26.1 → 0.26.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/dist/chat/chat-client.js +2 -2
- package/dist/chat/subagent-list.d.ts +1 -1
- package/dist/chat/subagent-list.js +5 -18
- package/dist/chat/ui/ChatActivity.js +2 -1
- package/dist/state/chats.d.ts +3 -1
- package/dist/state/chats.js +19 -5
- package/package.json +8 -8
- package/src/chat/chat-client.ts +3 -2
- package/src/chat/subagent-list.ts +8 -23
- package/src/chat/ui/ChatActivity.tsx +2 -1
- package/src/state/chats.ts +27 -5
package/dist/chat/chat-client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MountedRegistry } from '../mounted-registry.js';
|
|
2
2
|
import { errorCode, isConnectionError } from '../transport.js';
|
|
3
3
|
/* How many items a chat reads at once, on attach and for every page before it. */
|
|
4
|
-
const HISTORY_PAGE =
|
|
4
|
+
const HISTORY_PAGE = 90;
|
|
5
5
|
/*
|
|
6
6
|
* One chat per chat id on one host. A chat opens on mount and detaches on unmount, and every mounted
|
|
7
7
|
* chat is attached again when the transport comes back. The provider list is fetched once per
|
|
@@ -268,7 +268,7 @@ export class ChatClient {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
else {
|
|
271
|
-
const page = result.history === undefined ? undefined : { history: result.history, pending: result.pending ?? [] };
|
|
271
|
+
const page = result.history === undefined ? undefined : { history: result.history, pending: result.pending ?? [], subagents: result.subagents ?? [] };
|
|
272
272
|
this.sink.reset(chatId, result.info, result.items, page);
|
|
273
273
|
}
|
|
274
274
|
// A host without bookmarks or visuals sends none, and then this chat has none.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ChatInfo, type ChatItem, type ChatSubagentItem } from '@adecore/agent-contracts';
|
|
2
2
|
import type { StatusWord } from '../agents/status-look.js';
|
|
3
3
|
import type { SubagentTask } from '../host.js';
|
|
4
4
|
export declare function subagentTitle(item: ChatSubagentItem): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import i18next from 'i18next';
|
|
2
|
+
import { activeSubagents } from '@adecore/agent-contracts';
|
|
2
3
|
import { formatMoment, formatElapsedShort, formatTokens } from '@adecore/ui/format';
|
|
3
4
|
export function subagentTitle(item) {
|
|
4
5
|
return item.description || item.summary || item.subagentType || i18next.t('agent-chat:rows.subagent.label');
|
|
@@ -45,26 +46,12 @@ export function progressOf(states) {
|
|
|
45
46
|
active: states.filter((state) => state === 'running' || state === 'paused' || state === 'waiting').length
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
|
-
/*
|
|
49
|
-
* What the flyout over the composer lists: every sub-agent still at work, and the ones that settled
|
|
50
|
-
* since the last message, so a finished batch stays until the next message is sent. That includes an
|
|
51
|
-
* older row a message woke again. A settled one from before that message stays too while a sibling of
|
|
52
|
-
* its turn still runs. Older ones are only in the thread.
|
|
53
|
-
*/
|
|
49
|
+
/* What the flyout over the composer lists (`activeSubagents`); `order` puts the rows from before the page first. */
|
|
54
50
|
export function flyoutSubagents(order, structure) {
|
|
55
|
-
|
|
56
|
-
const lastMessageAt = lastMessage < 0 ? null : (structure[order[lastMessage]]?.createdAt ?? null);
|
|
57
|
-
const items = order.flatMap((id, index) => {
|
|
51
|
+
return activeSubagents(order.flatMap((id) => {
|
|
58
52
|
const item = structure[id];
|
|
59
|
-
return item
|
|
60
|
-
});
|
|
61
|
-
const turns = new Set(items.flatMap(({ item }) => (item.status === 'running' && item.turnId !== null ? [item.turnId] : [])));
|
|
62
|
-
return items
|
|
63
|
-
.filter(({ item, index }) => item.status === 'running' ||
|
|
64
|
-
index > lastMessage ||
|
|
65
|
-
(lastMessageAt !== null && item.finishedAt !== null && item.finishedAt > lastMessageAt) ||
|
|
66
|
-
(item.turnId !== null && turns.has(item.turnId)))
|
|
67
|
-
.map(({ item }) => item);
|
|
53
|
+
return item === undefined ? [] : [item];
|
|
54
|
+
}));
|
|
68
55
|
}
|
|
69
56
|
/* The one state the badge shows for all of them: work in progress first, then work held up, then whatever went wrong. */
|
|
70
57
|
export function summaryWordOf(words) {
|
|
@@ -24,8 +24,9 @@ export function ChatActivity({ chatId }) {
|
|
|
24
24
|
const host = useChatHost();
|
|
25
25
|
const { t } = useTranslation('agent-chat');
|
|
26
26
|
const order = useChatRow(chatId, (row) => row?.order) ?? NO_ITEMS;
|
|
27
|
+
const before = useChatRow(chatId, (row) => row?.subagentsBefore) ?? NO_ITEMS;
|
|
27
28
|
const structure = useChatRow(chatId, (row) => row?.structure);
|
|
28
|
-
const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents(order, structure)), [order, structure]);
|
|
29
|
+
const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents([...before, ...order], structure)), [before, order, structure]);
|
|
29
30
|
const { id, keyOf } = useChatScope();
|
|
30
31
|
const tasks = host.tasks.useTasks(id);
|
|
31
32
|
// The infos of these sub-agents' own chats, so the info of any other chat changing does not draw this again.
|
package/dist/state/chats.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentKind, ChatApprovalItem, ChatBookmark, ChatEvent, ChatHistoryResult, ChatInfo, ChatItem, ChatQuestionItem, ChatVisual } from '@adecore/agent-contracts';
|
|
1
|
+
import type { AgentKind, ChatApprovalItem, ChatBookmark, ChatEvent, ChatHistoryResult, ChatInfo, ChatItem, ChatQuestionItem, ChatSubagentItem, ChatVisual } from '@adecore/agent-contracts';
|
|
2
2
|
export interface ChatState {
|
|
3
3
|
info: ChatInfo;
|
|
4
4
|
items: Record<string, ChatItem>;
|
|
@@ -6,6 +6,7 @@ export interface ChatState {
|
|
|
6
6
|
order: string[];
|
|
7
7
|
history?: ChatHistoryPage;
|
|
8
8
|
waitingBefore?: string[];
|
|
9
|
+
subagentsBefore?: string[];
|
|
9
10
|
bookmarks?: ChatBookmark[];
|
|
10
11
|
visuals?: ChatVisual[];
|
|
11
12
|
}
|
|
@@ -13,6 +14,7 @@ export type ChatHistoryPage = ChatHistoryResult['history'];
|
|
|
13
14
|
export interface ChatPage {
|
|
14
15
|
history: ChatHistoryPage;
|
|
15
16
|
pending: ChatItem[];
|
|
17
|
+
subagents?: ChatSubagentItem[];
|
|
16
18
|
}
|
|
17
19
|
export type ChatsById = Record<string, ChatState>;
|
|
18
20
|
export type ChatStatuses = Record<string, Pick<ChatState, 'info'>>;
|
package/dist/state/chats.js
CHANGED
|
@@ -12,17 +12,30 @@ function stateOf(info, items, page) {
|
|
|
12
12
|
return { info, items: byId, structure: byId, order };
|
|
13
13
|
}
|
|
14
14
|
const before = page.pending.filter((item) => !(item.id in byId));
|
|
15
|
-
|
|
15
|
+
const subagents = (page.subagents ?? []).filter((item) => !(item.id in byId));
|
|
16
|
+
for (const item of [...before, ...subagents]) {
|
|
16
17
|
byId[item.id] = item;
|
|
17
18
|
}
|
|
18
|
-
return {
|
|
19
|
+
return {
|
|
20
|
+
info,
|
|
21
|
+
items: byId,
|
|
22
|
+
structure: byId,
|
|
23
|
+
order,
|
|
24
|
+
history: page.history,
|
|
25
|
+
waitingBefore: before.map((item) => item.id),
|
|
26
|
+
subagentsBefore: subagents.map((item) => item.id)
|
|
27
|
+
};
|
|
19
28
|
}
|
|
20
29
|
/* An older host names no place in the thread, and it always hands over the whole of it. */
|
|
21
30
|
function isBeforePage(state, historyIndex) {
|
|
22
31
|
return state.history !== undefined && historyIndex !== undefined && historyIndex < state.history.start;
|
|
23
32
|
}
|
|
24
|
-
/* An item from before the page this client holds matters only as a request that waits, or one that stopped waiting. */
|
|
33
|
+
/* An item from before the page this client holds matters only as a sub-agent, a request that waits, or one that stopped waiting. */
|
|
25
34
|
function withEarlierItem(state, item) {
|
|
35
|
+
if (item.kind === 'subagent') {
|
|
36
|
+
const subagents = state.subagentsBefore ?? [];
|
|
37
|
+
return { ...withItem(state, item), subagentsBefore: subagents.includes(item.id) ? subagents : [...subagents, item.id] };
|
|
38
|
+
}
|
|
26
39
|
const waiting = state.waitingBefore ?? [];
|
|
27
40
|
const held = waiting.includes(item.id);
|
|
28
41
|
if (isWaiting(item)) {
|
|
@@ -45,11 +58,12 @@ export function prependPage(state, cursor, page) {
|
|
|
45
58
|
}
|
|
46
59
|
const order = [...olderIds, ...state.order];
|
|
47
60
|
if (page.history.cursor === null) {
|
|
48
|
-
const { history: _history, waitingBefore: _waitingBefore, ...rest } = state;
|
|
61
|
+
const { history: _history, waitingBefore: _waitingBefore, subagentsBefore: _subagentsBefore, ...rest } = state;
|
|
49
62
|
return { ...rest, items, structure: items, order };
|
|
50
63
|
}
|
|
51
64
|
const waitingBefore = (state.waitingBefore ?? []).filter((id) => !olderIds.has(id));
|
|
52
|
-
|
|
65
|
+
const subagentsBefore = (state.subagentsBefore ?? []).filter((id) => !olderIds.has(id));
|
|
66
|
+
return { ...state, items, structure: items, order, history: page.history, waitingBefore, subagentsBefore };
|
|
53
67
|
}
|
|
54
68
|
/* The requests that still wait, oldest first: those from before the page this client holds, then the thread's own. */
|
|
55
69
|
export function waitingRequestsOf(chat) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
4
4
|
"description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -67,11 +67,11 @@
|
|
|
67
67
|
"test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@adecore/agent-contracts": "0.26.
|
|
71
|
-
"@adecore/editor": "0.26.
|
|
72
|
-
"@adecore/editor-react": "0.26.
|
|
73
|
-
"@adecore/intelligent-ui": "0.26.
|
|
74
|
-
"@adecore/merge": "0.26.
|
|
70
|
+
"@adecore/agent-contracts": "0.26.2",
|
|
71
|
+
"@adecore/editor": "0.26.2",
|
|
72
|
+
"@adecore/editor-react": "0.26.2",
|
|
73
|
+
"@adecore/intelligent-ui": "0.26.2",
|
|
74
|
+
"@adecore/merge": "0.26.2",
|
|
75
75
|
"@codemirror/commands": "^6.11.1",
|
|
76
76
|
"@codemirror/language": "^6.12.4",
|
|
77
77
|
"@codemirror/state": "^6.7.6",
|
|
@@ -94,14 +94,14 @@
|
|
|
94
94
|
"zustand": "^5.0.15"
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
|
-
"@adecore/ui": "^0.26.
|
|
97
|
+
"@adecore/ui": "^0.26.2",
|
|
98
98
|
"i18next": "^26.4.2",
|
|
99
99
|
"react": "^19.3.0",
|
|
100
100
|
"react-dom": "^19.3.0",
|
|
101
101
|
"react-i18next": "^17.0.15"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@adecore/ui": "0.26.
|
|
104
|
+
"@adecore/ui": "0.26.2",
|
|
105
105
|
"@types/bun": "^1.4.2",
|
|
106
106
|
"@types/react": "^19.3.0",
|
|
107
107
|
"@types/react-dom": "^19.3.0",
|
package/src/chat/chat-client.ts
CHANGED
|
@@ -47,7 +47,7 @@ interface Mounted extends ChatOpenOptions, MountedEntry {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/* How many items a chat reads at once, on attach and for every page before it. */
|
|
50
|
-
const HISTORY_PAGE =
|
|
50
|
+
const HISTORY_PAGE = 90;
|
|
51
51
|
|
|
52
52
|
interface ProviderSink {
|
|
53
53
|
setProviders(providers: ProviderInfo[]): void;
|
|
@@ -339,7 +339,8 @@ export class ChatClient {
|
|
|
339
339
|
this.sink.apply(chatId, event);
|
|
340
340
|
}
|
|
341
341
|
} else {
|
|
342
|
-
const page =
|
|
342
|
+
const page =
|
|
343
|
+
result.history === undefined ? undefined : { history: result.history, pending: result.pending ?? [], subagents: result.subagents ?? [] };
|
|
343
344
|
this.sink.reset(chatId, result.info, result.items, page);
|
|
344
345
|
}
|
|
345
346
|
// A host without bookmarks or visuals sends none, and then this chat has none.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import i18next from 'i18next';
|
|
2
|
-
import type
|
|
2
|
+
import { activeSubagents, type ChatInfo, type ChatItem, type ChatSubagentItem } from '@adecore/agent-contracts';
|
|
3
3
|
import { formatMoment, formatElapsedShort, formatTokens } from '@adecore/ui/format';
|
|
4
4
|
import type { StatusWord } from '../agents/status-look';
|
|
5
5
|
import type { SubagentTask } from '../host';
|
|
@@ -58,29 +58,14 @@ export function progressOf(states: readonly TaskRowState[]): { done: number; tot
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/*
|
|
62
|
-
* What the flyout over the composer lists: every sub-agent still at work, and the ones that settled
|
|
63
|
-
* since the last message, so a finished batch stays until the next message is sent. That includes an
|
|
64
|
-
* older row a message woke again. A settled one from before that message stays too while a sibling of
|
|
65
|
-
* its turn still runs. Older ones are only in the thread.
|
|
66
|
-
*/
|
|
61
|
+
/* What the flyout over the composer lists (`activeSubagents`); `order` puts the rows from before the page first. */
|
|
67
62
|
export function flyoutSubagents(order: readonly string[], structure: Readonly<Record<string, ChatItem>>): ChatSubagentItem[] {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const turns = new Set(items.flatMap(({ item }) => (item.status === 'running' && item.turnId !== null ? [item.turnId] : [])));
|
|
75
|
-
return items
|
|
76
|
-
.filter(
|
|
77
|
-
({ item, index }) =>
|
|
78
|
-
item.status === 'running' ||
|
|
79
|
-
index > lastMessage ||
|
|
80
|
-
(lastMessageAt !== null && item.finishedAt !== null && item.finishedAt > lastMessageAt) ||
|
|
81
|
-
(item.turnId !== null && turns.has(item.turnId))
|
|
82
|
-
)
|
|
83
|
-
.map(({ item }) => item);
|
|
63
|
+
return activeSubagents(
|
|
64
|
+
order.flatMap((id) => {
|
|
65
|
+
const item = structure[id];
|
|
66
|
+
return item === undefined ? [] : [item];
|
|
67
|
+
})
|
|
68
|
+
);
|
|
84
69
|
}
|
|
85
70
|
|
|
86
71
|
/* The one state the badge shows for all of them: work in progress first, then work held up, then whatever went wrong. */
|
|
@@ -28,8 +28,9 @@ export function ChatActivity({ chatId }: { chatId: string }) {
|
|
|
28
28
|
const host = useChatHost();
|
|
29
29
|
const { t } = useTranslation('agent-chat');
|
|
30
30
|
const order = useChatRow(chatId, (row) => row?.order) ?? NO_ITEMS;
|
|
31
|
+
const before = useChatRow(chatId, (row) => row?.subagentsBefore) ?? NO_ITEMS;
|
|
31
32
|
const structure = useChatRow(chatId, (row) => row?.structure);
|
|
32
|
-
const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents(order, structure)), [order, structure]);
|
|
33
|
+
const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents([...before, ...order], structure)), [before, order, structure]);
|
|
33
34
|
const { id, keyOf } = useChatScope();
|
|
34
35
|
const tasks = host.tasks.useTasks(id);
|
|
35
36
|
// The infos of these sub-agents' own chats, so the info of any other chat changing does not draw this again.
|
package/src/state/chats.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ChatInfo,
|
|
10
10
|
ChatItem,
|
|
11
11
|
ChatQuestionItem,
|
|
12
|
+
ChatSubagentItem,
|
|
12
13
|
ChatVisual
|
|
13
14
|
} from '@adecore/agent-contracts';
|
|
14
15
|
import { appendToolOutput } from '@adecore/agent-contracts/text';
|
|
@@ -32,6 +33,11 @@ export interface ChatState {
|
|
|
32
33
|
* `structure` but not in `order`, since the thread on screen does not reach back to them yet.
|
|
33
34
|
*/
|
|
34
35
|
waitingBefore?: string[];
|
|
36
|
+
/*
|
|
37
|
+
* Sub-agents from before that part, held the same way, so the pill over the composer knows of one
|
|
38
|
+
* that a message woke again without a scroll reaching back to its row.
|
|
39
|
+
*/
|
|
40
|
+
subagentsBefore?: string[];
|
|
35
41
|
/* The messages marked to come back to, as the host last said; absent until it said anything. */
|
|
36
42
|
bookmarks?: ChatBookmark[];
|
|
37
43
|
/* The pages agents published in the chat, as the host last said; absent until it said anything. */
|
|
@@ -45,6 +51,8 @@ export interface ChatPage {
|
|
|
45
51
|
history: ChatHistoryPage;
|
|
46
52
|
/* Every request of the chat that still waits, wherever in the thread it is. */
|
|
47
53
|
pending: ChatItem[];
|
|
54
|
+
/* The active sub-agents from before the page; absent from a host that sends none. */
|
|
55
|
+
subagents?: ChatSubagentItem[];
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
/* Rows keyed with the scope's `keyOf`, so a thread says which host it runs on. */
|
|
@@ -93,10 +101,19 @@ function stateOf(info: ChatInfo, items: ChatItem[], page?: ChatPage): ChatState
|
|
|
93
101
|
return { info, items: byId, structure: byId, order };
|
|
94
102
|
}
|
|
95
103
|
const before = page.pending.filter((item) => !(item.id in byId));
|
|
96
|
-
|
|
104
|
+
const subagents = (page.subagents ?? []).filter((item) => !(item.id in byId));
|
|
105
|
+
for (const item of [...before, ...subagents]) {
|
|
97
106
|
byId[item.id] = item;
|
|
98
107
|
}
|
|
99
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
info,
|
|
110
|
+
items: byId,
|
|
111
|
+
structure: byId,
|
|
112
|
+
order,
|
|
113
|
+
history: page.history,
|
|
114
|
+
waitingBefore: before.map((item) => item.id),
|
|
115
|
+
subagentsBefore: subagents.map((item) => item.id)
|
|
116
|
+
};
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
/* An older host names no place in the thread, and it always hands over the whole of it. */
|
|
@@ -104,8 +121,12 @@ function isBeforePage(state: ChatState, historyIndex: number | undefined): boole
|
|
|
104
121
|
return state.history !== undefined && historyIndex !== undefined && historyIndex < state.history.start;
|
|
105
122
|
}
|
|
106
123
|
|
|
107
|
-
/* An item from before the page this client holds matters only as a request that waits, or one that stopped waiting. */
|
|
124
|
+
/* An item from before the page this client holds matters only as a sub-agent, a request that waits, or one that stopped waiting. */
|
|
108
125
|
function withEarlierItem(state: ChatState, item: ChatItem): ChatState {
|
|
126
|
+
if (item.kind === 'subagent') {
|
|
127
|
+
const subagents = state.subagentsBefore ?? [];
|
|
128
|
+
return { ...withItem(state, item), subagentsBefore: subagents.includes(item.id) ? subagents : [...subagents, item.id] };
|
|
129
|
+
}
|
|
109
130
|
const waiting = state.waitingBefore ?? [];
|
|
110
131
|
const held = waiting.includes(item.id);
|
|
111
132
|
if (isWaiting(item)) {
|
|
@@ -129,11 +150,12 @@ export function prependPage(state: ChatState, cursor: string, page: ChatHistoryR
|
|
|
129
150
|
}
|
|
130
151
|
const order = [...olderIds, ...state.order];
|
|
131
152
|
if (page.history.cursor === null) {
|
|
132
|
-
const { history: _history, waitingBefore: _waitingBefore, ...rest } = state;
|
|
153
|
+
const { history: _history, waitingBefore: _waitingBefore, subagentsBefore: _subagentsBefore, ...rest } = state;
|
|
133
154
|
return { ...rest, items, structure: items, order };
|
|
134
155
|
}
|
|
135
156
|
const waitingBefore = (state.waitingBefore ?? []).filter((id) => !olderIds.has(id));
|
|
136
|
-
|
|
157
|
+
const subagentsBefore = (state.subagentsBefore ?? []).filter((id) => !olderIds.has(id));
|
|
158
|
+
return { ...state, items, structure: items, order, history: page.history, waitingBefore, subagentsBefore };
|
|
137
159
|
}
|
|
138
160
|
|
|
139
161
|
/* The requests that still wait, oldest first: those from before the page this client holds, then the thread's own. */
|