@adecore/agents 0.48.0 → 0.48.1

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.
@@ -9,13 +9,13 @@ export interface MessageWords {
9
9
  }
10
10
  export interface ChatNoticeTargets {
11
11
  chat(id: string): Promise<'idle' | 'running' | 'none'>;
12
- fromMessage(id: string): boolean;
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 turnFromMessage(items: readonly ChatItem[], activeTurnId: string | null): boolean;
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
- * Whether the turn a chat is running was itself opened by a message, which stops two agents that
3
- * read each other from waking each other forever. It rides on the turn, so it survives a restart.
4
- * Between turns the last one counts, since work it left running (a background subagent) may still
5
- * write, and a turn the CLI opened by itself takes the step of the turn before it.
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 turnFromMessage(items, activeTurnId) {
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
- if ((turn.messageFrom ?? []).length > 0) {
15
- return true;
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 false;
20
+ return [];
19
21
  }
20
22
  }
21
- return false;
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
- fromMessage: (id) => {
36
+ wokenBy: (id) => {
35
37
  const session = chats.get(id);
36
- return session !== undefined && turnFromMessage(session.thread.list(), session.info.activeTurnId);
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
- * One step deep: the turn a message opened wakes nobody with a message of its own. A node that runs
44
- * something else, such as a terminal, is the host's to deliver to before this.
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
- if (targets.fromMessage(notice.from)) {
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.0",
3
+ "version": "0.48.1",
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.0",
97
- "@adecore/intelligent-ui": "0.48.0",
96
+ "@adecore/agent-contracts": "0.48.1",
97
+ "@adecore/intelligent-ui": "0.48.1",
98
98
  "zod": "^4.6.7"
99
99
  },
100
100
  "devDependencies": {
@@ -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
- /* Whether the turn this node is running was opened by a message of its own, which is where waking stops. */
22
- fromMessage(id: string): boolean;
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
- * Whether the turn a chat is running was itself opened by a message, which stops two agents that
37
- * read each other from waking each other forever. It rides on the turn, so it survives a restart.
38
- * Between turns the last one counts, since work it left running (a background subagent) may still
39
- * write, and a turn the CLI opened by itself takes the step of the turn before it.
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 turnFromMessage(items: readonly ChatItem[], activeTurnId: string | null): boolean {
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
- if ((turn.messageFrom ?? []).length > 0) {
52
- return true;
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 false;
59
+ return [];
57
60
  }
58
61
  }
59
62
 
60
- return false;
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
- fromMessage: (id) => {
79
+ wokenBy: (id) => {
77
80
  const session = chats.get(id);
78
81
 
79
- return session !== undefined && turnFromMessage(session.thread.list(), session.info.activeTurnId);
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
- * One step deep: the turn a message opened wakes nobody with a message of its own. A node that runs
88
- * something else, such as a terminal, is the host's to deliver to before this.
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
- if (targets.fromMessage(notice.from)) {
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