@adecore/agents 0.48.0 → 0.48.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.
|
@@ -22,6 +22,10 @@ function continuedNoteId(turnId) {
|
|
|
22
22
|
function newId(prefix) {
|
|
23
23
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24
24
|
}
|
|
25
|
+
/* The note of what the agent heard in front of a prompt, which a person reads in the turn it opened. */
|
|
26
|
+
function saidNote(turnId, now, text) {
|
|
27
|
+
return { id: newId('note'), kind: 'note', createdAt: now, turnId, level: 'info', text, said: true };
|
|
28
|
+
}
|
|
25
29
|
/*
|
|
26
30
|
* Item and request ids carry the generation of the CLI that made them, and a restart of the host
|
|
27
31
|
* must not hand out one a stored thread already holds. Starting from the clock puts every run above
|
|
@@ -834,7 +838,7 @@ export class ChatSession {
|
|
|
834
838
|
const context = this.contextNote(words.prompt);
|
|
835
839
|
const preamble = [words.preamble, context.preamble].filter(Boolean).join('\n\n') || null;
|
|
836
840
|
if (context.note) {
|
|
837
|
-
this.emit([this.thread.upsert(
|
|
841
|
+
this.emit([this.thread.upsert(saidNote(turnId, now, context.note))]);
|
|
838
842
|
}
|
|
839
843
|
const input = { promptId: randomUUID(), text: words.prompt, preamble, attachments: [], mentions: [], skills: [] };
|
|
840
844
|
this.submission = { turnId, input };
|
|
@@ -900,7 +904,7 @@ export class ChatSession {
|
|
|
900
904
|
...(wake.note === undefined
|
|
901
905
|
? []
|
|
902
906
|
: [this.thread.upsert({ id: newId('note'), kind: 'note', createdAt: now, turnId, level: 'info', text: wake.note })]),
|
|
903
|
-
...(note === null ? [] : [this.thread.upsert(
|
|
907
|
+
...(note === null ? [] : [this.thread.upsert(saidNote(turnId, now, note))]),
|
|
904
908
|
this.thread.patchInfo({ status: this.thread.statusFor(turnId), activeTurnId: turnId })
|
|
905
909
|
]);
|
|
906
910
|
this.options.persist();
|
|
@@ -1383,7 +1387,7 @@ export class ChatSession {
|
|
|
1383
1387
|
const now = Date.now();
|
|
1384
1388
|
const events = [this.thread.upsert({ id: turnId, kind: 'turn', createdAt: now, turnId, state: 'running', origin: 'user', endedAt: null, costUsd: 0 })];
|
|
1385
1389
|
if (note !== null) {
|
|
1386
|
-
events.push(this.thread.upsert(
|
|
1390
|
+
events.push(this.thread.upsert(saidNote(turnId, now, note)));
|
|
1387
1391
|
}
|
|
1388
1392
|
if (text !== null) {
|
|
1389
1393
|
const mentions = extras.mentions?.length ? extras.mentions : undefined;
|
|
@@ -9,13 +9,13 @@ export interface MessageWords {
|
|
|
9
9
|
}
|
|
10
10
|
export interface ChatNoticeTargets {
|
|
11
11
|
chat(id: string): Promise<'idle' | 'running' | 'none'>;
|
|
12
|
-
|
|
12
|
+
wokenBy(id: string): readonly string[];
|
|
13
13
|
}
|
|
14
14
|
export interface ChatDelivery {
|
|
15
15
|
outcome: 'wake' | 'no-chat' | 'in-turn' | 'from-message';
|
|
16
16
|
waiting: number;
|
|
17
17
|
}
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function turnWokenBy(items: readonly ChatItem[], activeTurnId: string | null): readonly string[];
|
|
19
19
|
export declare function chatNoticeTargets(chats: Pick<ChatCore, 'get' | 'hasStored'>): ChatNoticeTargets;
|
|
20
20
|
export declare function deliverToChat(store: Pick<NoticeStore, 'put'>, targets: ChatNoticeTargets, notice: Omit<Notice, 'createdAt'>): Promise<ChatDelivery>;
|
|
21
21
|
export interface NoticeChat {
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
3
|
-
* read each other from waking each other forever. It rides on the turn,
|
|
4
|
-
* Between turns the last one counts, since work it left running (a
|
|
5
|
-
* write, and a turn the CLI opened by itself takes the step of the
|
|
2
|
+
* Who sent the message that opened the turn a chat is running, empty when no message opened it,
|
|
3
|
+
* which stops two agents that read each other from waking each other forever. It rides on the turn,
|
|
4
|
+
* so it survives a restart. Between turns the last one counts, since work it left running (a
|
|
5
|
+
* background subagent) may still write, and a turn the CLI opened by itself takes the step of the
|
|
6
|
+
* turn before it.
|
|
6
7
|
*/
|
|
7
|
-
export function
|
|
8
|
+
export function turnWokenBy(items, activeTurnId) {
|
|
8
9
|
const from = activeTurnId === null ? items.findLastIndex((item) => item.kind === 'turn') : items.findIndex((item) => item.id === activeTurnId);
|
|
9
10
|
for (let i = from; i >= 0; i--) {
|
|
10
11
|
const turn = items[i];
|
|
11
12
|
if (turn.kind !== 'turn') {
|
|
12
13
|
continue;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const senders = turn.messageFrom ?? [];
|
|
16
|
+
if (senders.length > 0) {
|
|
17
|
+
return senders;
|
|
16
18
|
}
|
|
17
19
|
if (turn.origin !== 'agent' || turn.taskIds !== undefined || turn.summaryFor !== undefined) {
|
|
18
|
-
return
|
|
20
|
+
return [];
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
|
-
return
|
|
23
|
+
return [];
|
|
22
24
|
}
|
|
23
25
|
/* The chats of a core as a message sees them. */
|
|
24
26
|
export function chatNoticeTargets(chats) {
|
|
@@ -31,17 +33,19 @@ export function chatNoticeTargets(chats) {
|
|
|
31
33
|
// A chat nobody has loaded is idle: the turn opens on the thread the host reads back from disk.
|
|
32
34
|
return (await chats.hasStored(id)) ? 'idle' : 'none';
|
|
33
35
|
},
|
|
34
|
-
|
|
36
|
+
wokenBy: (id) => {
|
|
35
37
|
const session = chats.get(id);
|
|
36
|
-
return session
|
|
38
|
+
return session === undefined ? [] : turnWokenBy(session.thread.list(), session.info.activeTurnId);
|
|
37
39
|
}
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
/*
|
|
41
43
|
* One message on its way to a chat, and whether the chat owes a turn on it. A chat between turns gets
|
|
42
44
|
* one, as for a settled task, or the message sits there until someone happens to prompt that chat.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
+
* A turn a message opened wakes nobody with a message of its own, except the sender it answers,
|
|
46
|
+
* once: only while that sender's own turn was opened by no message, so a question and its answer
|
|
47
|
+
* take two steps and the turn the answer opens wakes nobody. A node that runs something else, such
|
|
48
|
+
* as a terminal, is the host's to deliver to before this.
|
|
45
49
|
*/
|
|
46
50
|
export async function deliverToChat(store, targets, notice) {
|
|
47
51
|
const waiting = await store.put(notice);
|
|
@@ -52,7 +56,9 @@ export async function deliverToChat(store, targets, notice) {
|
|
|
52
56
|
if (chat === 'running') {
|
|
53
57
|
return { outcome: 'in-turn', waiting };
|
|
54
58
|
}
|
|
55
|
-
|
|
59
|
+
const wokenBy = targets.wokenBy(notice.from);
|
|
60
|
+
const answer = wokenBy.includes(notice.targetId) && targets.wokenBy(notice.targetId).length === 0;
|
|
61
|
+
if (wokenBy.length > 0 && !answer) {
|
|
56
62
|
return { outcome: 'from-message', waiting };
|
|
57
63
|
}
|
|
58
64
|
return { outcome: 'wake', waiting };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.2",
|
|
4
4
|
"description": "Node-compatible agent chat hosts, providers, accounts, usage and durable task coordination.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"test": "bun --conditions=source test"
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@adecore/agent-contracts": "0.48.
|
|
97
|
-
"@adecore/intelligent-ui": "0.48.
|
|
96
|
+
"@adecore/agent-contracts": "0.48.2",
|
|
97
|
+
"@adecore/intelligent-ui": "0.48.2",
|
|
98
98
|
"zod": "^4.6.7"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
package/src/chat/chat-session.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ChatGoalAction,
|
|
7
7
|
ChatInfo,
|
|
8
8
|
ChatItem,
|
|
9
|
+
ChatNoteItem,
|
|
9
10
|
ChatNoteMessage,
|
|
10
11
|
ChatQueuedMessage,
|
|
11
12
|
ChatQuoteSource,
|
|
@@ -157,6 +158,11 @@ function newId(prefix: string): string {
|
|
|
157
158
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
/* The note of what the agent heard in front of a prompt, which a person reads in the turn it opened. */
|
|
162
|
+
function saidNote(turnId: string, now: number, text: string): ChatNoteItem {
|
|
163
|
+
return { id: newId('note'), kind: 'note', createdAt: now, turnId, level: 'info', text, said: true };
|
|
164
|
+
}
|
|
165
|
+
|
|
160
166
|
/*
|
|
161
167
|
* Item and request ids carry the generation of the CLI that made them, and a restart of the host
|
|
162
168
|
* must not hand out one a stored thread already holds. Starting from the clock puts every run above
|
|
@@ -1184,7 +1190,7 @@ export class ChatSession {
|
|
|
1184
1190
|
const preamble = [words.preamble, context.preamble].filter(Boolean).join('\n\n') || null;
|
|
1185
1191
|
|
|
1186
1192
|
if (context.note) {
|
|
1187
|
-
this.emit([this.thread.upsert(
|
|
1193
|
+
this.emit([this.thread.upsert(saidNote(turnId, now, context.note))]);
|
|
1188
1194
|
}
|
|
1189
1195
|
|
|
1190
1196
|
const input: TurnInput = { promptId: randomUUID(), text: words.prompt, preamble, attachments: [], mentions: [], skills: [] };
|
|
@@ -1271,7 +1277,7 @@ export class ChatSession {
|
|
|
1271
1277
|
...(wake.note === undefined
|
|
1272
1278
|
? []
|
|
1273
1279
|
: [this.thread.upsert({ id: newId('note'), kind: 'note', createdAt: now, turnId, level: 'info', text: wake.note })]),
|
|
1274
|
-
...(note === null ? [] : [this.thread.upsert(
|
|
1280
|
+
...(note === null ? [] : [this.thread.upsert(saidNote(turnId, now, note))]),
|
|
1275
1281
|
this.thread.patchInfo({ status: this.thread.statusFor(turnId), activeTurnId: turnId })
|
|
1276
1282
|
]);
|
|
1277
1283
|
|
|
@@ -1878,7 +1884,7 @@ export class ChatSession {
|
|
|
1878
1884
|
const events = [this.thread.upsert({ id: turnId, kind: 'turn', createdAt: now, turnId, state: 'running', origin: 'user', endedAt: null, costUsd: 0 })];
|
|
1879
1885
|
|
|
1880
1886
|
if (note !== null) {
|
|
1881
|
-
events.push(this.thread.upsert(
|
|
1887
|
+
events.push(this.thread.upsert(saidNote(turnId, now, note)));
|
|
1882
1888
|
}
|
|
1883
1889
|
|
|
1884
1890
|
if (text !== null) {
|
|
@@ -18,8 +18,8 @@ export interface MessageWords {
|
|
|
18
18
|
export interface ChatNoticeTargets {
|
|
19
19
|
/* What the host holds under this id: a chat between turns, one that is in a turn, or no chat at all. */
|
|
20
20
|
chat(id: string): Promise<'idle' | 'running' | 'none'>;
|
|
21
|
-
/*
|
|
22
|
-
|
|
21
|
+
/* The senders whose message opened the turn this node is running, empty for a turn no message opened. */
|
|
22
|
+
wokenBy(id: string): readonly string[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/*
|
|
@@ -33,12 +33,13 @@ export interface ChatDelivery {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/*
|
|
36
|
-
*
|
|
37
|
-
* read each other from waking each other forever. It rides on the turn,
|
|
38
|
-
* Between turns the last one counts, since work it left running (a
|
|
39
|
-
* write, and a turn the CLI opened by itself takes the step of the
|
|
36
|
+
* Who sent the message that opened the turn a chat is running, empty when no message opened it,
|
|
37
|
+
* which stops two agents that read each other from waking each other forever. It rides on the turn,
|
|
38
|
+
* so it survives a restart. Between turns the last one counts, since work it left running (a
|
|
39
|
+
* background subagent) may still write, and a turn the CLI opened by itself takes the step of the
|
|
40
|
+
* turn before it.
|
|
40
41
|
*/
|
|
41
|
-
export function
|
|
42
|
+
export function turnWokenBy(items: readonly ChatItem[], activeTurnId: string | null): readonly string[] {
|
|
42
43
|
const from = activeTurnId === null ? items.findLastIndex((item) => item.kind === 'turn') : items.findIndex((item) => item.id === activeTurnId);
|
|
43
44
|
|
|
44
45
|
for (let i = from; i >= 0; i--) {
|
|
@@ -48,16 +49,18 @@ export function turnFromMessage(items: readonly ChatItem[], activeTurnId: string
|
|
|
48
49
|
continue;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const senders = turn.messageFrom ?? [];
|
|
53
|
+
|
|
54
|
+
if (senders.length > 0) {
|
|
55
|
+
return senders;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
if (turn.origin !== 'agent' || turn.taskIds !== undefined || turn.summaryFor !== undefined) {
|
|
56
|
-
return
|
|
59
|
+
return [];
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
return
|
|
63
|
+
return [];
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
/* The chats of a core as a message sees them. */
|
|
@@ -73,10 +76,10 @@ export function chatNoticeTargets(chats: Pick<ChatCore, 'get' | 'hasStored'>): C
|
|
|
73
76
|
// A chat nobody has loaded is idle: the turn opens on the thread the host reads back from disk.
|
|
74
77
|
return (await chats.hasStored(id)) ? 'idle' : 'none';
|
|
75
78
|
},
|
|
76
|
-
|
|
79
|
+
wokenBy: (id) => {
|
|
77
80
|
const session = chats.get(id);
|
|
78
81
|
|
|
79
|
-
return session
|
|
82
|
+
return session === undefined ? [] : turnWokenBy(session.thread.list(), session.info.activeTurnId);
|
|
80
83
|
}
|
|
81
84
|
};
|
|
82
85
|
}
|
|
@@ -84,8 +87,10 @@ export function chatNoticeTargets(chats: Pick<ChatCore, 'get' | 'hasStored'>): C
|
|
|
84
87
|
/*
|
|
85
88
|
* One message on its way to a chat, and whether the chat owes a turn on it. A chat between turns gets
|
|
86
89
|
* one, as for a settled task, or the message sits there until someone happens to prompt that chat.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
90
|
+
* A turn a message opened wakes nobody with a message of its own, except the sender it answers,
|
|
91
|
+
* once: only while that sender's own turn was opened by no message, so a question and its answer
|
|
92
|
+
* take two steps and the turn the answer opens wakes nobody. A node that runs something else, such
|
|
93
|
+
* as a terminal, is the host's to deliver to before this.
|
|
89
94
|
*/
|
|
90
95
|
export async function deliverToChat(store: Pick<NoticeStore, 'put'>, targets: ChatNoticeTargets, notice: Omit<Notice, 'createdAt'>): Promise<ChatDelivery> {
|
|
91
96
|
const waiting = await store.put(notice);
|
|
@@ -99,7 +104,10 @@ export async function deliverToChat(store: Pick<NoticeStore, 'put'>, targets: Ch
|
|
|
99
104
|
return { outcome: 'in-turn', waiting };
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
const wokenBy = targets.wokenBy(notice.from);
|
|
108
|
+
const answer = wokenBy.includes(notice.targetId) && targets.wokenBy(notice.targetId).length === 0;
|
|
109
|
+
|
|
110
|
+
if (wokenBy.length > 0 && !answer) {
|
|
103
111
|
return { outcome: 'from-message', waiting };
|
|
104
112
|
}
|
|
105
113
|
|