@adecore/agents-react 0.26.0 → 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.
@@ -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 = 60;
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 { ChatInfo, ChatItem, ChatSubagentItem } from '@adecore/agent-contracts';
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
- const lastMessage = order.findLastIndex((id) => structure[id]?.kind === 'user');
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?.kind === 'subagent' ? [{ item, index }] : [];
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.
@@ -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'>>;
@@ -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
- for (const item of before) {
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 { info, items: byId, structure: byId, order, history: page.history, waitingBefore: before.map((item) => item.id) };
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
- return { ...state, items, structure: items, order, history: page.history, waitingBefore };
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.0",
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.0",
71
- "@adecore/editor": "0.26.0",
72
- "@adecore/editor-react": "0.26.0",
73
- "@adecore/intelligent-ui": "0.26.0",
74
- "@adecore/merge": "0.26.0",
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.0",
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.0",
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",
@@ -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 = 60;
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 = result.history === undefined ? undefined : { history: result.history, pending: result.pending ?? [] };
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 { ChatInfo, ChatItem, ChatSubagentItem } from '@adecore/agent-contracts';
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
- const lastMessage = order.findLastIndex((id) => structure[id]?.kind === 'user');
69
- const lastMessageAt = lastMessage < 0 ? null : (structure[order[lastMessage]!]?.createdAt ?? null);
70
- const items = order.flatMap((id, index) => {
71
- const item = structure[id];
72
- return item?.kind === 'subagent' ? [{ item, index }] : [];
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.
@@ -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
- for (const item of before) {
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 { info, items: byId, structure: byId, order, history: page.history, waitingBefore: before.map((item) => item.id) };
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
- return { ...state, items, structure: items, order, history: page.history, waitingBefore };
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. */